This document describes the project's security posture, dependency policy, and stability-related concerns.
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.
New external dependencies are discouraged and require explicit justification before being added. Any proposed dependency must satisfy the following criteria:
- No standard library alternative — the functionality cannot be reasonably
implemented with
stdalone. - Minimal transitive graph — the crate itself must have few or no dependencies.
- Active maintenance — the crate must be actively maintained and compatible with the project's MSRV (minimum supported Rust version).
- 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.
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, andpathlibfrom 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.
Zenritme takes over the terminal during a session. The following measures ensure terminal safety:
- TerminalGuard captures the exact terminal state via
stty -gbefore making changes and restores it precisely on drop, even across panics (thanks to Rust'sDropsemantics). - 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 sanefallback is available if the exact state restore fails. - The recommended way to exit is pressing
qorEsc, 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
Dropimplementations, 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'sDropcleanup entirely. The terminal may be left in raw mode or alternate-screen state. - If the terminal appears stuck after a signal termination, press
Ctrl+Jthen typestty saneand press Enter, or simply open a new terminal window. This is a fundamental limitation of signal handling, not a bug in Zenritme.
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-updatecommand. - 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-playplayback.
See docs/ENDURANCE.md for the recommended long-running test procedure.
If you discover a security issue, please open a GitHub issue with the
security label or contact the maintainer directly.
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 | shpattern — the project does not support or recommend remote-script installation. - No
sudocalls — 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.
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 | shpattern — the response body is parsed in-process to extract the version tag; downloaded content is never executed. - No auto-install — there is no
--install-updateflag. Users manually download and install updates. - No authentication required — the public GitHub API is used without tokens.
- Timeout-bounded — the
curlrequest uses--max-time 15to 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_namefield is extracted using a hand-rolled parser (noserde_jsondependency).
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.