Use before exposing something, after inheriting a system you did not build, or when a security review says "looks fine". Finds who can reach what, the authorisation gaps scanners never report, and what not to put in the findings list.
Scanned 9/4/2026
Install to Claude Code
npx -y skills add everywan-dev/claude-code-engineering --skill map-the-attack-surface --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Map The Attack Surface?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/everywan-dev-map-the-attack-surface)More formats (shields.io, HTML) on the badges page.
---
name: map-the-attack-surface
description: Use before exposing something, after inheriting a system you did not build, or when a security review says "looks fine". Finds who can reach what, the authorisation gaps scanners never report, and what not to put in the findings list.
---
# Map the attack surface
Most security work in ordinary projects is not cryptography. It is answering one
question honestly:
**Who can reach what, and what stops them from reaching more?**
The bugs that actually hurt are rarely exotic. They are an endpoint that checks
you are *logged in* but not that the record is *yours*, a test environment
sharing production credentials, an admin panel reachable from the internet
because a proxy rule was broader than intended.
🔴 **Automated scanners find injection. They do not find authorisation.** A
scanner cannot know that record 41 belongs to a different customer than record
42. That gap is where the expensive findings live, and it is found by asking, not
by tooling.
## When to use this
- Before exposing anything to the internet, or widening who can reach it
- After inheriting a system nobody can fully explain
- When a scan came back clean and you want to know what it could not see
- Before handling customer data, money or credentials
- After any credential leak, to work out what it could reach
## The procedure
**1. List the entrances.** Every way in: public endpoints, admin interfaces, APIs,
webhooks, file uploads, scheduled jobs that fetch from outside, database ports,
management planes, SSH. Include the ones you assume are internal — then check
whether they are.
**2. For each entrance, name what is on the other side and who is allowed.**
Write it as a sentence. Vagueness here is the finding: if nobody can state the
rule, nobody is enforcing it.
**3. Test authorisation with two accounts, not one.** This is the step that finds
the real bugs. Log in as tenant A, take an identifier belonging to tenant B, and
ask for it. Do it for every object type that has an owner. A scanner will never
do this for you.
| Question | What it finds |
|---|---|
| Can A read B's record by changing an id? | Broken object-level authorisation |
| Can a normal user reach an admin action directly? | Missing function-level checks |
| Does the list endpoint filter by owner, or the detail one only? | The most common variant |
| Do the test and production systems share a credential? | One breach becoming two |
| Does an error message differ for "not yours" and "does not exist"? | Enumeration |
**4. Follow every credential to everything it opens.** A key is not a secret, it
is a set of permissions. Write down what each one can do, and whether it is
shared between environments.
**5. Ask what a compromise would let someone do, not whether it is likely.**
Likelihood is a guess; blast radius is a fact you can establish today.
**6. Write down what you did not check.** An attack-surface map that implies
completeness is worse than a partial one that is honest about its edges.
## Before you report it
Finding things is half the work. The other half is not crying wolf, because a
report padded with maybes gets skimmed once and never opened again — and the
real finding was on page three.
**Score every finding, and keep the low ones to yourself.**
| Score | What it means | What to do |
|---|---|---|
| 9–10 | You read the code. You could write the exploit. | Report it |
| 8 | A known-exploitable pattern, path traced. | Report it |
| 5–7 | Plausible. You have not traced it end to end. | Report only if asked for a wide sweep, marked *tentative* |
| Below 5 | A shape that looks suspicious. | Keep it in your notes |
**Quote the line that made you say it.** Every finding carries `file:line` and
the code itself. A finding you cannot anchor to a specific line is a hunch, and
half the time the field it complains about does not exist.
**Prove it by reading, never by firing.** Trace the path in the code: does the
signature check exist anywhere in the middleware chain, can this URL actually
reach an internal host, is the vulnerable function imported and called. Do not
send the request, do not test the key against the live API. You are auditing a
system, not attacking it — and a "harmless" probe is how audits become
incidents.
If you cannot reach the vulnerable function from the code you can see, say so:
*"not directly called — may still be reachable through framework internals."*
That sentence is worth more than a confident guess in either direction.
**These are not findings.** Reporting them is how a security review loses its
audience:
- Denial of service, resource exhaustion, rate limits. *Except* uncapped spend
on a metered third-party service — that is financial risk with a real number
on it.
- Missing hardening. A concrete reachable weakness is a finding; the absence of
a best practice is a recommendation, and they do not belong in the same list.
- Memory safety in a memory-safe language.
- Input validation on fields with no security consequence.
- Absence of audit logs. Logging *secrets* is a finding; not logging is not.
- Weak randomness where the value is not a secret — a UI element id, a cache
key.
- Anything only reachable from test fixtures that nothing else imports.
- Race conditions without a concrete path someone can walk.
- Containers running as root in a local development compose file. In a
production image, it is a finding.
**Say what you did not look at.** Same rule as the map itself. A findings list
that implies completeness is the one that gets trusted for years after it
stopped being true.
## Anti-patterns
- **"It's behind a VPN."** So is everyone who is already inside, including
anything compromised.
- **"Nobody knows the URL."** Obscurity is not a control. Certificate transparency
logs, referrers, browser history and crawlers all know the URL.
- **"The scanner is clean."** It tested for the classes it knows. Authorisation is
not one of them.
- **"Test data is not real."** Check. Test environments are frequently refreshed
from production, and often with weaker access controls.
- **Threat-modelling the exotic attacker** while an unauthenticated admin path
sits open.
## The incident
A security review of a multi-tenant panel came back clean on the automated axes:
no injection, credentials handled correctly, dependencies current.
Asking the two-account question found that a customer could read another
customer's records by changing an identifier in the URL. The list endpoint
filtered correctly by owner; the detail endpoint did not check at all. Every
automated tool had passed it, because from the outside the request looked exactly
like a legitimate one — which is precisely what it was, except for who was making
it.
The same review found the test environment authenticating to a third-party
service with the **same key as production**. Individually a housekeeping note;
combined with any weakness in the less-guarded environment, it is a path into the
real one.
## Related
- [`keep-secrets-out-of-the-repository`](../keep-secrets-out-of-the-repository/SKILL.md)
— where credentials leak from, and what to do the moment one does
- [`route-work-to-the-right-model`](../route-work-to-the-right-model/SKILL.md) —
anything on this page is level 3: three validations, one trying to break it
- [`map-an-undocumented-system`](../map-an-undocumented-system/SKILL.md) — you
cannot secure what nobody can describe
- [`review-code-you-did-not-write`](../review-code-you-did-not-write/SKILL.md) —
authorisation checks are exactly what a plausible-looking diff omits
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!