One install gives your agent an executed, evidence-backed red-team pass over an app you own: nine written safety gates first, then three parallel record-only subagents — each with its own isolated User A, User B and Admin accounts — running 14 authorization and abuse checks, a request and a response captured per row, one central fix pass, and a rerun with a negative control that proves the test can see the bug.
install
Detects Claude Code, Cursor, Codex, Qoder and more, then writes the skill into the right folder. Add -l to see what the repo ships before anything is installed.
npx skills add arm-pz/pre-ship-security-red-team
ask it things like
It takes a URL and, optionally, a project path — <local app URL> [project path] — and asks until every one of the nine gates is answered in writing. Nothing is attacked before the gate is filled in.
The launch question, asked honestly.
Names the failure class; it runs the check and shows the capture.
The workflow fires on the surfaces that carry the risk, not only on an explicit "red team" request.
how it works
The subagents attack and record — scenario id, preconditions, safe reproduction, redacted request and response, expected versus actual, evidence location. They never fix, never edit application files, never touch accounts. The chair consolidates duplicates, fixes centrally in blast-radius order, then reruns.
The failures that expose or destroy someone else's data.
Where the client was trusted and the server agreed.
Everything that leaves the request/response pair.
User A, User B and Admin per group, prefixed g1a@test.invalid so two subagents never write the same rows and cleanup stays mechanical. The chair creates and deletes them; subagents never promote or remove an account.
Scaffolded into .pre-ship-red-team/<run-id>/: gate.md, an ignored .env written before the first test credential exists, one brief and one results table per group, capture directories, then report.md. Verdicts are pass, fail, not-proven, not-applicable — unrun is the scaffolded default and always fails the check. After the fixes, check_results.mjs --require-fixes exits 1 on a missing row, a pass without a captured request and response, a fix without its negative control, or an unmasked credential anywhere in the run directory.
before and after
One end-to-end run, 2026-10-18, against a purpose-built disposable app on 127.0.0.1 — a dependency-free Node notes/SaaS fixture, not published. The environment gate resolved to LOCAL, so every request stayed on loopback against synthetic rows. Three record-only subagents, one per isolated account set, returned this.
real end-to-end run · 2026-10-18 · local fixture · gate verdict LOCAL · every row executed
| scenario | before | after | falsified |
|---|---|---|---|
| S01 anonymous database or API access | fail critical private collection readable with no credentials, using a key the app published itself | pass | yes |
| S02 cross-user object access | fail critical read, write and delete on another user's row, on both the API and the HTML route | pass | yes |
| S03 self-service privilege escalation | fail critical PATCH /api/me {"role":"admin"} persisted and unlocked the admin endpoint | pass | yes |
| S04 password-reset token reuse | fail high same token accepted three times, never consumed, no expiry, minting unthrottled | pass | yes |
| S05 direct request bypass of UI limits | fail high unreviewed draft published by POSTing past the disabled button | pass | yes |
| S06 free-tier or quota bypass | fail medium one AI surface gated on a client-supplied x-credits header | pass | yes |
| S07 concurrent quota race | fail high 5 concurrent calls accepted against an allowance of 3; only 2 deducted | pass | yes |
| S08 input validation and boundaries | fail medium 50k-char, empty, NUL, bidi, wrong-type and non-JSON bodies all stored and read back | pass | yes |
| S09 redirect validation | fail medium //evil and /\evil both cleared a leading-slash test and left the origin | pass | yes |
| S10 stored XSS | fail critical body rendered raw, alert() dialog fired in a browser as a second user | pass | yes |
| S11 SSRF | fail critical internal loopback service fetched and its body reflected back to the caller | pass | yes |
| S12 private response and cache isolation | fail critical per-user profile marked public, max-age=600 with no Vary | pass | yes |
| S13 preview deployment access | pass route required its token at baseline | pass | n/a |
| S14 prompt injection through ingested content | fail critical a SYSTEM: line in an uploaded document made the tool return another user's records | pass | yes |
how the fixes were proved
Twice, not asserted: the committed rerun suite returns 14 REFUSED / 0 DETECTED, and a mutation harness deletes one protection at a time on a throwaway copy of the server and requires the mapped check to flip back to DETECTED — 17 of 17 protections falsified. That second run is what makes the first one meaningful; a suite that only ever passes cannot tell you whether it can see dirt.
Read the table with its limits, which the run records in report.md: it shows what the rig planted, not what an average app contains; six controls in the rig held at baseline, so 13-of-14 is a fixture property; the S12 proof is header-level because no shared cache sits in front of a loopback server; the browser dialog proof exists for the before state only; and the hosting-platform preview class (Vercel, Netlify, Cloudflare) was never reachable from a local run.
Also verified, independently of the app: the scaffold and the checker are tested against 11 fixture outcomes — unfilled rows, code-only passes, legitimate not-applicable rows, open fails allowed pre-fix and blocked with --require-fixes, fixes without a negative control, an unmasked JWT planted in a capture — all passing. This is a pass rate over 14 checks, not a claim that an app is free of vulnerabilities, and it never calls anything secure.
what it will not do
Explicit ownership or explicit authorization, and nothing outside the approved environment. No third-party systems, no real users, no real payment flows — payments in test mode only, and the mode is confirmed from the key itself, not from the filename.
Local or isolated environments only, never production. A preview that shares the production database is production: a non-local --app-url is flagged in the written gate, and the checker refuses to certify a report whose gate was never filled in.
The concurrent-quota test defaults to five requests; twenty only on your explicit approval for a local-only test. It backs off on 429 or 503, stays inside the per-group request budget recorded in the gate, and stops the run on instability or any sign of production connectivity.
SSRF uses a local mock server and reserved .invalid destinations; redirects go to .test hosts. It never loads a third-party origin, never port-scans past the app's own origin, and never calls a real cloud metadata endpoint.
Passwords stay in a git-ignored .env written before the first credential exists; tokens and personal data are masked in every artifact. And it changes nothing you did not approve: no plugin installs, no dashboard or configuration edits, no external deletions, no commits or pushes.
It reports what ran, what was refused and what was never proven, with a ship posture of HOLD or ready scoped to what was tested, and a named list of every row that never ran. Fourteen passing rows is evidence about fourteen checks — not proof of no vulnerabilities.
Free, open, and it only runs where you are allowed to test: local or isolated environments, never production.
Install pre-ship-security-red-team Read the source