Your attack surface is everything an attacker can interact with. The larger it is, the more opportunities they have. This lesson maps the attack surface of a typical modern web application so you know exactly what you are defending.
What counts as attack surface
Any point where data crosses a trust boundary is attack surface. That includes:
- Every HTTP endpoint your server exposes
- Every client-side route that processes URL parameters
- Every WebSocket connection
- Every file upload handler
- Every third-party integration that sends or receives data
- Every environment variable, secret, or configuration value
- Every dependency in your
package.json,requirements.txt, orGemfile
If an attacker can reach it, control its input, or influence its behaviour, it is part of your attack surface.
The layers
1. The network edge
Before a request even reaches your application code:
- DNS — can an attacker hijack your domain or subdomain? Dangling DNS records pointing to deprovisioned services (subdomain takeover) are a real and common issue.
- TLS — are you terminating TLS correctly? Are certificates valid and rotated? Is HTTP redirected to HTTPS?
- CDN / Load balancer — what headers does your CDN add or strip? Can an attacker bypass the CDN and hit your origin directly?
- WAF — if you have one, what does it cover and what does it miss? WAFs are a layer, not a solution.
2. The HTTP layer
Every endpoint is a potential entry point:
- Routes — every route your framework exposes, including ones you forgot about (debug endpoints, health checks leaking internal state, test routes left in production).
- Methods — does your endpoint respond to methods you did not intend? A
GET /users/123endpoint might also respond toDELETE /users/123if you have not restricted methods. - Headers — custom headers,
Hostheader injection, and CORS misconfigurations all live here. - Cookies — are they
HttpOnly,Secure,SameSite? Are session tokens predictable? - Query parameters and request body — every field is an input. Every input is a potential injection vector.
3. Authentication and session management
- Login endpoints — brute-force, credential stuffing, timing attacks
- Password reset flows — token predictability, token reuse, email enumeration
- OAuth/OIDC integrations — redirect URI validation, state parameter, token leakage via referrer
- Session handling — fixation, expiry, concurrent sessions, token storage location
4. APIs
Modern apps often expose more surface through APIs than through traditional pages:
- REST endpoints — every path and parameter combination
- GraphQL — introspection queries, nested query abuse, authorisation per field/resolver
- gRPC / WebSocket — often less battle-tested and less monitored than REST
- Internal APIs — microservice-to-microservice calls that may lack authentication because "they're internal"
5. Client-side code
Your JavaScript bundle ships to the attacker's machine. They can read every line of it.
- Source maps — if deployed to production, they give attackers a readable version of your code
- API keys and secrets — anything in client-side code is public. Period.
- Client-side routing logic — DOM-based XSS, open redirects, insecure
postMessagehandlers - LocalStorage / SessionStorage — storing tokens or sensitive data here is accessible to any XSS
6. Data storage
- Database — SQL injection, NoSQL injection, ORM injection, broken access control at the query level
- Object storage (S3, GCS, Azure Blob) — public buckets, insecure pre-signed URLs, no server-side encryption
- Caches (Redis, Memcached) — unauthenticated by default in many configurations, cache poisoning
- Log storage — sensitive data in logs, log injection
7. Third-party dependencies
Your application does not exist in isolation:
- npm / PyPI / Maven packages — every dependency is code you did not write but run with full trust
- SaaS integrations — Stripe, Auth0, SendGrid, etc. — each has API keys, webhooks, and trust assumptions
- CDN-hosted scripts — any external
<script>tag is giving that host code execution in your origin - Browser extensions — outside your control, but they can modify your page DOM
8. Infrastructure and CI/CD
- Container images — base images with known CVEs, running as root
- CI/CD pipelines — secrets in environment variables, overly permissive runners, pull request injection
- Cloud IAM — overly broad roles, unused service accounts, cross-account trust
- Kubernetes — exposed dashboards, RBAC misconfigurations, network policies
Reducing your attack surface
You cannot eliminate attack surface entirely, but you can minimise it:
- Remove what you do not need. Dead endpoints, unused dependencies, debug routes, legacy APIs — delete them.
- Restrict access. Not every endpoint needs to be public. Not every service needs internet access. Use network segmentation, authentication, and authorisation.
- Limit functionality. If an endpoint only needs to accept JSON, reject everything else. If a field only needs an email address, validate it as one.
- Monitor what remains. You cannot protect what you cannot see. Maintain an inventory of your endpoints, dependencies, and integrations.
Practical exercise
Pick a feature in your application. Draw its data flow from the user's browser to the database and back. For each hop, list:
- What protocol is used?
- Who can reach this component?
- What input does it accept?
- What could an attacker do at this point?
You will likely find at least one thing you had not considered. That is the point.
Summary
A modern web application's attack surface spans the network edge, HTTP layer, authentication system, APIs, client-side code, data stores, third-party dependencies, and infrastructure. The more surface you expose, the more you have to defend. Map it, minimise it, and monitor what remains.
