Skip to content

feat: adversarial commit-reviewer + mechanical pre-commit gate#18

Closed
bglusman wants to merge 2 commits intomainfrom
feat/auto-adversarial-review
Closed

feat: adversarial commit-reviewer + mechanical pre-commit gate#18
bglusman wants to merge 2 commits intomainfrom
feat/auto-adversarial-review

Conversation

@bglusman
Copy link
Copy Markdown
Owner

Summary

Two-layer pre-merge check for work Claude produces:

  1. Pre-commit mechanical gate (scripts/pre-commit-hook.sh
    .git/hooks/pre-commit via scripts/install-git-hooks.sh).
    Blocks any commit that would fail cargo fmt --check, cargo clippy --workspace -- -D warnings, or gitleaks protect --staged.
    Rust checks only run when Rust files are staged. Gitleaks fails
    closed — if the binary isn't installed, the commit is refused
    rather than silently bypassed. The existing scripts/pre-push.sh
    (full tests + loom) gets installed alongside as .git/hooks/pre-push.
  2. Post-commit semantic review — a commit-reviewer subagent
    (.claude/agents/commit-reviewer.md) invoked via the
    /review-commit slash command (.claude/commands/review-commit.md).
    Prompt tuned to reject style nits and "could-be-tighter" feedback
    — emits only BLOCK (real bug), INCONSISTENCY (sibling code
    disagrees), or TEST-QUALITY findings. Zero-finding reviews are
    valid and expected to be common.
  3. Automation trigger (scripts/claude-post-commit.sh, wired via
    .claude/settings.local.json.example) fires after Claude Code's
    Bash tool runs git commit, parses the JSON tool input with jq,
    excludes --dry-run/--help/non-commit forms, checks HEAD
    actually advanced since the last queued review, and writes the diff
    to .claude/pending-reviews/<sha>.md. Next Claude turn reads the
    flag file and runs /review-commit. Nothing is auto-applied —
    findings return to the main session for judgment. Amend vs. stack
    a new commit decided per-finding.

Dogfood evidence

The reviewer was run against its own introducing commit (1aba88a).
It found 3 real issues + 1 false positive:

  • BLOCK — post-commit grep -q 'git commit' matched too broadly
    (matched echo "git commit", --dry-run, etc.). Fixed with JSON
    parse + dry-run exclusion + prior-SHA backstop.
  • BLOCK — pre-commit note "gitleaks not installed — skipping"
    was a default-on bypass, contradicting CLAUDE.md. Fixed to fail
    closed with an explicit PRE_COMMIT_SKIP_GITLEAKS=1 override.
  • INCONSISTENCYinstall-git-hooks.sh left pre-existing
    non-symlink hooks in place and returned 0 silently. Fixed to back
    them up and replace.
  • Rejected — reviewer questioned the if ! cmd | tail pipefail
    idiom. Verified empirically it works; noted as a "be more decisive"
    prompt tweak for future reviewer iterations.

All three fixes live in the follow-up commit in this PR (stacked, not
amended — demonstrating the intended workflow).

Test plan

  • bash -n on all three scripts
  • install-git-hooks.sh is idempotent (symlinks identical → no-op)
  • Commit in this PR passed through the new pre-commit gate (gitleaks green)
  • /tmp/pipefail_test.sh empirically confirmed the if-!-pipe idiom works
  • Manual: stage a file with a trailing whitespace change, commit, expect fmt block
  • Manual: stage a Bearer-token-shaped string, commit, expect gitleaks block
  • Manual: copy settings.local.json.example to settings.local.json, commit something, confirm .claude/pending-reviews/<sha>.md appears

🤖 Generated with Claude Code

bglusman and others added 2 commits April 24, 2026 10:55
Two-layer code review layered on top of commits:

1. **Mechanical gate** — `scripts/pre-commit-hook.sh` installed at
   `.git/hooks/pre-commit` via `scripts/install-git-hooks.sh`. Blocks
   a commit if it would fail:
   - `cargo fmt --all -- --check` (only when Rust files touched)
   - `cargo clippy --workspace --all-targets -- -D warnings` (ditto)
   - `gitleaks protect --staged --config .gitleaks.toml`

   Deliberately excludes `cargo test` — too slow per-commit. The
   existing `scripts/pre-push.sh` covers full tests and is installed
   alongside at `.git/hooks/pre-push`.

