feat(audit): PATH shadowing + system package audit - #37
Merged
Conversation
A verified install means nothing if a different binary earlier on PATH is what actually executes. doctor --audit now resolves every managed tool on PATH (exec.LookPath semantics, PATHEXT-aware on windows, symlink-resolved, case-insensitive compare on windows) and reports: - shadowed-binary (critical): PATH resolves the tool name to a binary other than the one scuta verified - bin-dir-not-in-path (warning): scuta's bin directory is not on PATH, so installed tools are not runnable by name Report schema stays v1: new Tool fields effective_path and shadowed are additive and omitempty. PathEnv injects PATH/PATHEXT/GOOS/Stat/EvalSymlinks so both platforms are tested from any host. Closes #32
…apter doctor --audit --system extends the audit beyond scuta-managed tools: one adapter per package manager, each answering where a package came from and whether its integrity can be verified. Adapters report only what the manager can prove — no integrity data means IntegrityNotVerifiable, never a silent pass. Undetected managers are still listed so the report is explicit about what was and was not checked. First adapter: go install. Reads toolchain-embedded build metadata (debug/buildinfo, no commands executed) and reports module origin, version, and sum, flagging source-checkout builds (unpinned-build, info), missing module sums (no-integrity-data, info), and dirty VCS working trees (dirty-build, warning). No criticals: --system never gates CI yet. Report schema stays v1: system section and summary count are additive omitempty fields. Closes #33
Extends --system audit with three new manager adapters: - brew: reads Cellar INSTALL_RECEIPT.json directly (no shelling out). Flags third-party taps (third-party-source) and source builds (unpinned-build). Homebrew keeps no per-file manifest, so integrity is honestly reported as not-verifiable. - mise: inventories $MISE_DATA_DIR/installs, skipping symlinked partial-version aliases. Checksums live in per-project mise.lock, so integrity is not-verifiable machine-wide. - dpkg: counts packages from /var/lib/dpkg/status (summarized, not listed), runs a single 'dpkg --verify' pass. Non-conffile checksum mismatches are reported as binary-drift at critical severity: these are the first --system findings that can gate CI, deliberately, since a modified package binary is exactly what this audit exists to catch. Conffile changes are config-drift (info). Findings capped at 25 with an overflow summary. New finding codes: third-party-source, config-drift, inventory-summarized. Report schema stays v1 (all fields omitempty). Closes #34
lookPath joined candidate paths with filepath.Join, which uses the host separator. On a Windows runner that produced backslash paths, so the injected-GOOS test environments (keyed with forward slashes) never matched and all five cross-platform shadowing tests failed there. Join with a forward slash instead, trimming trailing separators per the injected GOOS. Every supported platform accepts forward slashes, so the real check is unaffected; lookups now depend only on the injected GOOS, not the machine running them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phases A, B, and C of the system-audit roadmap (#32, #33, #34). Together they answer: is the binary that executes the one that was verified, and what else is installed on this machine that nobody is auditing?
PATH shadowing detection (#32)
A verified install means nothing if a different binary earlier on PATH is what actually executes.
doctor --auditnow resolves every managed tool on PATH and reports:shadowed-binary(critical): PATH resolves the tool name to a binary other than the one scuta verified. Message names both paths.bin-dir-not-in-path(warning, machine-level): scuta's bin directory is not on PATH at all, so installed tools are not runnable by name.system package audit framework + go adapter (#33)
Opt-in
doctor --audit --systemextends the audit beyond scuta-managed tools to system package managers, via a smallManagerinterface (lib/managers). Undetected managers are listed explicitly rather than omitted.debug/buildinfofrom each binary in the go bin dir (zero exec). Flags source-checkout builds (unpinned-build), missing module sums (no-integrity-data), and dirty VCS trees (dirty-build).brew, mise, and dpkg adapters (#34)
INSTALL_RECEIPT.jsondirectly, never shells out. Flags third-party taps (third-party-source) and source builds (unpinned-build). Homebrew keeps no per-file manifest, so integrity is honestly reported as not-verifiable.$MISE_DATA_DIR/installs, skipping symlinked partial-version aliases. Checksums live in per-projectmise.lock, so integrity is not-verifiable machine-wide./var/lib/dpkg/status(summarized, not listed), runs a singledpkg --verifypass. Non-conffile checksum mismatches arebinary-drift(critical): the first--systemfindings that can gate CI, deliberately, since a modified package binary is exactly what this audit exists to catch. Conffile changes areconfig-drift(info). Findings capped at 25 with an overflow summary.Design
PathEnvinjects PATH/PATHEXT/GOOS/Stat/EvalSymlinks, so windows and linux semantics are both tested from any host. Lookup mirrorsexec.LookPath: first match wins, exec-bit check on POSIX, PATHEXT on windows (bare names don't match), symlink-resolved and case-insensitive-on-windows comparison--verifycall (exit code tolerated as data, not failure); all OS touchpoints are injectable for testsrecorded,not-verifiable,unknown. Managers with no integrity data are never passed silentlyTool.effective_path/shadowed,Report.system,Summary.system_packages) are additiveomitemptyTesting
lib/auditandlib/managers: shadowing semantics on both platforms, gobin buildinfo findings, brew receipt parsing (verified against a real Cellar receipt), mise symlink skipping, dpkg verify parsing (drift vs conffile severity, cap + overflow)go test -race ./...green (29 pkgs), darwin + windows/amd64 builds, golangci-lint 0 issues, all 8 CI checks green--audit --systemrun (go/brew/mise sections, dpkg not-detected on macOS) and an end-to-end PATH shadowing exercise (critical fires with exit 1; symlink to verified binary correctly not flagged)Also updates README (commands, Security section, roadmap) and docs/SECURITY.md.
Closes #32
Closes #33
Closes #34