Security vulnerabilities accumulate as quiet assumptions. An overprivileged database connection here, a missing CSP header there, an IAM policy that drifted to wildcard permissions sometime last quarter. Each one passes code review because it works and the sprint is ending. Collectively, they compose into a system with no good answers when something goes wrong: a single compromised credential cascades through layers that were never designed to contain a breach. Every layer leaks, and what matters is whether the layers around it absorb the damage or amplify it.
This is a walk through each layer of a production application, from design through deployment to monitoring, looking at specific vulnerabilities and concrete mitigations at each one. The underlying premise is defense in depth: security as an emergent property of the whole stack, one that only holds when the layers are coordinated rather than independently stacked.
Security Starts Before Code: The Design Phase
The most consequential security decisions happen before anyone opens an editor. Monolith vs. microservices determines your blast radius model; your authentication architecture decides whether a session compromise affects one service or everything. Trust boundaries drawn during design constrain what’s possible to lock down later, and trust boundaries you skip during design create gaps that runtime controls can never fully close.
I keep coming back to STRIDE threat modeling for this reason. Thirty minutes before implementation, sitting with the team and asking “who can spoof this identity, how could this data flow be tampered with, what happens if this service denies requests” surfaces vulnerability classes that are structurally baked into the architecture. Catching them here costs almost nothing; catching them after deployment costs a sprint or worse.
Least privilege matters at this stage as an architectural constraint baked into the system’s shape. Consider a microservice platform where every service shares a single database credential with full read-write access; if an attacker compromises the least critical service in the fleet, that credential gives them the same reach as compromising the most critical one. Scoped credentials per service, explicit trust boundaries in architecture diagrams, and lightweight threat model sessions for any feature touching auth or external data are all cheap at design time and expensive to retrofit.
The Frontend: Where Users and Attackers Enter
Cross-site scripting keeps showing up in production applications because it comes in flavors that catch different teams off guard. Reflected XSS bounces user input through a URL parameter back into the page. Stored XSS persists malicious payloads in your database and serves them to other users. DOM-based XSS happens entirely in the browser, when client-side JavaScript reads from an attacker-controlled source and writes it into the page without sanitization. The search page that renders the query parameter directly into the DOM is the textbook example, and I’ve seen variants of it survive multiple rounds of code review.
Modern frameworks help here. React escapes output by default, which eliminates most reflected and stored XSS. But dangerouslySetInnerHTML in React and v-html in Vue exist precisely to bypass that protection, and they get reached for more often than they should.
// Unsafe: renders raw HTML from user input
<div dangerouslySetInnerHTML={{ __html: userComment }} />
// Safe: React escapes this automatically
<div>{userComment}</div>
Content Security Policy headers add a second line of defense, restricting what the browser is willing to execute even if an XSS payload gets through your application code. A policy like Content-Security-Policy: default-src 'self'; script-src 'self' tells the browser to reject inline scripts and external script sources entirely, which neutralizes most XSS payloads even when the application fails to sanitize input.
CSRF exploits the browser’s automatic cookie attachment on cross-origin requests. The mitigations are well-established: SameSite cookie attributes, anti-CSRF tokens validated server-side, and Origin header verification. And a point worth internalizing early: client-side validation serves UX, and only UX. Every validation you perform in the browser needs a server-side duplicate, because attackers send requests directly to your API without ever loading the frontend.
The API Layer: Your Application’s Exposed Nerve Endings
APIs get probed directly with curl, automated scanners, and custom scripts, bypassing whatever protections the frontend provides. I’ve watched teams treat the API as a secure interior behind a trusted frontend, and that assumption keeps showing up in breach postmortems.
Broken authentication (OWASP A07:2021) surfaces in subtle ways: a JWT validation routine that checks the signature but skips the exp claim accepts tokens forever, secrets embedded in client-side code get extracted within hours of deployment, and token refresh flows that fail to invalidate the old token create a window where stolen credentials remain usable indefinitely.
SQL injection (OWASP A03:2021) is well-understood with well-understood mitigations, and it keeps reappearing because string concatenation is easier in the moment than parameterized queries:
-- Vulnerable: string concatenation
query = "SELECT * FROM users WHERE id = " + userId;
-- Safe: parameterized query
query = "SELECT * FROM users WHERE id = $1", [userId];
Without rate limiting, every endpoint is open to credential stuffing, scraping, and denial-of-service. Token bucket or sliding window algorithms applied per-endpoint and per-user are easy to implement and dramatically shrink your attack surface. Schema validation at the API boundary, using tools like Zod or JSON Schema, gives you a first line of defense by rejecting malformed input before it reaches business logic.
The vulnerability that tops the OWASP API Security Top 10 is Broken Object Level Authorization (BOLA), also known as IDOR, and it’s almost embarrassingly simple: /api/users/123/records returns data for user 123, but the endpoint never verifies that the authenticated requester is user 123 or has permission to access their records. Change the ID in the URL and you get someone else’s data.
The most common API vulnerability is the simplest: the endpoint returns data for the requested ID without checking whether the requester is allowed to see it.
The Database: Where Breaches Become Catastrophes
Frontend vulnerabilities are damaging; database compromises make headlines. The difference is data volume: a compromised API endpoint might leak individual records, whilst a compromised database connection exposes everything.
SQL injection at the database layer goes deeper than the API examples above. ORM misuse is a common vector: developers trust the ORM to handle parameterization, then drop into raw queries for complex operations and forget to parameterize those. Second-order injection is harder to spot because the injection point and the execution point are separated; malicious input gets stored safely, then a different code path concatenates it into a query later.
This is where least privilege gets concrete. A more defensible pattern than the ubiquitous single-user connection grants the application only the permissions it actually needs: SELECT on reference tables, INSERT and UPDATE on transactional tables, never DROP or ALTER. Separate credentials for migration tooling, analytics, and backups limit what any single compromised credential can reach.
Encryption operates at multiple levels: TLS for connections in transit, transparent data encryption for data at rest, and column-level encryption for sensitive fields like credentials and PII. Key management complicates everything here; encryption is only as strong as access control over the keys, and rotating keys without downtime requires architectural forethought.
Backup security is an overlooked parallel attack surface. Unencrypted backups stored in a location accessible from the application tier give an attacker a second path to the same data, one that bypasses every runtime access control you built. Your database security is only as strong as the weakest path to the data it contains, and backups are a path most teams forget to audit.
Infrastructure: The Floor Beneath the Floor
Secrets management follows a maturity ladder that most teams are somewhere in the middle of: hardcoded secrets in source are the worst case, environment variables are better, and a dedicated secrets manager like HashiCorp Vault or AWS Secrets Manager is the target state. Rotation matters as much as storage here. A secret that never rotates has an unbounded attack window, whilst automated rotation with short TTLs limits the usefulness of any credential an attacker manages to extract.
Network segmentation keeps the blast radius contained. The database should never be reachable from the public internet, and internal services belong on private subnets. Zero-trust networking takes this further by requiring every service to authenticate to every other service regardless of network position, because network boundaries alone break down the moment a single compromised container starts pivoting laterally.
TLS everywhere means more than the load balancer. Internal service-to-service traffic, database connections, cache connections, message queue traffic, all of it warrants encryption in transit. The overhead is negligible for most workloads on modern hardware, and the alternative is trusting that your internal network stays uncompromised forever.
Container security starts with minimal base images (distroless or Alpine), non-root execution, and read-only filesystems where possible. Image vulnerability scanning in the build pipeline catches known CVEs before they reach production, because a container running as root with a writable filesystem and a full OS userland is a gift to any attacker who achieves code execution.
IAM policies deserve the same rigor as application code review. Least privilege for cloud roles, short-lived credentials via OIDC federation over long-lived access keys, and regular audits of permission drift. A scoped policy contains an incident; a wildcard policy turns it into a full account compromise:
// Scoped: minimal permissions for a specific function
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-uploads/*"
}
// Anti-pattern: unrestricted access
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
The CI/CD Pipeline: Your Software Supply Chain
Supply chain attacks have moved from theoretical to routine. The SolarWinds breach, the Codecov bash uploader incident, and the xz utils backdoor all demonstrated the same principle: compromise the build pipeline, and every deployment carries the payload.
Dependency management is a trust problem. Every npm install pulls code from thousands of maintainers whose security practices you can’t verify. Lock files pin versions, and tools like Dependabot, Snyk, and Socket.dev scan for known vulnerabilities and detect suspicious package behavior. These tools are imperfect, but they raise the cost of a supply chain attack substantially.
Static analysis (SAST) tools like Semgrep and CodeQL scan source code for vulnerability patterns before deployment, whilst dynamic analysis (DAST) tools like OWASP ZAP probe the running application for exploitable behavior. They catch different vulnerability classes, and a mature pipeline runs both. Build provenance through frameworks like SLSA and artifact signing then establishes a verifiable chain from source to deployed artifact.
CI secrets need the same scoping discipline as production secrets: restrict to specific environments and stages, use OIDC federation over long-lived tokens, and audit access regularly.
Monitoring and Incident Response: Security After Deployment
Security doesn’t end at deployment, and attackers will probe your systems regardless of how thorough your pre-production controls are. The gap between a contained incident and a catastrophic one usually comes down to detection speed.
Security logging needs to capture authentication events (successes and failures alike), authorization failures, input validation rejections, and anomalous access patterns. Structured JSON logs make these events queryable. A spike in 403 responses, logins from unfamiliar geographies, or unusual API call volumes against sensitive endpoints all warrant investigation.
Anomaly detection requires a baseline of normal behavior. Once you know what typical traffic looks like, deviations become visible: bursts of failed logins suggest credential stuffing, whilst unusual read volumes and scattered 404s across endpoints reveal reconnaissance or early-stage exfiltration.
Incident response readiness separates organizations that contain breaches from those that discover them months later. Runbooks for common scenarios, clear on-call escalation paths, and pre-drafted communication templates reduce the cognitive load during an active incident. For more on how incident response reveals organizational structure, see Incident Response Reveals Your Org Chart and The First Fire Tells You Everything.
Every incident is a design feedback loop. Patching the specific vulnerability closes the immediate gap, and asking why the architecture permitted that class of vulnerability in the first place, what structural change would prevent the entire category, turns incidents into durable improvements.
The Compounding Property
Security compounds: each layer done well reduces the blast radius when an adjacent layer fails, and the layers reinforce each other in ways that might not be obvious until something breaks. Least-privilege database access limits what a stolen credential can reach, and that containment compounds when CSP headers are also catching XSS payloads and the CI pipeline is flagging compromised dependencies upstream.
The systems that hold up under real pressure all assume breach at every layer and contain the damage through segmentation, graceful degradation, and enough observability to detect intrusions before incidents escalate. Every layer leaks, and architecture’s job is ensuring that a leak at any single layer doesn’t cascade into a flood. For how these principles extend to AI agent infrastructure specifically, see Security at Machine Speed.
Build a system where a failure at any single layer is absorbed by the layers around it, and you have something far more durable than any perimeter.