-
Notifications
You must be signed in to change notification settings - Fork 0
Security Model
A practical summary. The authoritative version is
docs/security.md.
The short version: the manifest is trusted configuration; everything it points at is not. Downloads, archive metadata, terminal text and process output are treated as untrusted input at display and install boundaries.
| Boundary | Rule |
|---|---|
| π YAML parsing | Fields that would execute scripts are rejected before planning. |
| π URLs | HTTPS with a host, always. |
| π³οΈ Hosts | Names that resolve to loopback, link-local, private, site-local, multicast or known cloud-metadata endpoints are rejected β and every redirect hop is re-validated before it is followed. |
| π€ Interpolation | Only a fixed allowlist of non-secret environment variables (HOME, USER, LOGNAME, SHELL, XDG_*) is exposed. A manifest cannot interpolate a process secret into an outbound URL. |
| π₯ Downloaded bytes | Untrusted until bounded, checksum-verified when configured, extracted or staged, and executables verified. |
| ποΈ Archive members | Names and mapped targets must stay inside staging or install roots. |
| π₯οΈ Terminal output | Control characters scrubbed, sensitive environment-derived values redacted at display time. |
| πΎ State files | Local resume metadata β not a security authority. |
A profile containing an installer: block fails at config load:
installer scripts are not supported; use direct binary or archive download
This is deliberate. It keeps shell syntax, inherited environment, cleanup semantics, timeouts and sudo behaviour out of the manifest contract entirely.
-
zip,tar.gzandtar.xzare all decoded in-process. Extraction does not shell out to the systemtarorunzip. - Unsafe member paths are rejected before anything is written.
- The tar path rejects symlink, hardlink and unsupported entry types.
- Duplicate member sources and duplicate output targets are rejected.
- An aggregate expanded-byte budget bounds decompression bombs, independent of the compressed download cap.
- Path confinement is equivalent across all three archive types.
- SHA-256 only, exactly 64 hex characters.
- Verified before the staged install replaces the previous one.
- Missing checksums are allowed in
developermode and surfaced loudly asnot configured/missing/no-checksumrisk markers in plan output. -
policy.mode: strictrejects missing checksums unlesspolicy.allowMissingChecksums: trueis explicitly set.
Strict-policy failures render with stable strict-policy[...] codes and matching
suggestion[...] hints, so they are easy to grep in CI logs.
Sudo is available for one thing: creating symlinks. It requires all of:
-
policy.allowSudoSymlinks: truein the manifest -
sudo: trueon the symlink entry - An absolute destination path
Ordinary downloads, extraction, executable checks, local symlinks and state writes never cross the sudo boundary.
The flow:
- Probe cached credentials with fixed
sudo -n trueargv. - If cached, create the link with fixed
sudo -n ln -sfn <target> <path>argv β no password requested. - Otherwise ask the injected credential provider for one password, for that one operation.
- Interactive CLI runs prompt on the terminal. Non-interactive runs fail closed.
- Password-backed commands use fixed
sudo -S -p "" ln -sfn <target> <path>argv plus secret stdin.
Passwords never appear in argv, environment variables, command previews, diagnostics, installer
events, apply state or logs. Cancelling a credential prompt fails that symlink only;
continueOnError decides whether later tools proceed.
- Sensitive runtime-variable-derived values are scrubbed from plan output,
versionsoutput, apply errors, progress labels, command output tails and state messages. - Redaction is display-only β raw values are preserved for filesystem and network behaviour, so legitimate paths and URLs are never corrupted.
- Untrusted text is collapsed into safe terminal lines, so config values, resolver output and command stdout/stderr cannot inject cursor movement, alternate-screen toggles, colour resets or mouse-mode escapes into your terminal.
- The state path comes from
--stateorspec.policy.stateFile. - Only current-directory filenames are accepted β absolute, nested and empty paths are rejected.
-
plannever validates or touches state. - State is written after terminal tool results via a same-directory temp file and an atomic move.
- Incompatible profile names or manifest fingerprints fail unless
--reset-stateis passed.
- Each release artifact carries a
.sha256file. - Releases are signed with keyless Sigstore signatures.
-
scripts/install.shverifies the checksum always, and the signature whencosignis present.
βΉοΈ The per-release SHA-256 files share the release origin, so on their own they defend against corruption rather than acting as an independent trust anchor. The Sigstore signature is the stronger anchor β install
cosignbefore running the install script if that matters to you.
Documented honestly rather than hidden:
- Download body deadlines are checked at chunk boundaries, not continuously.
- Missing checksums are still accepted by
developer-mode profiles. - ZIP external attributes are deferred.
- Credential-provider cancellation is scoped to the current privileged operation β it is not a general cancellation mechanism for a running download, extraction or command.
- Native-image validation is environment-bound when
native-imageis not installed locally; the release workflow remains the native build boundary.
See docs/hardening-review.md
for deferred items and rationale.
spec:
policy:
mode: strict # reject latest-URLs, missing checksums, sudo symlinks
allowSudoSymlinks: false # keep everything user-scoped
appsDir: "${HOME}/.apps"Then pin every version, give every download a checksum, commit a lock file, and run
binstaller apply --locked.
Please open a GitHub issue for non-sensitive findings. For anything you believe is sensitive, use GitHub's private vulnerability reporting on the repository's Security tab rather than a public issue.
- π Home
- π Getting Started
- ποΈ CLI Reference
- π³ Manifest Cookbook
- π§ Lock Files
- π Security Model
- π©Ί Troubleshooting
- β FAQ
- π οΈ Development