by opaland
Run the six passive security checks a QA engineer can own - auth boundaries, IDOR, error handling, user enumeration, headers, ZAP baseline - each as a protocol with its fixture requirement and its well-known way of being run wrong, so a check that tests nothing stops looking like a pass.
npx @qaskills/cli add security-surface-checks-passive-risk-ranked-with-the-mis-runs-namedAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
Standalone adaptation. Self-contained version of the
security-surfaceskill from QAIA (MIT). The canonical version and itsreferences/live in that repository. QAIA is pre-alpha and says so.
Passive: observation only. No active exploitation, no payload fuzzing, no destructive request. An unattended agent running active exploitation against someone's application is a liability, not a test. An OWASP ZAP baseline scan is available as an opt-in extra.
Self-hosted or explicitly authorised targets only.
This is what a QA engineer can own without becoming a pentester. It does not replace one.
A fixed checklist run uniformly treats every application the same regardless of what it actually protects.
Propose the ranking with your reasoning and let a human arbitrate. Never a silent verdict.
Four distinct cases, not one. Protected endpoints must reject: a missing token, a malformed token, an expired token, and a foreign-signed token — each with 401.
Testing only the missing-token case is the common shortcut, and it is the weakest of the four.
The most frequently mis-run check in this list, and the one with the highest hit rate on real applications.
Fixtures — mandatory. The check is void without them.
Requests, with B's valid token, against A's resource id:
| Case | Method | Expected |
|---|---|---|
| S2-a | GET /resource/{A_id} | 404 (or 403) |
| S2-b | PUT/PATCH /resource/{A_id} | 404 (or 403), and A's resource unchanged |
| S2-c | DELETE /resource/{A_id} | 404 (or 403), and A's resource still exists |
| S2-d | GET /resources | A's resource absent from B's list |
Then repeat S2-a with A's own token and expect 200. Without that control, a mistyped id or a resource that never existed makes every 404 look like a pass — the check would "succeed" against a completely broken endpoint.
404 is preferred over 403 where the existence of the resource is itself sensitive: a 403 confirms "this id exists, you just can't have it". Whichever you choose must be consistent between existing-but-foreign and never-existed ids — an application answering 403 for one and 404 for the other has rebuilt the enumeration oracle it was trying to close.
The usual mis-run, named because it is the whole point. Testing with a missing or invalid token instead of B's valid one. That is S1. It tests authentication and passes trivially. IDOR is an authorization failure: the caller is perfectly authenticated and simply asks for something that is not theirs.
If there is no second account, it is not an IDOR test. Report it as blocked for want of a second account — never as passed.
Second mis-run. Checking read only. Write paths are frequently authorized separately from read
paths, and DELETE is regularly the one left unguarded. S2-b and S2-c must verify the side
effect by re-reading as A, not just the status code: an API can return 403 and still have
applied the change.
Malformed bodies, wrong content types, oversized payloads, unsupported methods. The application must refuse without leaking a stack trace, a framework banner, a SQL fragment or an internal path.
The honest boundary: if the specification documents no error contract, an odd-but-harmless response is an observation, not a defect. Record it so a human can decide whether to add the promise — do not fill the gap with a guess about what the application should have done.
Fixtures. One valid username, one certain not to exist.
Requests. Valid user + wrong password; non-existent user + any password; and where applicable, a locked or disabled user + wrong password.
Expected — identical on all three channels:
Check password reset and registration too. Reset flows are where enumeration usually survives after the login form has been fixed ("no account with that email").
The usual mis-run. Comparing only the message string. Timing and status are the channels that stay open after someone has unified the copy.
Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options,
Referrer-Policy; and on session cookies: Secure, HttpOnly, SameSite.
Report what is absent. Do not present a missing header as an exploit.
Passive spider and passive rules only. Never the active scanner without explicit written authorisation for that specific target.
- name: Install QA Skills
run: npx @qaskills/cli add security-surface-checks-passive-risk-ranked-with-the-mis-runs-named4 of 29 agents supported
Go from zero to Playwright pro: Page Object Model, fixtures, and CI/CD on real projects.
Use code PROMODE at checkout