fix(guardrails): resolve source-control enablement across user-global scope - #928
Conversation
|
Claude finished @kyle-sexton's task in 2m 18s —— View job PR Review
SummaryClean, well-scoped fix. The three-scope precedence model in
|
|
Claude finished @kyle-sexton's task in 2m 55s —— View job Security Review
Scope: FindingsOverall: no significant security vulnerabilities found in this PR. The core change adds a user-global scope (
SUGGESTION · Confidence: low ·
jq -r '...' "$file" 2>/dev/null | head -1If No further findings. The |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1dfdb909c3
ℹ️ 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".
1dfdb90 to
3504dc5
Compare
|
Claude finished @kyle-sexton's task in 3m 0s —— View job
PR Review —
|
|
Claude finished @kyle-sexton's task in 1m 44s —— View job Security Review
Scope: FindingsNo new security vulnerabilities found in this PR. The core change introduces a
SUGGESTION · Confidence: low · The jq -r '...' "$file" 2>/dev/null | head -1If Note: the prior automated security review on this PR already flagged this identical issue. No further findings. The |
3504dc5 to
ab813b7
Compare
|
Claude finished @kyle-sexton's task in 1m 48s —— View job Security Review
Scope: FindingsNo new security vulnerabilities found in this PR. The core change extends
SUGGESTION · Confidence: low · The jq -r '...' "$file" 2>/dev/null | head -1If Note: both prior automated security reviews on this PR flagged this same issue. No new surface has been added in this revision. No further findings. The |
|
Claude finished @kyle-sexton's task in 2m 28s —— View job
PR Review —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab813b7d65
ℹ️ 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".
ab813b7 to
8640d60
Compare
|
Claude finished @kyle-sexton's task in 2m 54s —— View job
PR Review —
|
|
Claude finished @kyle-sexton's task in 1m 40s —— View job Security Review
Scope: FindingsNo new security vulnerabilities introduced by this PR. The core change adds
SUGGESTION · Confidence: low · jq -r '...' "$file" 2>/dev/null | head -1The Note: this identical issue was flagged by both prior automated security reviews on this PR. No new surface has been added in this revision. No further findings. The |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8640d602fa
ℹ️ 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".
|
Automated babysit-lane classification (safe tier — reply only; not resolving threads/merging). Re: the codex P2 finding ("Honor the project-key prerequisite for local overrides").
claude[bot] Security-Review's one SUGGESTION (jq |
8640d60 to
78b848a
Compare
|
Claude finished @kyle-sexton's task in 2m 28s —— View job Security Review
Scope: FindingsNo new security vulnerabilities introduced by this PR. The refactored
SUGGESTION · Confidence: low · Both jq -r '...' "$file" 2>/dev/null # sc_keys
jq -r --arg k "$key" '...' "$file" 2>/dev/null | head -1 # sc_key_valueIf This issue was flagged by all three prior automated security reviews on this PR. No new surface has been added in the current revision — the finding applies equally to the new No further findings. The |
|
Claude finished @kyle-sexton's task in 2m 57s —— View job
PR Review —
|
| key | uval | bval | lval | effective | result |
|---|---|---|---|---|---|
source-control@new |
"" |
"false" |
"" |
"false" |
skip |
source-control@old |
"true" |
"" |
"" |
"true" |
return 0 ✓ |
Advisory fires because @old is enabled — correct, the skill is available through the old marketplace key.
The new test case at lines 157–166 covers exactly this scenario.
Finding 1 — README still describes old two-scope behavior (minor, carry-over)
plugins/guardrails/README.md line 19 (table row):
"Only fires when the consuming project's own
.claude/settings.jsonenables thesource-controlplugin"
Lines 125–132 (prose):
"
flag-commit-pr-skill-bypassreadsenabledPluginsfrom the consuming project's own.claude/settings.json"
Both still describe the old single-scope behavior. The hook now resolves per-exact-key across user-global (CLAUDE_CONFIG_DIR / ~/.claude/), project, and local scopes — none of that is reflected here.
Finding 2 — Telemetry test not hermetic on HOME or CLAUDE_CONFIG_DIR (minor, carry-over)
flag-commit-pr-skill-bypass.test.sh lines 180–181:
env HOOK_TELEMETRY_SINK="$SINK" CLAUDE_PROJECT_DIR="$ENABLED_PROJECT" \
bash "$HOOK" <<<"$(command_json 'gh pr create --title x --body y')" >/dev/null 2>&1 || trueThis is the only test block that bypasses run_hook and calls env directly — so it misses both hermetic guards that run_hook now injects (HOME="$HERMETIC_HOME" and -u CLAUDE_CONFIG_DIR). Now that CLAUDE_CONFIG_DIR is a live code path, a CI runner with that variable set could redirect the probe to an unrelated config. ENABLED_PROJECT has base_val=true so no functional failure today, but the asymmetry is more notable than before.
Fix: update line 180 to match run_hook's pattern:
env -u CLAUDE_CONFIG_DIR HOOK_TELEMETRY_SINK="$SINK" HOME="$HERMETIC_HOME" CLAUDE_PROJECT_DIR="$ENABLED_PROJECT" \
bash "$HOOK" <<<"$(command_json 'gh pr create --title x --body y')" >/dev/null 2>&1 || trueWhat's correct in this commit
- Per-exact-key resolution —
sc_keys()+sc_key_value()split the problem cleanly: enumerate names first, then resolve each key's value per-scope. The union viasort -uover all three scopes is correct (a key only in local still gets evaluated — correctly resolving toeffective=""sincebvalis empty and the local gate fails, so user-globaluvalstays or falls back to empty → silent). sc_key_value()parameterisation —--arg k "$key"is the right tool: the key is passed as a jq variable, never interpolated into the filter expression. No injection surface even if a key name were adversarially constructed.- Precedence chain per key —
uval → bval overrides → lval gated on bvalremains correct and the[[ -n "$bval" && -n "$lval" ]]gate for local correctly honors the documented platform constraint from#27247. - Multi-key test case (lines 157–166) — covers the exact P2 failure mode and verifies the
@old=trueresult is not suppressed by@new=false. - All prior fixes confirmed present —
CLAUDE_CONFIG_DIRresolution (lines 133–134),run_hook's-u CLAUDE_CONFIG_DIR+HOME="$HERMETIC_HOME"guards (line 77), theCLAUDE_CONFIG_DIRtest case (lines 152–155), and the local-prerequisite gate — all intact.
Verdict: All three Codex P2 findings are resolved and covered by tests. Findings 1 and 2 above are clean-up items — the README is stale, and the telemetry test block is the sole non-hermetic outlier in the suite. Neither affects the correctness of the fix. Ready to merge once the carry-overs are addressed or deferred consciously.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78b848a543
ℹ️ 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".
… scope The flag-commit-pr-skill-bypass advisory's source_control_enabled probe read only the consuming project's .claude/settings.json (+ local override), so a plugin enabled solely at user-global scope (~/.claude/settings.json) -- a common install -- false-negatived and the gh pr create advisory never fired. Resolve effective enablement across user-global, project, and local scopes in Claude Code's precedence order (user-global base, project overrides, local overrides), matching how the platform merges enabledPlugins. Make the contract test hermetic w.r.t. HOME and add four user-global cases. Refs #916 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78b848a to
65c9edb
Compare
|
Claude finished @kyle-sexton's task in 2m 45s —— View job
PR Review —
|
| uval | bval | lval | effective (before fix) | effective (after fix) |
|---|---|---|---|---|
"true" |
"false" |
"" |
"true" (wrong) |
"false" ✓ |
"false" |
"true" |
"" |
"false" (wrong) |
"true" ✓ |
The inline comment on lines 120–121 is precise and explains the why correctly.
sc_keys() is also correct (line 112): keys[] enumerates all keys in enabledPlugins regardless of value, so a source-control@…: false key is still included in the union — and then resolved correctly per-scope in source_control_enabled().
Finding 1 — README still describes old single-scope behavior (minor, carry-over)
plugins/guardrails/README.md line 19 (table row):
"Only fires when the consuming project's own
.claude/settings.jsonenables thesource-controlplugin — silent otherwise."
Lines 125–132 (prose):
"
flag-commit-pr-skill-bypassreadsenabledPluginsfrom the consuming project's own.claude/settings.json"
Both still describe the old project-only scope. The hook now resolves per-exact-key across user-global (CLAUDE_CONFIG_DIR / ~/.claude/), project, and local scopes. The CHANGELOG and hook header comment are accurate; only the user-facing README is stale.
Finding 2 — Telemetry test not hermetic on HOME or CLAUDE_CONFIG_DIR (minor, carry-over)
flag-commit-pr-skill-bypass.test.sh lines 180–181:
env HOOK_TELEMETRY_SINK="$SINK" CLAUDE_PROJECT_DIR="$ENABLED_PROJECT" \
bash "$HOOK" <<<"$(command_json 'gh pr create --title x --body y')" >/dev/null 2>&1 || trueStill the only block that calls env directly instead of going through run_hook, missing both hermetic guards (HOME="$HERMETIC_HOME" and -u CLAUDE_CONFIG_DIR). ENABLED_PROJECT has base_val=true, so neither a CI runner's user-global false nor a leaked CLAUDE_CONFIG_DIR would cause a functional failure today — but the asymmetry with the rest of the suite is notable now that CLAUDE_CONFIG_DIR is a live code path.
Fix: update line 180 to match the run_hook pattern:
env -u CLAUDE_CONFIG_DIR HOOK_TELEMETRY_SINK="$SINK" HOME="$HERMETIC_HOME" CLAUDE_PROJECT_DIR="$ENABLED_PROJECT" \
bash "$HOOK" <<<"$(command_json 'gh pr create --title x --body y')" >/dev/null 2>&1 || trueWhat's correct in this commit
if has($k) then (.[$k] | tostring) else empty end— correct pattern for boolean-preserving extraction;//alternative would swallowfalse.- Inline comment (lines 120–121) — accurately explains why
has()is preferred over//. sc_keys()enumerates all keys regardless of value — correct; the per-key resolution insource_control_enabled()handlesfalsevalues explicitly.- Precedence chain (
uval → bval → lval gated on bval) — correct and unchanged from prior review; works correctly now that"false"is non-empty. - All prior fixes confirmed present —
CLAUDE_CONFIG_DIRfallback,run_hook's-u CLAUDE_CONFIG_DIR+HERMETIC_HOMEguards, local-key prerequisite gate, and per-exact-key resolution all intact.
Verdict: All four Codex P2 findings are now resolved and covered by tests. Findings 1 and 2 above are clean-up items that don't affect the correctness of the reported fix — the README is stale and the telemetry block is the sole non-hermetic outlier in an otherwise hermetic test suite. Happy to implement either if useful.
|
Claude finished @kyle-sexton's task in 2m 9s —— View job
Security Review —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 65c9edbfa7
ℹ️ 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".
The flag-commit-pr-skill-bypass section still described the pre-fix project-only probe (reads the project .claude/settings.json, quiet when absent). The 0.9.6 change resolves enabledPlugins across user-global, project, and local scopes, so the advisory now fires for a plugin enabled only at user-global even with no project settings. Update the bullet to match: user-global base -> project override -> local override (local only for a key the project declares), per-exact-key resolution, any enabled key fires. Refs #916 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 54s —— View job
PR Review —
|
|
Claude finished @kyle-sexton's task in 3m 6s —— View job Security Review
Scope: No new security vulnerabilities introduced by this PR. The refactored
SUGGESTION · Confidence: low · jq -r '...' "$file" 2>/dev/null # sc_keys
jq -r --arg k "$key" '...' "$file" 2>/dev/null | head -1 # sc_key_valueNeither invocation uses a This finding was raised in all five prior automated security reviews on this PR. The current revision introduces No further findings. The |
…ath roots (#932) Two-lane posture fixes (audit #912): externalize hardcoded assumptions in two guardrails detectors. ## W3 - hook-manager bypass detection `block-no-verify` matched only `lefthook*` env-var disables, silently missing `HUSKY=0` and others. Now resolves a configurable prefix set (`block_no_verify_hook_manager_prefixes` userConfig; default `lefthook, husky, pre_commit, simple_git_hooks`). Consumer values are reduced to identifier characters before splicing into the regex alternation, so no metacharacter injection. ## W4 - machine-path checkout roots `hardcoded-path-check`'s drive-letter-anchored checkout-parent pattern matched only `X:\repos\...`, missing `C:\Projects\...` (this very repo) and `C:\Dev\...`. Broadened to also match `Projects` and `Dev` (both capitalizations). A consumer's own checkout root remains caught by the driver's project-root literal scan. ## Verification - `block-no-verify.test.sh` 83/0 (adds husky/pre_commit/simple_git_hooks + configurability + sanitization cases) - `hardcoded-path-check.test.sh` 38/0 - shellcheck clean; guardrails `0.9.5` -> `0.9.6` + CHANGELOG ## Related - #912 -- umbrella (not closed here) - Shares the guardrails manifest with #928 (B1); final merges serialize + re-bump per the playbook Closes #918 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ue grammar (#950) ## Summary Two independent posture fixes from umbrella #912, batched under one source-control bump (`0.16.2 → 0.16.3`). **W1 — dependency-manager hold-merge login set is configurable.** The babysit merge gate held only the built-in `dependabot`/`renovate` product bots (`DEPENDENCY_MANAGER_LOGINS`), so a non-dependabot/renovate dependency bot an operator runs slipped the cross-tier hold. `is_dependency_author` now also matches any login in the new `babysit_extra_dependency_manager_logins` userConfig, threaded as the `--extra-dependency-manager-logins` merge-wrapper flag (mirroring the existing `--approver-bot-logins` arg-threading through `evaluate()`). Logins normalize identically on both sides (casefold, strip `app/` and `[bot]`). Ships empty → unconfigured installs match the built-in set alone. Wired only to the merge gate (the single `is_dependency_author` call site), not the snapshot. **W2 — branch-to-issue grammar is configurable.** `parse-branch-issue.sh` hardcoded the `<type>/<N>-<slug>` (and `routine-issue-<N>`) convention, so a repo on a different scheme (e.g. Jira keys `feature/PROJ-123-slug`) silently failed to derive a `Closes #N` line. The script now accepts an ERE `pattern` positional (last capture group = issue id), passed from the new `branch_issue_pattern` userConfig at the `/pull-request create` call site; the built-in convention stays the default when unset (an unsubstituted `${user_config…}` placeholder is treated as absent). Per the plugins-reference, `CLAUDE_PLUGIN_OPTION_*` reaches hook processes only — not skill-invoked scripts — so the value is passed as an arg rather than read from the environment. ## Testing - `is_dependency_author` extra-login normalization cases (casefold, `app/`, `[bot]`; empty extra never widens the built-in set). - An `evaluate()`-level integration test that flips the dependency hold via the config on the same PR — a pure-function test would pass even with broken wiring, so this exercises the CLI-arg-shaped frozenset → `evaluate()` param → line-715 hold path. - Full babysit Python suite: 348 passed. - `parse-branch-issue.test.sh`: 10 passed (incl. Jira-key custom pattern and the unsubstituted-placeholder fallback). `shellcheck` clean. ## Docs `plugin.json` userConfig (both keys), babysit `SKILL.md` config table, `reference/feedback.md`, source-control README config table, and the `create.md` call site. ## Related - Part of umbrella #912 (contract + design there — not diverged). - Sibling merged this session: #928 (guardrails 0.9.6), #932 (guardrails 0.9.7). Closes #917 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary Audit f2 residual (#912; follow-up to #916 / PR #928): `source_control_enabled()` in `flag-commit-pr-skill-bypass.sh` counted a `settings.local.json` value only when the project `settings.json` already declared the same key. A plugin enabled ONLY at local scope — `claude plugin install --scope local`, a first-class state per the official plugins reference — therefore resolved as disabled, and the `gh pr create` advisory never fired (same silent false-negative class #916 fixed for user-global). A local value now participates in per-key resolution unconditionally, matching the documented scope precedence (Local > Project > User). The two tests that encoded the old "a local-only key is ignored" model are inverted, plus a new local-only-enable-with-no-other-scope case. Docs consulted per the fresh-docs mandate: [settings scope precedence](https://code.claude.com/docs/en/settings), [`--scope local`](https://code.claude.com/docs/en/plugins-reference). guardrails `0.12.0` → `0.12.1` with CHANGELOG entry. ## Test plan - [x] `flag-commit-pr-skill-bypass.test.sh` — 28/0 (red-first: 3 new/inverted local-scope cases) - [x] `scripts/check-changelog-parity.sh --check-bump main` — pass - [x] shellcheck clean ## Related - Closes #1045 - Refs #912 (audit umbrella, f2), #916 / #928 (user-global fix this completes) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
f2 (audit #912). The
flag-commit-pr-skill-bypassadvisory'ssource_control_enabledprobe read only the consuming project's.claude/settings.json(+ local override), so when source-control is enabled solely at user-global scope (~/.claude/settings.json) — a common install — the probe false-negatived and thegh pr createadvisory never fired.Fix
enabledPlugins.read_sc_enabledhelper (dedups the jq read).HOME(a clean HOME by default, so no CI-runner~/.claudeleaks in) and add four user-global cases, including the exact false-negative this fixes.Verification
bash plugins/guardrails/hooks/flag-commit-pr-skill-bypass.test.sh-> 21/21 passshellcheck --rcfile=.shellcheckrcclean0.9.5->0.9.6+ CHANGELOG entryRelated
Closes #916