2. **Semantic reviewer** — a `commit-reviewer` subagent
   (`.claude/agents/commit-reviewer.md`) invoked via the
   `/review-commit` slash command (`.claude/commands/review-commit.md`).
   The agent prompt is tuned HARD against style nits and
   "could-be-tighter" feedback — it only emits BLOCK (real bug),
   INCONSISTENCY (sibling code disagrees), or TEST-QUALITY findings.
   Zero-finding reviews are a valid output.

3. **Automation** — `scripts/claude-post-commit.sh` is a Claude Code
   PostToolUse hook (wire it via the example at
   `.claude/settings.local.json.example` — copy to
   `settings.local.json` to enable). When the Bash tool runs
   `git commit`, the hook writes the diff to
   `.claude/pending-reviews/<sha>.md` and touches
   `.claude/REVIEW_PENDING`. On Claude's next turn it notices the
   flag and runs `/review-commit`, which spawns the subagent.
   Findings come back to the main session for judgment — nothing
   is auto-applied, and the amend-vs-fixup decision stays with the
   human (/Claude) on a per-finding basis.

Gitignore additions:
- `.claude/REVIEW_PENDING` — transient trigger file
- `.claude/pending-reviews/` — queued diffs awaiting review
- `.claude/settings.local.json` — per-machine Claude Code config

Rationale for splitting the subagent out of the hook:
Calling Claude from inside a Claude Code session (nested sessions)
is unpredictable and expensive. Instead the hook is a cheap
shell-only signaller; the real review happens in-session via the
normal Agent tool, which keeps cost/latency under the main
session's control.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The reviewer subagent caught three real issues in its own introducing
commit (plus one false positive about a pipefail idiom that empirically
works). Fixing the real ones:

**BLOCK: claude-post-commit.sh grep fired on non-commits.**
The old `grep -q 'git commit'` matched `echo "git commit"`,
`git commit --dry-run`, `git commit --help`, and any Bash invocation
that merely quoted the substring. Replaced with:
- JSON parse of `.tool_input.command` via `jq` (bail if jq missing —
  don't fall back to permissive grep)
- case-block excluding `--dry-run` / `--help` / `man git-commit`
- regex requiring `git commit` as a standalone action (start of
  string, after `&&`/`;`/`||`)
- a `.last-reviewed-sha` state file so a second hook invocation on
  the same HEAD is a no-op (backstop for anything the JSON guard
  misses).

**BLOCK: pre-commit gitleaks silently skipped when missing.**
The original script printed `note "gitleaks not installed — skipping"`
and let the commit through — effectively a default-on bypass, which
CLAUDE.md "Do NOT bypass the scan" explicitly rejects. Now fails
closed: if no `gitleaks` binary is found anywhere we check, the commit
is refused. Explicit override via `PRE_COMMIT_SKIP_GITLEAKS=1` for the
rare case where that's needed (and the commit message should document
why).

**INCONSISTENCY: install-git-hooks.sh silent no-op on pre-existing hook.**
The original `if [[ -e "$target" ]] && [[ ! -L "$target" ]]; then
echo '!'; return 0; fi` left any pre-existing non-symlink hook in
place, returned 0, and let the user/CI believe the install had
succeeded. Now backs the old hook up to `.git/hooks/<name>.backup-<ts>`
and installs the new link. One-step restore available if the old hook
was intentional.

**Rejected finding (for the record):** the reviewer also flagged the
`if ! cargo clippy | tail` pipefail idiom as silently swallowing
failures. Empirically verified it works — pipefail sets pipeline rc
to clippy's rc, `!` flips, `if` enters branch, `fail` runs. The
reviewer's writeup talked itself in a circle; landed correct but
ambiguous. Worth tracking as a "be more decisive" prompt tweak.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 24, 2026 16:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds local git-hook enforcement and a Claude Code “commit reviewer” workflow to catch mechanical issues (fmt/clippy/gitleaks) pre-commit and semantic issues post-commit, aligning with the repo’s public-repo + secret-scanning constraints.

