feat(profiles)!: require explicit config; stop auto-discovering .clawscan.yml#20
Open
jesse-merhi wants to merge 5 commits into
Open
feat(profiles)!: require explicit config; stop auto-discovering .clawscan.yml#20jesse-merhi wants to merge 5 commits into
jesse-merhi wants to merge 5 commits into
Conversation
…scan.yml Auto-discovery of .clawscan.yml via upward directory walk is now opt-in (--discover-config). Discovered-but-ignored configs produce a stderr notice, and run artifacts record their config source. Groundwork for config-defined scanners: discovered config must never gain the power to execute commands chosen by a scan target.
… bundled skill Review findings from ticket-1 code review: the migration hint was skipped exactly when the requested profile lived in an unloaded discovered config, and skills/clawscan-cli/SKILL.md still documented automatic discovery.
An embedded profile run recorded configSource: flags-only (or an unrelated discovered config path). selected.source is authoritative: built-in profiles record built-in provenance regardless of what other config was loaded alongside them.
Discovery without a profile recorded the discovered file as the run's configSource while applying none of its settings (sandbox mode, image, env). Reject the combination with guidance instead of silently claiming provenance.
Flags-only runs no longer walk parent directories looking for .clawscan.yml, and the discovered-but-not-loaded stderr notice is gone: if we deliberately do not trust a config, we do not spend I/O finding it or mention it. Discovery lives solely in discoverConfig, reached only via --discover-config. clawscan profiles now lists built-ins only for the same reason. Help and docs explain the security rationale: a config can define scanners that run arbitrary commands, so ClawScan never loads config it was not explicitly told to trust.
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: feat(profiles)!: require explicit config; stop auto-discovering .clawscan.yml This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
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.
New behavior
ClawScan never loads — or even looks for —
.clawscan.ymlfiles it was not explicitly told to trust. Operators name their config (--config <path>) or opt in to upward-walk discovery (--discover-config, which requires--profile); otherwise flags-only and built-in-profile runs do no config discovery I/O at all and print nothing about nearby config files. Run artifacts record where the effective config came from (configSource: a path,built-in, orflags-only)..clawscan.ymlignores it silently: no upward walk, no stderr notice,configSource: "flags-only".--profile <name>where the profile lives in an undiscovered config fails withUnknown profile: <name> (available: clawhub, clawhub-aig)— no filesystem traversal to hint at nearby files.--discover-configrestores the old upward-walk behavior for a single run. It is mutually exclusive with--configand requires--profile(discovery without a profile would record the file asconfigSourcewhile applying none of it).--profile clawhub) recordconfigSource: built-ineven when--discover-configfinds an unrelated project config.clawscan profileslists built-in profiles only, each markedSource: built-in.Breaking change for anyone relying on silent repo-root
.clawscan.ymlpickup. Migration: pass--config <path>or--profile <name> --discover-config.Why
Groundwork for user-defined (command-backed) scanners: config discovered from a scan target must never gain the power to execute commands an attacker chose by dropping a
.clawscan.ymlinto a skill. If ClawScan deliberately does not trust a config, it also does not spend I/O finding it or mention it — a notice naming the file would itself be attacker-influenced output. Precedent: git, VS Code Workspace Trust, direnv, Trivy, and Grype all refuse discovered executable config; ESLint's implicit config trust is the cautionary tale.CLI proof
Before — PR base silently auto-discovers
.clawscan.ymland resolves theteam-policyprofile from it with no notice:What this shows:
clawscan ./README.md --profile team-policy --jsonon the PR base finds the config via upward walk and runs the profile without any indication a file was trusted.State: demo directory containing
README.mdand a.clawscan.ymldefiningteam-policy; binary built from the PR base (main@ a37f49f); 114×31 terminal window capture.Now — the same directory: the local config is ignored with no notice, and the run records
configSource: "flags-only":What this shows:
ls -ashows.clawscan.ymlsitting next to the target;clawscan ./README.md --scanner clawscan-static --jsonproduces JSON with"configSource": "flags-only"and prints nothing to stderr — untrusted config is neither loaded nor mentioned.State: same demo directory; binary built from this PR's head; 114×31 terminal window capture.
Now — asking for a profile that lives in the undiscovered config fails plainly, and
--discover-configopts back in:What this shows:
clawscan ./README.md --profile team-policyfails withUnknown profile: team-policy (available: clawhub, clawhub-aig)— no traversal, no mention of the nearby file. The same command with--discover-config --jsonloads the config and runs with"profile": "team-policy".State: same demo directory and PR-head binary; 114×31 terminal window capture.
How to verify
.clawscan.ymldefining a profile, runclawscan <file> --scanner clawscan-static --json 2>err.txt— expect normal JSON withconfigSource: "flags-only"and an emptyerr.txt.clawscan <file> --profile <that-profile>— expectUnknown profile: <that-profile> (available: clawhub, clawhub-aig)with no mention of the local file.--discover-config— expect the profile to resolve and the artifact'sconfigSourceto record the loaded path.clawscan <file> --profile clawhub— expectconfigSource: "built-in"in the artifact.clawscan profilesin that directory — expect built-ins only,Source: built-in.Checks
TestResolveArgsDefaultRunDoesNotAutoDiscover_*),--discover-configrestoring the walk, its--profilerequirement, mutual exclusion with--config, the plain unknown-profile error (TestResolveArgsUnknownProfileDoesNotDiscoverConfig), andconfigSourceprovenance (loaded path /built-in/flags-only).go test -count=1 ./...— passes except two pre-existing macOS/var-symlink failures intarget_test.gounrelated to this change.go vet ./...,gofmt,make docs-site— clean.