This file is downstream-only. Do not port it upstream to cachix/secretspec.
| Path | Purpose |
|---|---|
sudo-secretspec-cli/ |
Rust companion binary/library (the product) |
sudo-secretspec/ |
Guidance, config example, companion README |
packaging/ |
Release script + Homebrew formula |
skills/sudo-secretspec/ |
Distributable AI skill |
All downstream work commits directly to sudo-main; there are no feature
branches and no PRs. "main" and "master" both mean sudo-main. main itself
is only the upstream mirror — do not develop on it, and do not merge it into
sudo-main unasked, since it carries post-0.19.1 upstream work held for a
future release. The downstream version scheme lives in CLAUDE.md, which is
loaded into agent context automatically.
-
Do not write complex shell for the boundary. Runtime policy is Rust.
-
Avoid
rusqlitebundledon this Mac. It can hang inlibsqlite3-sysbuild scripts. Prefer system SQLite:export PKG_CONFIG_PATH="/opt/homebrew/opt/sqlite/lib/pkgconfig:$PKG_CONFIG_PATH" export LIBRARY_PATH="/opt/homebrew/opt/sqlite/lib:$LIBRARY_PATH" export CPATH="/opt/homebrew/opt/sqlite/include:$CPATH"
and
rusqlite = { version = "0.31" }(nobundled). -
Cargo registry extract cache can be incomplete after interrupted builds. Incomplete dirs under
~/.cargo/registry/src/index.crates.io-*missingCargo.tomlcan be deleted when the matching.cratearchive exists. -
Toolchain is pinned by
rust-toolchain.toml(currently1.92.0). EnsurePATHincludes~/.cargo/bin. -
macOS path aliases:
/var→/private/var,/etc→/private/etc. Validate resolved protected chains; accept public spellings. -
Dotenv provider must be pinned to the vault file:
dotenv:///var/db/.../.env— never baredotenv(cwd-relative). -
Audit must fail closed. Do not ignore
append_eventerrors. -
Public client elevates with:
- lifecycle:
sudo -n /usr/local/libexec/sudo-secretspec __broker ... - install/rollback: interactive
sudo(Touch ID) - doctor: prefer
sudo -nlibexec elevation
Always invoke
/usr/bin/sudoby absolute path. A baresudoresolves through the caller'sPATH, and a planted one satisfies every broker call with forged values and no audit event — the whole boundary, defeated without root.The NOPASSWD grant is per-subcommand (
__broker *,doctor), neversudo-secretspec *. The client and the broker are the same binary, so a blanket grant would exposeinstallandrollbackwith no Touch ID.mainenforces the same restriction internally, because sudoers argument matching is easy to get subtly wrong. - lifecycle:
-
Adopt-existing must not chown/chmod the vault. Only verify metadata.
-
Install UX goal: short forms (
install,install --adopt-existing) with auto-detection/prompts; long flags are overrides only. -
Adoption defaults on provenance, not on convenience. A vault named by the installed root-owned config is adopted with no flag — the host vouching for the store it already serves from, so a reinstall over it is an upgrade. A vault found only by path scan still requires
--adopt-existing, because one of the scan candidates is the retired wrapper's store. The rule isinstall::adopts_without_flag; keep it there rather than re-deciding it inline inmain.rs.
The companion is macOS-only, so it is excluded from the workspace-wide test
invocations in .github/workflows/test.yml and devenv.nix (they also run on
Linux and Windows, where libc/dscl//private/var do not exist). Its suite
runs in .github/workflows/sudo-release.yml on macOS. Test it locally with
-p, never by relying on cargo test --all.
# focused tests
cargo test -p sudo-secretspec-cli
# release binary
cargo build -p sudo-secretspec-cli --release
cp target/release/sudo-secretspec ~/bin/sudo-secretspec
# live smoke after install
sudo -n /usr/local/libexec/sudo-secretspec doctor
sudo-secretspec check --reason "smoke"- Commit directly to
sudo-mainonfrdminc/sudo-secretspec. Do not create branches or open PRs for downstream work. - Never open upstream issues/PRs without explicit permission.
- Keep
CHANGELOG.mdUnreleased entries user-facing. - After privileged install, verify paths under
/usr/localand vault ownership without reading secret values.
After adopt-existing install of the stayturgid vault, doctor may report:
LEGACY_VAULT_CLUTTER/ previouslyUNEXPECTED_RUNTIME_ENTRYfor.local/.ansibleunder the vault (non-secret tool state; safe to clean later). This is advisory: it prints under a passingdoctor: OKand does not fail the check. Advisory codes must never clearReport.ok— agents treat a drift failure as a hard stop, so a permanent advisory would wedge every automated caller indefinitely.CLIENT_DUPLICATEif a byte-identicalsudo-secretspecsits in a second root-owned search directory. Also advisory. The failing sibling isCLIENT_SHADOWED, which means a different binary would run instead of the installed client — that one is a hard stop, not residual noise. This host is expected to have exactly one copy, at/usr/local/bin/sudo-secretspec; the keg'slibexecbootstrap is not on any search path and is not a shadow.UPGRADE_AVAILABLEwhen the keg'slibexecbootstrap reports a different version than the installed boundary — a build staged bybrewbut never installed. Also advisory: an upgrade the operator has not run yet is not a reason to refuse credential operations. The version it compares against is theversionkey the installer stamps into/usr/local/etc/sudo-secretspec.toml; a boundary installed before that key existed reports nothing rather than guessing. Note the staged version is probed by the unprivileged client and passed in — the root broker must not execute anything out of an operator-writable Homebrew prefix.- audit ledger ownership should be
_secretspec:staff(fixed in current tree by chown-after-open when broker is root). The pre-open metadata check deliberately does not assert ownership; the post-open check does, so a ledger left root-owned by an older install is repaired rather than fatal.
The sudoers policy generated by install changed from a blanket
sudo-secretspec * grant to per-subcommand rules. Hosts installed before that
change still carry the old wildcard on disk — the narrowing only takes effect
after another sudo-secretspec install (since 0.19.1-sudo.17 an upgrade
adopts the installed config's vault with no flag). Until then the
in-binary guard in main is the only thing refusing install/rollback
through the libexec path.
sudo-secretspec/AI-GUIDANCE.mdskills/sudo-secretspec/SKILL.mdpackaging/README.md- Local reusable Rust agent notes:
~/src/agent-guidance/rust/AGENTS.md
SKILL.md deliberately restates policy from AI-GUIDANCE.md rather than
linking to it: the skill is installed on machines that may not have this repo,
so it must stand alone. When the policy contract changes, update both.