
itsrealaj/never-get-hacked-skill
Developer toolsNEVER get hacked again while vibecoding!
Overview
End-to-end web app security for people who build by prompting. It meets a project wherever it actually is — idea, half-built, live with users, or on fire — audits what exists on disk instead of what the user says exists, and returns a prioritised plan with the fixes written as working code. Never answer a security question from priors. A builder's description of their app is unreliable — not because they lie, but because they don't know which details matter, and because AI-generated code contains things they never saw written. A finding you verified beats ten you predicted. The corollary is the part most tools get wrong: You can say a specific gate passed. You can say a specific check found nothing. Those are different claims, and the difference is the whole point. Stage and architecture lane, both read off the filesystem. The skill does not ask — self-reports are systematically optimistic, and the files know. If security/data-map.
README
End-to-end web app security for people who build by prompting.
It meets a project wherever it actually is — idea, half-built, live with users, or on fire — audits what exists on disk instead of what the user says exists, and returns a prioritised plan with the fixes written as working code.
The one rule
Audit before you advise. Every time. No exceptions.
Never answer a security question from priors. A builder’s description of their app is unreliable — not because they lie, but because they don’t know which details matter, and because AI-generated code contains things they never saw written. A finding you verified beats ten you predicted.
The corollary is the part most tools get wrong:
Never tell the user they are secure.
You can say a specific gate passed. You can say a specific check found nothing. Those are different claims, and the difference is the whole point.
How it works
1. Locate the project. Stage and architecture lane, both read off the filesystem. The skill does
not ask — self-reports are systematically optimistic, and the files know. If security/data-map.yaml
is absent, Stage 0 is incomplete no matter how much code exists.
2. Audit, cheapest tier first. Two offline scripts and one consent-gated live probe, then the differential authorization test that no scanner can do. It stops and reports early if Tier 0 finds something critical — a user with an open database does not need Tier 3 style notes.
3. Filter before reporting. Every draft finding runs through a false-positive catalog first. This is not optional, and it is the step that decides whether the report is useful or noise.
4. Report by chain, not by severity column. Every real breach in the research record is a chain. The solo-founder threat model has no SOC, no WAF, and a time-to-detect of whenever they next look, so composite risk behaves like the product of exploitabilities, not the max.
The stage ladder
Eleven stages, each with a mechanically verifiable exit gate, plus an out-of-band incident track. Stages 0–2 are cheap and are the ones that get skipped.
| Stage | Name | Exit gate proves |
|---|---|---|
| 0 | Data decisions | Three files exist; no field lacks a reader |
| 1 | Accounts | Hardware keys on five root accounts; agent scoped |
| 2 | Scaffold | No secret in git or bundle; lockfile committed; install scripts off |
| 3 | Data model | External curl with the public key returns nothing |
| 4 | Features | Differential two-account test passes on every route |
| 5 | Pre-deploy | Build artifact grepped; headers set; debug routes gone |
| 6 | First URL | Previews gated; .env / .git / _src 404; DNS clean |
| 7 | Real users | Rate limits with shared state; spend caps with actions |
| 8 | Money | Webhook signature verified; entitlement only from the webhook |
| 9 | Steady state | Dependabot + cooldown + weekly slot + tested restore |
| 10 | Decommission | DNS first, then resources, then revoke; nothing resolves |
| E | Incident | Evidence preserved before anything is touched |
Stage 3 is bolded because that is where almost every published breach in the research record actually
lives. None of them needed an exploit. They needed a curl.
The three architecture lanes
Getting the lane wrong is the single worst failure mode of this skill, so it is decided before any advice is given.
| Lane | Shape | Consequence |
|---|---|---|
| A | Server-rendered framework, managed auth, Postgres, server-only DAL | Standard defenses apply |
| B | Client-only SPA talking straight to Supabase/Firebase, no server module | Most standard defenses are inexpressible, not merely missing |
| C | Python backend — FastAPI, Django, Flask | A different footgun set entirely |
Telling a Lane B user to add authorization to your API route or validate with Zod on the server is advice they cannot execute. There is no server. It reads as competent and it is useless. Lane B gets its own impossible-vs-substitute table, plus the seven observable triggers that mean the client-only architecture has to end.
Install
git clone https://github.com/ItsRealAJ/Never-Get-Hacked-Skill.git
cp -r Never-Get-Hacked-Skill/never-get-hacked ~/.claude/skills/never-get-hacked
For one project instead of every project, copy it into .claude/skills/never-get-hacked inside the
repo. Keep the directory name — it must match the name: field in SKILL.md.
Invoke it with /never-get-hacked, or let it trigger on its own. It fires on is my app secure,
review my security, I’m about to deploy, I think I leaked a key, I think I’ve been hacked,
and on adding auth, payments, file upload, or AI features.
The scripts
Three bash scripts. curl for the live probe, nothing else — no install step, no dependencies.
scripts/audit-repo.sh [path] # read-only, no network, safe on any repo
scripts/audit-bundle.sh [build-dir] # what the attacker actually reads
scripts/probe-live.sh --i-own-this # only against a target you own
Each takes --brief for one line per finding when output budget is the constraint. Nothing is
skipped in brief mode — same checks, same finding IDs, same counts; re-run without it to get every
word back.
audit-repo.sh — static, offline, safe on a stranger’s repo
Read-only by construction: no writes to the target, no network, no execution of anything in the repo,
and its only temp directory is removed on exit. Always exits 0 — it is an informational tool, not a
gate.
never-get-hacked :: audit-repo.sh v1.0 (--brief)
target : ./demo-app | 4 files | matcher grep | READ-ONLY, no network
=== INVENTORY
stack : supabase
lane : A — server tier present: src/app/api
git : no repo — SECRET-05 history checks skipped
=== TIER 0 — fifteen-minute triage
[CRIT] BAAS-04 service_role referenced — check every one of these is server-only
:: ./.env:3 ./src/app/api/orders/route.ts:2
[CRIT] BAAS-04 Bundled JWT decodes to role=service_role — this key carries BYPASSRLS
:: ./.env:3 -> role=service_role
[HIGH] AUTHZ-10 No test directory at all — there is no differential authorization test
[ ok ] no Firebase rules files in the repo
=== TIER 1 — secrets and the client bundle
[CRIT] SECRET-01 A public build-time env prefix in front of a secret-sounding name :: ./.env:4
[HIGH] SECRET-04 Production source maps enabled in the build config :: ./next.config.js:1
[NOTE] NOTVULN-01..04 Public-by-design keys are present. THIS IS NOT A FINDING.
Governing rule: flag the MISSING CONTROL, not the visible key. Reporting one of
these as a 'critical leak' is the fastest way to make the real finding get ignored.
=== SUMMARY
CRITICAL 4 HIGH 5 MEDIUM 2 notes 1
11 findings across 4 files. Each is a CHECK that fired, not a confirmed exploit.
Order fixes by CHAIN, not by the severity column.
Then it prints the section that most tools leave out:
=== WHAT THIS SCRIPT DID NOT CHECK
1. AUTHORIZATION — static analysis structurally cannot find broken access control.
THE #1 FAILURE CLASS AND THIS SCRIPT IS BLIND TO IT.
2. THE LIVE DEPLOYMENT — no network request was made.
3. THE SHIPPED BUNDLE — a secret absent from source can be inlined into the build.
4. BUSINESS LOGIC — no grep expresses "the user should not be able to do this."
5. THE DATABASE ITSELF — RLS state and grants live in Postgres; migrations show intent.
6. GIT HISTORY — working tree only. A secret removed later is still in history.
7. DEPENDENCY VULNERABILITIES — no CVE database consulted, nothing installed.
8. ACCOUNTS AND BILLING — MFA, spend caps with an ACTION not an alert, restore tests.
Silence reads as a clean bill of health, so the skill never leaves silence where a gap exists.
audit-bundle.sh — the artifact, not the source
Audits built output, because that is what the attacker downloads. A secret absent from your source can still be inlined into the bundle at build time.
It decodes JWTs rather than grepping for them, which is the entire trick: the anon key and the
service_role key are the same shape, the same length, and differ only by one claim inside the
payload. Grepping for the literal string service_role hits harmless SDK source and misses the
actual token. The role claim is the check.
| Exit | Meaning |
|---|---|
0 |
No critical finding |
2 |
A real secret is in browser-reachable output |
3 |
No build output found — nothing was audited |
probe-live.sh — external recon against your own deployment
Runs the attacker’s own recon pipeline against your site, in the order an attacker would, so you find what they would find first.
It refuses to run without both a target and --i-own-this, and prints an authorisation banner before
any request. Requests are strictly sequential with short timeouts, so it never floods and cannot be
mistaken for an attack on your own infrastructure.
| Tier | What runs |
|---|---|
| SAFE | Surface mapping, header fingerprinting, exposed-path probing, bundle mining, CORS reflection. Passive reads, indistinguishable from normal traffic. |
| LOGGED | The Next.js middleware-bypass check (CVE-2025-29927). No writes, but it appears in your access logs. |
| STAGING-ONLY | Write probes and rate-limit flooding are not run. The script prints what they are and tells you to run them against staging. |
What no scanner can do
Static analysis structurally cannot find broken access control. It requires knowing the intended policy, and no grep expresses this user should not be able to read that row. Since broken access control is the #1 real-world failure class, every scanner above is blind to the thing most likely to sink the app.
So the skill runs a differential test instead: two users, two tenants, replay every request as owner
/ other-tenant / anonymous, and diff the responses. references/authz-verification.md ships the
harness, a Playwright policy matrix wired as a required CI check, a route-coverage inventory diff
that catches the one route that missed the guard, and a pgTAP suite — because RLS tests lie by
default, and a green pgTAP run can sit next to a leaking app if a service-role route bypasses the
policy your SQL tests were checking.
The false-positive filter
The fastest way to make a security skill worthless is to tell someone their
NEXT_PUBLIC_SUPABASE_ANON_KEY is a critical leak. It is not. It was never secret. And once you have
cried wolf, the finding that actually matters gets ignored with it.
Governing rule: flag the missing control, not the visible key.
Thirteen catalogued not-findings, each paired with the control you must verify instead — publishable
Supabase and Firebase keys, Stripe and Clerk pk_, PostHog and Sentry and Mapbox, schema
enumeration, a JWT in the browser, dangerouslySetInnerHTML inside a real sanitizer, a CORS error in
the console, low-severity dependency noise, a public repo. The fourteenth entry is the list of things
the research deliberately does not claim.
Two traps that catch naive tooling in both directions:
- A Gemini API key carries the same
AIzaprefix as a Firebase web key, and must never ship to a client. Check what the key is restricted to, not what it starts with. - An
sb_secret_key returning 401 to a browser User-Agent is a guardrail against accidents, not a mitigation — and equally, that 401 is not evidence the key is safe.curlsends no browser User-Agent.
The 15 killers
Ranked by what actually takes these apps down for this population, not by CVSS.
What’s in the repo
never-get-hacked/
├─ SKILL.md the always-loaded core: the rule and the four steps
├─ references/
│ ├─ stage-detection.md classify a project without interrogating the user
│ ├─ false-positives.md 14 not-findings and the control each one requires
│ ├─ top-killers.md the 15, with exploitation chains
│ ├─ stack-and-lanes.md lane A/B/C, and the client-only ceiling
│ ├─ gates.md mechanically verifiable exit gate per stage
│ ├─ authz-verification.md the differential harness, CI matrix, pgTAP suite
│ ├─ migration-paths.md four starting points, non-destructive paths out
│ └─ incident-and-challenge.md the breach runbook, and hosting a hack challenge
└─ scripts/
├─ audit-repo.sh static, offline, read-only
├─ audit-bundle.sh the built artifact
└─ probe-live.sh external probe, consent-gated
About 9,500 lines. SKILL.md is the only file always in context; every reference loads on demand.
The common asks
“I built a site in plain HTML — how do I deploy it?” No reflexive rewrite. Static HTML with no backend and no secrets is a legitimate and safe architecture, and a needless framework migration adds attack surface. The finding is a key in a `` tag, a form posting somewhere unvalidated, or a client-side admin mode. Often the right answer is static plus one serverless function, not Next.js.
“Is my app secure?” Never answered directly. Audit, then report what passed and what did not.
“I think I leaked a key.” Rotate first, then remove from history — and know that removing it from history is not enough, because it was already scraped. But run the false-positive filter first: half of these reports are publishable keys that were never secret, and rotating a Supabase anon key as remediation is a tell that the real problem, missing RLS, went unfixed.
“I think I’ve been hacked.” Evidence first. Five minutes of preservation before anything is touched — Vercel Hobby keeps runtime logs for one hour, so the window closes on its own.
“I want to run a public hack challenge.” There is a part for that, and it is meant to be read before the challenge goes live: a separate account rather than a separate project, a separate registrable domain rather than a subdomain, hard provider caps with actions rather than alerts, and a rehearsed sub-60-second kill switch.
Hard rules the skill holds itself to
- Audit before advising. Always.
- Never say “you’re secure.” Name the check that passed.
- Get the lane right before recommending anything. Lane B cannot run server-side defenses.
- Run the false-positive filter before every report.
- Never invent a CVE, version, statistic, or advisory. Fact-checking during development caught fabricated CVE quotes and an invented report edition. An unsourced specific is worse than a general statement.
- Preserve evidence before remediating a suspected live breach.
- State what you did not check. Every time.
- Authorization is verified by testing, not by reading. Two accounts, replayed requests, diffed responses.
Scope and safety
audit-repo.sh and audit-bundle.sh are read-only, make no network calls, and execute nothing in
the target. They are safe to run against a repository you did not write.
probe-live.sh sends real HTTP requests and refuses to start without an explicit --i-own-this
attestation. Run it only against a deployment you own or are authorised in writing to test. The
destructive tiers are deliberately not implemented — the script tells you what they are and sends you
to staging.
License
MIT — see LICENSE.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add itsrealaj/never-get-hacked-skill