Adversarial review of a plan file — a single Opus pass — before implementation of a ≥3-module or ambiguous-scope change.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add senda-labs/DQIII8 --skill panel-review --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Panel Review?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/senda-labs-panel-review)More formats (shields.io, HTML) on the badges page.
---
name: panel-review
description: Adversarial review of a plan file — a single Opus pass — before implementation of a ≥3-module or ambiguous-scope change.
command: /panel-review
allowed-tools: [Bash, Read]
user-invocable: true
---
# /panel-review — Plan Adversarial Review
Run before implementing any plan that touches ≥3 modules or has ambiguous scope,
after plan-mode design and before writing code.
## Usage
```
/panel-review <plan-file>
```
Runs `python3 bin/tools/panel_review.py <plan-file>`.
## What it does
Under the Anthropic-only directive (no non-Anthropic provider API is operative
today), there is no multi-seat NIM pre-filter alongside the Opus pass — it would
route through dead infrastructure. If the multi-tier chain is ever reactivated
(`.claude/rules_db/archive/multi-tier-dormant-2026-08.md`), re-adding a
pre-filter is a deliberate future decision, not an automatic revert of this
one.
1. **Exactly ONE Opus adversarial pass** (`code-reviewer` agent → `claude-opus-5`),
the entire review. **This spends the operator's own Claude Code session
quota** — no `ANTHROPIC_API_KEY` is configured, so it runs as a nested
`claude -p` OAuth call. It reuses the *existing* single-Opus-escalation
allowance defined in `.claude/rules_db/dqiii8-plan-gate.md` (max 1 per task) —
it is not an additional or second Opus budget. No iteration, no forced
dissent, no re-voting loop: one pass, one verdict. It reads the plan and
the repo and must cite `file:line` for every finding — uncited findings
are discarded by the orchestrator, not just flagged.
2. **Report**: written to `database/audit_reports/panel-review-YYYY-MM-DD-HH-<slug>.md`.
**Never committed.** `database/audit_reports/*.md` is fully gitignored, no negation —
a `.gitignore` negation here would put a report containing a real infra IP or
credential literal one `git add -A` away from being staged. Durability for these
reports comes
from the off-VPS backup channels documented in `CLAUDE.md` (`backup_audit_docs.sh` +
`telegram_audit_backup.py`), not from git. `.gitleaks.toml` still carries the
`audit-docs-bare-ipv4` / `audit-docs-password-literal` rules scoped to this corpus —
now mostly a defense-in-depth backstop against a future re-negation, but still worth
running (`gitleaks detect`) over any new report as a manual habit, since these files
never pass through the pre-commit hook at all while gitignored.
4. **Verdict is advisory, not a gate.** The tool reports; the calling session
(CC) is responsible for addressing each cited finding before implementation
proceeds.
## Analysis procedure (v3, strict/structured)
Every seat is instructed to work through a fixed STRIDE-derived taxonomy —
Security / Correctness / DataIntegrity / Resilience / Operational — and for
each category either report a finding in a strict block format
(`[CATEGORY] [SEVERITY:P0-P3]` + `file:line` + one-line defect + concrete
exploit/failure scenario) or state explicitly "considered, no issues found."
Category/severity are advisory metadata (attached if present, never required
to survive); the **only hard discard gate is the file:line citation actually
existing in this repo** — anything discarded is still shown in a collapsed
"dropped findings" appendix with its reason (`no_citation`/`fake_path`),
never silently deleted, because silent deletion is indistinguishable from a
seat having genuinely found nothing.
One extra safeguard survives INV2's single-seat redesign:
- **"All clean" flag**: the report calls out a zero-verified-findings result
explicitly, rather than letting it read the same as "not shown" — the Opus
pass returning nothing is rare and actually means something.
(The pre-INV2 design also carried a seat-degradation banner comparing
intended vs actual provider/model per seat, and gated "all clean" on the
**Opus** seat specifically among several. Both no longer apply with a single
seat and no NIM fallback path — see "Why this design (history)" below.)
There is deliberately no "find at least one issue per category" quota — on a
seat that has already fabricated a citation once (observed live: invented
`src/db.py:15` in a repo with no `src/` directory), a quota is a fabrication
incentive, not a rigor increase.
## Hardening from enterprise-grade stress testing
Live adversarial testing of `panel_review.py` (and the sibling `watermark_scan.py`
pre-commit check) against real crafted payloads, not hypothetical ones, found and
fixed 4 real bugs in the citation/parsing path:
- **Citation path escaped `REPO_ROOT`**: `/etc/passwd:1` and
`../../../etc/passwd:1` both resolved and "verified" — `Path`'s `/` operator
discards the left operand entirely for an absolute right-hand operand, so the
intended repo-scoping silently didn't happen. Fixed: `_citation_exists()`
rejects any path starting with `/` or `~` outright, then requires
`candidate.is_relative_to(REPO_ROOT.resolve())` before treating a match as real.
- **ReDoS in `CITATION_RE`**: a ~200KB adversarial non-matching block hung the
parser >120s (confirmed quadratic backtracking, not exponential, via a
doubling-input timing sweep). Fixed with `MAX_BLOCK_LEN = 3000`: blocks past
that length can't be a legitimate finding anyway (real findings are short,
~4-line structured blocks) and are dropped with reason `block_too_long`
*before* ever reaching the regex — never silently discarded, still shown in
the dropped-findings appendix.
- **Markdown/HTML structure injection**: finding text originates from an LLM
response, itself shaped by the plan-under-review (untrusted input). Unsanitized
text could forge a fake `## Verdict` heading or close the report's `<details>`
block early, visually spoofing the real verdict for whoever reads the report.
Fixed with `_sanitize_for_report()`, applied at parse time: escapes leading
`#` headings, `</details>`, `<details`, `<summary`, `<script`.
- **Hook exit-code swallowing**: `.git/hooks/pre-commit` had no `set -e`, so a
failing `gitleaks protect` followed by a passing `watermark_scan.py` returned
exit 0 overall — silently defeating the secret-blocking gate. Fixed by adding
`set -e` to both the live hook and `bin/tools/setup_gitleaks_hook.sh` (so
re-provisioning doesn't reintroduce it).
`watermark_scan.py` got 3 companion fixes in the same pass: it now scans the
**staged git index blob** (`git cat-file -p :<path>`) instead of the working-tree
file (closes a stage-then-revert-without-restaging bypass), skips staged
symlinks entirely (a symlink's blob content is the target path string, not the
target file's content — following it would scan the wrong thing), and reads
staged filenames via `git diff --cached ... -z` instead of the default
C-quoted output (closes a silent skip of any staged file with a non-ASCII name).
**Known residual limitation, disclosed rather than fixed**: `_citation_exists()`
only proves the cited `file:line` exists in this repo — it does not verify the
finding's actual *claim* is really about that file/line. A seat could cite a
real, unrelated file to make a fabricated defect look verified. Judged
disproportionate to fix (would require semantic verification of claim-to-code
correspondence, a much heavier mechanism) relative to the residual risk, given
Opus's findings are weighted highest and a human (the calling CC session) reads
every verified finding before acting on it. Not silently assumed safe — recorded
here as an open gap.
## Why this design (history)
An earlier draft used 10 NIM seats with forced-dissent iteration and treated the
Opus seat as if it were a cheap parallel call. An independent Opus adversarial
review of that draft found: two of the "9 reviewers" resolved to the exact same
underlying model (zero epistemic diversity), and the 3-4-round design would have
spent the Opus session-quota escalation 3-4x per plan, conflicting with the
repo's own 1-per-task rule. This design replaces it.
A stricter taxonomy/format was considered and rejected: it would have silently
degraded already-weak NIM output (from zero verified findings to zero *parseable*
findings), and a per-category finding quota would incentivize fabrication.
## Related
- `.claude/rules_db/dqiii8-plan-gate.md` — the single-Opus-escalation rule this reuses
- `quality-gate` skill — code-level checks; this is plan-level, runs earlier
- `audit` skill — same tracked-report-path pattern
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!