Changes:

  • Introduces a pre-commit hook script that conditionally runs Rust checks and always runs staged-only gitleaks.
  • Adds an idempotent hook installer to wire pre-commit + existing pre-push checks.
  • Adds a Claude Code PostToolUse hook script + /review-commit command + commit-reviewer agent prompt, plus ignores for local Claude state.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/pre-commit-hook.sh New pre-commit mechanical gate (fmt/clippy conditional, gitleaks staged scan).
scripts/install-git-hooks.sh New installer to symlink repo hook scripts into .git/hooks/.
scripts/claude-post-commit.sh New Claude PostToolUse hook to queue commit diffs for subagent review.
.gitignore Ignores local Claude hook config/state and pending review artifacts.
.claude/settings.local.json.example Example local Claude hook configuration to run the post-commit script.
.claude/commands/review-commit.md Adds /review-commit command to spawn the commit-reviewer subagent.
.claude/agents/commit-reviewer.md Adds the commit-reviewer agent spec/prompt and output contract.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


# Only run the rust checks if the commit touches Rust files. Docs /
# config / scripts commits shouldn't pay the clippy tax.
STAGED_RUST="$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.rs$|Cargo\.(toml|lock)$' || true)"
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex integration sweep: addressed in #38 without editing this PR branch. FnoxClient now wraps subprocess waits in a bounded timeout with fast tests for hung get/set paths.

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# install-git-hooks.sh — idempotent installer for the repo's git hooks.
# Safe to run multiple times. Won't clobber any hook already in place.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex integration sweep: acknowledged. I am leaving this PR branch untouched per the parallel-agent boundary; this remains actionable for the PR owner or a follow-up unless it is superseded by #38.

Comment on lines +24 to +28
# Extract the actual command from the JSON. Require jq — it's present
# on every platform we support via the installer's toolchain. If jq is
# missing we bail cleanly rather than falling back to a permissive
# grep that fires on text-about-commits.
if ! command -v jq >/dev/null 2>&1; then
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex integration sweep: acknowledged. I am leaving this PR branch untouched per the parallel-agent boundary; this remains actionable for the PR owner or a follow-up unless it is superseded by #38.

