Skip to content

Security: oxyzenQ/zenritme

docs/SECURITY.md

Zenritme Security and Supply-Chain Policy

This document describes the project's security posture, dependency policy, and stability-related concerns.

Zero external dependency policy

Zenritme currently has zero external Rust dependencies (see Cargo.toml). All functionality is implemented using only the Rust standard library and platform APIs invoked through std::process::Command.

This is an intentional design choice:

  • Smallest possible attack surface — no third-party code enters the binary.
  • Predictable builds — the compiler, standard library, and system tools are the only inputs to the build.
  • Audit simplicity — every line of code in the project is written and reviewed by the project maintainers.

Supply-chain minimization

New external dependencies are discouraged and require explicit justification before being added. Any proposed dependency must satisfy the following criteria:

  1. No standard library alternative — the functionality cannot be reasonably implemented with std alone.
  2. Minimal transitive graph — the crate itself must have few or no dependencies.
  3. Active maintenance — the crate must be actively maintained and compatible with the project's MSRV (minimum supported Rust version).
  4. Permissive or GPL-compatible license — the crate's license must be compatible with GPL-3.0-only.

When a dependency is accepted, it should be pinned to an exact version in Cargo.toml and audited periodically with cargo audit.

Procedural sound assets

Zenritme bundles four built-in notification sounds (start, pause, phase, complete) that are embedded into the binary at compile time via include_bytes!.

These sounds are procedurally generated by scripts/generate-sounds.py using only the Python standard library:

  • No downloaded samples — no audio files are fetched from the internet.
  • No copyrighted content — every sound is a pure sine-wave composition generated by deterministic math.
  • No external Python dependencies — only wave, math, struct, and pathlib from the standard library.
  • No network access — generation is fully offline.
  • Reproducible — re-running the generator script produces identical output.

External sound overrides are supported via environment variables (ZENRITME_SOUND_START, ZENRITME_SOUND_PAUSE, etc.). These overrides must be local file paths only — no URLs, no network fetching. The binary resolves override paths via std::env::var and passes them directly to pw-play for playback; no content is parsed or executed.

A no-spam cooldown system prevents rapid sound toggling from producing audible spam (500 ms pause, 1 s phase, 2 s complete debounce). Sound profiles (--sound-profile calm|silent) provide session-wide control.

The sound system is organized into focused submodules (src/sound/assets.rs, src/sound/playback.rs, src/sound/resolve.rs, src/sound/cooldown.rs, src/sound/cleanup.rs) for auditability. Temp sound files are automatically cleaned up on process exit via an RAII guard (TempCleanupGuard). The cleanup is PID-specific and only targets the zenritme-sounds-{PID} directory under the system temp folder — it never touches user files outside that scope.

Terminal safety

Zenritme takes over the terminal during a session. The following measures ensure terminal safety:

  • TerminalGuard captures the exact terminal state via stty -g before making changes and restores it precisely on drop, even across panics (thanks to Rust's Drop semantics).
  • Alternate screen buffer is used when available, so the original terminal content is preserved and restored on exit.
  • Raw mode is entered for single-keypress input and fully reversed on exit.
  • A stty sane fallback is available if the exact state restore fails.
  • The recommended way to exit is pressing q or Esc, which returns from the main loop and reliably drops all guards (TerminalGuard, TempCleanupGuard), restoring terminal state and removing temp files.
  • Panic unwind may also fire the Drop implementations, depending on whether the panic is caught or aborts the process.
  • Unhandled SIGINT (Ctrl+C) and SIGKILL (kill -9) are signal-level terminations that may bypass Rust's Drop cleanup entirely. The terminal may be left in raw mode or alternate-screen state.
  • If the terminal appears stuck after a signal termination, press Ctrl+J then type stty sane and press Enter, or simply open a new terminal window. This is a fundamental limitation of signal handling, not a bug in Zenritme.

Long-running stability

As a timer application, Zenritme may run for hours. Stability concerns include:

  • No unbounded allocations — all internal data structures have fixed or bounded size.
  • No unbounded threads — only a single reader thread is spawned for input.
  • No network access for timers — normal timer modes (timer-up, timer-down, stopwatch, pomodoro) never connect to the network. Network access is used only by the opt-in --check-update command.
  • No file I/O during runtime — Zenritme does not read or write files while running (configuration is passed via CLI arguments and environment variables). The only exception is a temp directory used to extract embedded sound files for pw-play playback.

See docs/ENDURANCE.md for the recommended long-running test procedure.

Reporting vulnerabilities

If you discover a security issue, please open a GitHub issue with the security label or contact the maintainer directly.

Install script security

The install and uninstall scripts (scripts/install.sh, scripts/uninstall.sh) are designed with a minimal-trust approach:

  • No network access — they copy a binary from the local filesystem only.
  • No curl | sh pattern — the project does not support or recommend remote-script installation.
  • No sudo calls — privilege escalation is the caller's responsibility.
  • Strict shell mode — all scripts use set -euo pipefail.
  • Syntax-validated — all scripts pass bash -n.

See RULES.md for the full install script safety policy.

Update checker security

The --check-update command (src/update.rs) is designed as a read-only, minimal-trust network operation:

  • Read-only — it only queries the GitHub releases API and prints a status report. It never downloads, installs, or replaces any binaries.
  • No curl | sh pattern — the response body is parsed in-process to extract the version tag; downloaded content is never executed.
  • No auto-install — there is no --install-update flag. Users manually download and install updates.
  • No authentication required — the public GitHub API is used without tokens.
  • Timeout-bounded — the curl request uses --max-time 15 to prevent indefinite hanging.
  • No shell interpolation — the API URL is a compile-time constant; no user input is interpolated into shell commands.
  • Minimal JSON parsing — only the tag_name field is extracted using a hand-rolled parser (no serde_json dependency).

If a self-update feature (--install-update) were ever added, it would require checksum verification, optional signature validation, and an explicit user confirmation step before replacing the binary. No such feature is currently planned.

There aren't any published security advisories