Files
schedls/docs/security-model.md
T
mdaleo404 aded37d491
CI / test (3.11) (push) Canceled after 0s
CI / test (3.12) (push) Canceled after 0s
CI / test (3.13) (push) Canceled after 0s
CI / test (3.14) (push) Canceled after 0s
CI / package (push) Canceled after 0s
CI / precommit-and-security (push) Canceled after 11s
CI / typecheck (push) Canceled after 11s
Initial code commit
2026-09-25 11:16:19 +01:00

4.8 KiB

Security model

This document describes what schedls trusts, what it does not, and how the main threats are mitigated. It complements SECURITY.md.

Trust model

  • Native schedulers are authoritative. systemd and cron own execution. schedls only reads and writes their configuration.
  • Discovered configuration is data. Commands and schedules found on the host are parsed and displayed, never executed.
  • Unmanaged objects are protected. A timer or cron entry not created by schedls cannot be modified or removed by default.
  • The user chooses privilege. schedls never escalates on the user's behalf.

Threats and mitigations

Malicious schedule names

Names must match [A-Za-z0-9][A-Za-z0-9_.-]{0,63}. Path separators, traversal, whitespace and shell metacharacters are rejected. Names are never interpolated into shell strings.

Malicious command arguments

Arguments are kept as an argv internally. systemd units are rendered with a dedicated serializer that always quotes arguments and escapes backslash, double quotes, literal $ and %; arguments containing NUL or newline are rejected. cron commands are serialized with shell quoting and then have every % escaped for cron's own parser. Calendar expressions are validated with systemd-analyze and must not contain control characters or begin with -; timer durations (--jitter, --accuracy) are validated with systemd-analyze timespan. If a safe representation cannot be guaranteed, creation is refused.

Before replacing a file, schedls uses lstat, rejects symlinks and non-regular files, and verifies ownership. Destination directories must be real directories owned by the expected user (root when running as root) and must not be group- or other-writable. Temporary files are created in the same directory and atomically renamed.

PATH hijacking

Critical helpers (systemctl, systemd-analyze, journalctl, crontab, loginctl) are resolved once with a controlled lookup. A helper is rejected unless the executable and its realpath target are regular files, are owned by root (when schedls runs as root) or by root or the current user, and live in a directory that is not group- or other-writable. Helper processes run with a fixed PATH rather than the caller's.

Path traversal through discovered unit names

A Unit= value read from a timer file is treated as untrusted data. Mutation never uses it to build a filesystem path: unit names are always derived from the validated job name (schedls-<name>.timer / .service). Generated unit names are re-validated before they are written to a temporary directory, so a crafted unit cannot redirect writes or removals outside the trusted unit directory.

Untrusted output

journalctl output is program output, not configuration. When stdout is a terminal, control characters are escaped before display so a log line cannot inject terminal escape sequences; redirected output is passed through unchanged. Machine-readable --json output escapes control characters as part of JSON encoding.

Environment injection and pagers

Helper commands run with a controlled environment (LC_ALL=C, SYSTEMD_COLORS=0, SYSTEMD_PAGER=cat, PAGER=cat) and never with a shell. Pagers and editors are never launched while schedls is running. Environment values passed with --env are written to unit files with systemd quoting and are world-readable like any other unit; do not put secrets in them.

TOCTOU races

Plans snapshot existing content. At apply time the current content is compared against the snapshot; a mismatch aborts the operation. Privileged critical sections are kept small and use file descriptors / atomic rename where practical.

Corrupt or malicious existing configuration

Configuration is treated as opaque data. Malformed schedls cron markers cause mutation to fail closed rather than guess. Unknown crontab lines are preserved byte-for-byte. A systemd unit whose name starts with schedls- is only treated as managed when its file carries the # Managed by schedls and # Name: markers, so a same-named foreign unit is not modified.

Partial operations

systemd creation writes both units, reloads the manager, and enables the timer; if a later step fails, or the operation is interrupted, the previous state is restored and a partially enabled timer is disabled again. Cron installs are validated first and restored from the previous crontab text on failure.

Supply chain

Runtime dependencies are zero. Development dependencies are locked and audited in CI.

Availability, privacy

0.1.0 requires systemd-analyze to create or validate calendar expressions and refuses the operation if it is unavailable. schedls makes no network requests and collects no telemetry.