@bglusman bglusman marked this pull request as ready for review April 24, 2026 16:13
bglusman added a commit that referenced this pull request Apr 25, 2026
…ning (#44)

Squash-merge of integration/super-combined — 4 weeks of feature work + cross-PR security
fixes + codex agent's hardening, all green CI (14/14 checks).

## Features landing
- **fnox secret-resolver integration** (#15) + FnoxClient subprocess wrapper (#21)
- **Adversarial commit-reviewer + mechanical pre-commit gate** (#18)
- **{{secret:NAME}} substitution engine** in security-proxy URL/headers/body (#19)
- **Per-secret destination allowlist** (#22) — RFC §11.1 attack defense
- **!secure chat commands** (set/list) on Telegram (#20), Matrix (#28), WhatsApp (#31)
- **zeroclawed-mcp** scaffold — agent-facing secret discovery server (#23)
- **install.sh wires MCP** into Claude Code agent configs (#26)
- **zeroclawed-secret-paste** — localhost web UI for one-shot secret input (#34)
- **Bulk paste UI** — .env-style multi-secret onboarding with per-line results
- **LAN-friendly defaults** — bind 0.0.0.0 + RFC 1918 Origin acceptance
- **WhatsApp HMAC verification** (was always-true placeholder before — codex hardening)

## Security fixes folded in
- /vault/:secret bearer auth + 127.0.0.1 default bind (#39)
- URL-embedded secrets honor destination allowlist (#41)
- Paste-flow: bearer URL only at debug, fnox set via stdin not argv (#40)
- Paste-flow: graceful shutdown, exit-on-submit, reject Origin: null (#43)
- Subprocess timeouts + kill_on_drop on FnoxClient
- BrokenPipe-tolerant stdin write (Linux CI surface)
- Header-value log redaction
- OneCLI bound to 127.0.0.1 by default
- Sanitized real API token + Telegram IDs from sample configs (#36)

## Architecture / refactors
- Consolidated onecli binary into security-proxy (#17)
- Hardcoded vault URL removed from onecli-client
- security-proxy resolver wired into hot path
- Extracted build_app router; migrated /vault/:secret route
- !secure parser uses split_whitespace (was splitn), audit-logs invocations

## Test coverage added
- security-proxy substitution engine + body/headers tests
- onecli-client retry + Http(_) variant + adversarial fallthrough suite
- onecli-client client.rs rewritten from tautologies to wiremock-backed
- config/validator coverage (was zero, now 290-line module covered)
- 16 zeroclawed-secret-paste tests including bulk-mode cases

## Docs / RFCs
- agent-secret-gateway holistic architecture
- consolidation-findings (what #28 must address)
- secret-input-web-ui RFC (input-only, new-by-default)
- browser-harness integration spike
- test-quality-audit Round 1+2+3 (host-agent + zeroclawed priority files)

## Codex agent's hardening cherry-picks
- Subprocess timeouts on fnox calls
- map_spawn_error helper
- Validator hardening + atomic-counter digest race fix
- WhatsApp HMAC implementation + tests
- proxy header-value log redaction

CI: all 14 checks green at squash time.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@bglusman
Copy link
Copy Markdown
Owner Author

Subsumed by #44 (squashed to 9ed51fbc on main). All commits from this branch are present in the squash. Closing as redundant rather than merging again.

@bglusman bglusman closed this Apr 25, 2026
bglusman added a commit that referenced this pull request Apr 25, 2026
…ning (#44)

Squash-merge of integration/super-combined — 4 weeks of feature work + cross-PR security
fixes + codex agent's hardening, all green CI (14/14 checks).

## Features landing
- **fnox secret-resolver integration** (#15) + FnoxClient subprocess wrapper (#21)
- **Adversarial commit-reviewer + mechanical pre-commit gate** (#18)
- **{{secret:NAME}} substitution engine** in security-proxy URL/headers/body (#19)
- **Per-secret destination allowlist** (#22) — RFC §11.1 attack defense
- **!secure chat commands** (set/list) on Telegram (#20), Matrix (#28), WhatsApp (#31)
- **zeroclawed-mcp** scaffold — agent-facing secret discovery server (#23)
- **install.sh wires MCP** into Claude Code agent configs (#26)
- **zeroclawed-secret-paste** — localhost web UI for one-shot secret input (#34)
- **Bulk paste UI** — .env-style multi-secret onboarding with per-line results
- **LAN-friendly defaults** — bind 0.0.0.0 + RFC 1918 Origin acceptance
- **WhatsApp HMAC verification** (was always-true placeholder before — codex hardening)

## Security fixes folded in
- /vault/:secret bearer auth + 127.0.0.1 default bind (#39)
- URL-embedded secrets honor destination allowlist (#41)
- Paste-flow: bearer URL only at debug, fnox set via stdin not argv (#40)
- Paste-flow: graceful shutdown, exit-on-submit, reject Origin: null (#43)
- Subprocess timeouts + kill_on_drop on FnoxClient
- BrokenPipe-tolerant stdin write (Linux CI surface)
- Header-value log redaction
- OneCLI bound to 127.0.0.1 by default
- Sanitized real API token + Telegram IDs from sample configs (#36)

## Architecture / refactors
- Consolidated onecli binary into security-proxy (#17)
- Hardcoded vault URL removed from onecli-client
- security-proxy resolver wired into hot path
- Extracted build_app router; migrated /vault/:secret route
- !secure parser uses split_whitespace (was splitn), audit-logs invocations

## Test coverage added
- security-proxy substitution engine + body/headers tests
- onecli-client retry + Http(_) variant + adversarial fallthrough suite
- onecli-client client.rs rewritten from tautologies to wiremock-backed
- config/validator coverage (was zero, now 290-line module covered)
- 16 zeroclawed-secret-paste tests including bulk-mode cases

## Docs / RFCs
- agent-secret-gateway holistic architecture
- consolidation-findings (what #28 must address)
- secret-input-web-ui RFC (input-only, new-by-default)
- browser-harness integration spike
- test-quality-audit Round 1+2+3 (host-agent + zeroclawed priority files)

## Codex agent's hardening cherry-picks
- Subprocess timeouts on fnox calls
- map_spawn_error helper
- Validator hardening + atomic-counter digest race fix
- WhatsApp HMAC implementation + tests
- proxy header-value log redaction

CI: all 14 checks green at squash time.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@bglusman bglusman deleted the feat/auto-adversarial-review branch May 1, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants