config: executable and key-bearing settings must never live in the tracked layer #119
Labels
No labels
already-shipped
bug
documentation
duplicate
enhancement
external-review
good first issue
help wanted
in progress
invalid
needs-decision
proposal
question
security
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: crenshawdev/cadence-archived#119
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Three config keys are executable or key-bearing, and all three live in
.planning/config.json, which is tracked and travels with a clone. Clone a repo, run Cadence, and that repo's config decides what runs on your machine and where your provider key is read from.workflow.test_commandcad-executor-contract:28,34,workflows/task.md:46,workflows/coverage.md:34,63,references/execute-parallel.md:28)workflow.lint_commandreview.key_fileThe
key_filearm is the nastiest of the three.review-provider.mjs providersEnvPath(override)takes the value straight through, expands~, and reads it, and--key-file <review.key_file, only if set>is passed from repo config at four call sites (references/review-triggers.md:197,workflows/decision-review.md:82,references/consult.md:43,workflows/config-review.md:27). A cloned repo can point it at a key file committed inside that same repo, and your review payload - your plan text, your diffs, your source - is then sent to the attacker's provider account. Silent, and it looks like a working review.Bounded honestly: that arm needs a cross-model provider configured, and the default
review.reviewersis["claude-subagent"], so nothing leaves the machine on a default install. An env-set key also still beats the file. It fires on the configured path, which is the path a real user is on.Decision: these never reach the tracked layer at all
Not a confirm-on-first-use prompt. If a value is an injection risk, the fix is that it is never committed in the first place, rather than asking the user to approve it after it has already arrived. A prompt puts a security decision in front of someone who has no way to judge it, on every fresh clone, which is exactly the prompt people learn to click through.
These keys become global-layer-only. A repo layer that sets one is IGNORED, with a warning naming the key and the file it came from.
The mechanism already exists and is inert
config.schema.jsonalready carries asrcfield. Today it has exactly one value across 34 keys ("src": "repo"), the other keys have none, and NO code reads it -route.mjs:48only mentions it in a comment about the retired waiver path. It is documentation that documents nothing.Make it load-bearing. Add
"src": "global"for the three keys above, have the merge honor it, and self-verify can then check in both directions the way it already does for routing cells. That reuses existing vocabulary instead of inventing a second config home.Constraints
mergeLayersalready haswarnings[]and callers already surface it.cmdDetectCommandsis unaffected.planning.mjs:2001-2065emits fixed literals and never interpolates repo content, so the detected arm keeps working with no config key set at all. That is the migration path for the common case: most users never needed the key./cad-configwrites these to the global layer. The write face stays in one place; only its destination changes for these keys.Consistency
Same root fact as #114: a tracked
.planning/config.jsonis attacker-controlled input, not the user's own settings. Both fixes should land together and be tested against the same hostile-repo fixture.Decided: option 1, confirm on first sight per repo
workflow.test_commandandworkflow.lint_commandstay in.planning/config.json. On the first run in a given repo, show the command and ask once, then remember the answer keyed by repo path so it is a single prompt on a fresh clone and silent thereafter.Rejected: option 2 (per-machine trust record) splits config across two homes and complicates
/cad-configfor a case the prompt already covers. Option 3 (document and accept) puts the burden on the user least likely to read it.Constraints the implementation has to respect
.planning/. Storing it in the tracked tree would let a cloned repo pre-approve its own command, which is the whole defect. It belongs beside the global config, keyed by repo path.git.auto_closethere is no user to ask, so an unapproved command is a refusal with a clear reason, never a silent yes. Same posture as #116's halt gate: absence of an answer is not an answer.cmdDetectCommandsstays as it is.planning.mjs:2001-2065emits fixed literals and never interpolates repo content, so a detected command is already trustworthy. Only a command that came out of a config layer needs the prompt.Consistent with #114: both treat a tracked
.planning/config.jsonas attacker-controlled input rather than as the user's own settings.workflow.test_command: a cloned repo chooses what runs on your machineto config: executable and key-bearing settings must never live in the tracked layerSuperseding the previous comment. Option 1 (confirm on first sight) is withdrawn. The right fix is that these values never reach a tracked file at all - approving an injection risk after it has arrived is the wrong shape, and it trains people to click through a security prompt on every fresh clone. Scope also grew:
review.key_filebelongs in this class and is the worst of the three, because it can redirect your review payload to an attacker's provider account. Issue body rewritten.Fixed in v3.2.0 (PR #124). CFG-02, phase 1. workflow.test_command, workflow.lint_command and review.key_file are global-layer only now. A repo layer that sets one is ignored and warns, naming the key and the file it came from, and a repo layer holding a non-object ancestor is stripped and warned too. The src field in config.schema.json carries the scope and self-verify checks it in both directions.
Verified through the phase UAT walk. The milestone audit traced 12/12 requirements with 0 broken and 36/36 acceptance criteria covered.