sudo-secretspec — a single Rust binary acting as:
- Public CLI (
/usr/local/bin/sudo-secretspec) — unprivileged operator - Privileged Broker (
/usr/local/libexec/sudo-secretspec) — root via NOPASSWD sudoers
The same binary dispatches via hidden __broker subcommand.
| Asset | Threat | Mitigation |
|---|---|---|
| Vault secret values | Leak via audit/logs/CLI | Value-free audit: only SHA-256(reason) stored; no argv/env values |
| Vault integrity | Mutation by compromised operator | Mutation backed by .rollback.<uuid> + atomic commit/restore |
| Control plane hijack | Alternate manifest/provider/binary | Fixed paths in broker; execute() purges ambient SECRETSPEC_* env |
| Audit tampering | Ledger rewrite / deletion | Hash-chained events + atomic head; verify() integrity check |
| Privilege escalation | Broker runs as root | require_root() + fixed allowlist; no shell; execvpe only |
| Vault access by unauthorized UID | File perm/ACL bypass | require_boundary() validates ownership/mode/symlinks/ACL |
Entry: run() → dispatch() → execute()
Key checks:
-
require_root()useslibc::geteuid() == 0 -
require_boundary()validates:- Vault exists, not symlink, mode 0700, owned by service user
- Manifest/dotenv exist, not symlinks, mode 0600, correct owner
- Binary/audit helper exist, not symlinks, root:wheel:755
- Allowlist: only
source-*+audit-verify+source-template-check - Empty reason rejected before any operation
- Audit attempt before ANY secret operation (fail-closed)
-
Mutationstruct:- Backs up manifest + dotenv to
.rollback.<uuid> restore()returnsbool(not void!)commit()removes backups- On failure:
restore()called; if fails →unknownterminal
- Backs up manifest + dotenv to
-
execute():- Purges
SECRETSPEC_PROVIDER/FILE/PROFILE/SCOPEenv - Pins dotenv provider:
dotenv:///var/db/.../.env - Uses
secretspec::Secretslibrary directly (no subprocess) ExportFormat::Json→ stdout → parsed → injected viaexecvpe
- Purges
- Terminal audit also fail-closed (maps failure to exit 125 if unknown)
-
AppendEventRequest→AuditEventwith SHA-256(reason) - Hash chain:
event_hash = H(previous_hash || event_without_hash) - Head table:
singleton=1row with(sequence, event_hash) -
verify()checks chain + head +PRAGMA integrity_check -
chownledger to vault owner when broker is root - No secret values in any field (verify
reason_sha256only) -
open_connection()validates dir metadata before opening DB
-
Report.okignoresLEGACY_VAULT_CLUTTER/PENDING_ROLLBACK - Vault allowlist includes
.local,.ansible,.cache→LEGACY_VAULT_CLUTTER(advisory) - Source manifest hash verification vs
MANIFEST.sha256 - Sudoers syntax check via
visudo -c - Ancestor chain validation (root-owned, non-writable, no ACLs)
- Vault realpath validation (
/private/var/db/...)
-
--adopt-existing: never chown/chmod vault — only verify metadata - Fresh install: creates service user/group (400-499), vault 0700
- Dry-run still requires root (metadata access)
- Source root derived from binary path (
libexec/../..)
- Snapshot path validated under
/usr/local/libexec/sudo-secretspec-rollback-* -
MANIFEST.sha256verified before any restore - Restores only installed artifacts (bin/libexec/share/config/sudoers)
- Never touches vault
-
visudo -cafter restore
-
run→execvpe(no shell) -
--reasonrequired for all lifecycle ops -
--reasonSHA-256 hashed before crossing broker boundary - Install/rollback/doctor elevate via
sudo(interactive) not-n
-
deny_unknown_fieldson TOML - Path traversal rejection (
..in managed paths) - Service identity pattern:
^_[a-z_][a-z0-9_]{0,30}$/^_?[a-z_][a-z0-9_]{0,30}$
-
rusqlite = { version = "0.31" }nobundled(system SQLite via Homebrew) -
secretspec = { path = "../secretspec", version = "0.19.1-sudo.9", default-features = false, features = ["manifest-edit"] }
- Installs
secretspec+sudo-secretspec-clifrom source - No
sudo, no user/group creation, no sudoers edit, no/var/dbmutation - Caveat instructs explicit
sudo-secretspec install
- Tag validation:
v0.19.1-sudo.9 - SHA-256 of tarball → formula rewrite
- Homebrew tap sync +
brew test+ version readback - Readback: repo/tag/release/fork lineage verified
-
/var→/private/varalias handled (canonicalize + validate/privatechain) -
rusqlitenobundled— uses Homebrew SQLite (PKG_CONFIG_PATHetc.) - Touch ID /
sudofor install/rollback (interactive),-nfor broker - System SQLite 3.53.4+ available via Homebrew
- Service user
_namepattern (hidden, no shell, uid 400-499)
cargo test -p sudo-secretspec-cli
# 152 tests: lib(94), audit(19), install_rollback(16), drift(11), cli(6), config(6)| Path | Why |
|---|---|
sudo-secretspec-cli/src/broker.rs |
Core privilege boundary |
sudo-secretspec-cli/src/audit.rs |
Value-free audit ledger |
sudo-secretspec-cli/src/drift.rs |
Non-repairing drift checker |
sudo-secretspec-cli/src/install.rs |
Adopt-existing + fresh install |
sudo-secretspec-cli/src/rollback.rs |
Artifact restore |
sudo-secretspec-cli/src/audit.rs |
Fail-closed SQLite audit |
sudo-secretspec-cli/src/config.rs |
Typed TOML validation |
sudo-secretspec-cli/src/main.rs |
Public CLI + elevation |
sudo-secretspec-cli/src/rollback.rs |
Artifact restore |
sudo-secretspec-cli/Cargo.toml |
Deps (no bundled SQLite) |
packaging/homebrew/sudo-secretspec.rb |
Formula (no privileged install) |
packaging/release.py |
Release automation |
Post findings as PR comments on #1
Tag:
[SECURITY]— exploitable / invariant violation[ADVISORY]— hardening / defense-in-depth[OK]— invariant holds
Reference: FORK-AI.md (Rust build caveats), AI-GUIDANCE.md (AI rules), CLAUDE.md (fork notes).