Updated
5 min read

What Happens When Security Lives Outside Your Code

Security That Doesn’t Know Your Code Is Guessing For a long time, running security outside the codebase was the only realistic option. Centralized

What Happens When Security Lives Outside Your Code

Security That Doesn’t Know Your Code Is Guessing

For a long time, running security outside the codebase was the only realistic option. Centralized systems could be added without changing application logic, shared across teams, and managed separately from product development. If you wanted protection, you put something in front of the application.

That approach works for blocking generic, volumetric abuse. But it also means security decisions are made without knowing what the code will actually do next. Requests are evaluated without context about user sessions, state, or business rules.

When security can’t see how an application behaves, it has no choice but to guess. It guesses which requests are safe, which users are suspicious, and which actions should be blocked. These guesses are based on broad patterns without the context of the specifics of your application. Sometimes that guess is right. Often it isn’t.

This post is about application-layer security: security controls that run inside your application code, with access to user metadata, state, and business logic. It explains why traditional perimeter tools like WAFs and proxies struggle with abuse patterns, and why in-code security enables more precise decisions.

The perimeter security model and its limits

Once security is separated from application code, it becomes a shared system. A single set of controls can protect many applications, apply the same rules everywhere, and be managed independently of product development. This model optimizes for organizational scale. Security teams define policies once and enforce them broadly. New applications inherit protection by default. Changes can be made without coordinating deployments or modifying code.

Traditional, perimeter-based tools like WAFs, proxies, and other network-layer services are built around these constraints. They evaluate traffic in a uniform way, looking for patterns that generalize across many applications. That generality is intentional.

The trade-off is accuracy and the risk of false positives. 

Perimeter tools see network packets, not actions. They don’t know what state a user is in or what an action will trigger once it reaches the code. They can’t see business rules, workflows, or resource cost. Without that context, perimeter tools are forced to rely on a narrow set of signals: request structure, headers, IP reputation, and known signatures. These are effective for blocking volumetric attacks, but they flatten application behavior into something generic.

As soon as abuse starts to resemble legitimate usage, this model breaks down. A request can look normal and still be harmful. A user can follow the protocol correctly while abusing the system at the application level. 

At that point, the limitation isn’t tuning or coverage. It’s that the most important security decisions depend on behavior the perimeter can’t see.

Why real security decisions happen inside application code

The most important security decisions don’t happen when a request arrives. They happen when the application decides what to do with it. That decision lives inside the code. It’s the point where a request becomes an action: creating a resource, triggering a workflow, consuming compute, or modifying state. Whether that action should be allowed depends on the context the perimeter never sees.

Only the application knows which routes are sensitive, which operations are expensive, and which sequences of actions are valid. Only the code can see user state, feature flags, internal limits, and business rules as they are actually enforced. This is where intent becomes clear. A request that looks harmless in isolation can be dangerous in context. A pattern that’s fine for one user or route can be abusive for another. Those distinctions are defined in application logic, not at the boundary.

When security decisions are made in code, they stop being guesses. They’re based on what the application is doing, not just on what the request looks like. That shift is what makes in-code security fundamentally different.

From guessing to informed decisions

Security systems outside the application are effective at filtering many classes of abuse. But when harmful behavior follows expected request patterns, its impact may only become clear once the application starts doing real work.

To compensate, teams add rules. Over time, those rules drift away from how the application actually behaves because they’re disconnected from code. Product changes land faster than security updates, and controls become either too permissive to be effective or too strict to be safe. Engineers end up debugging incidents without enough signal, trying to reconstruct intent from logs that were never designed to capture it.

This isn’t a tooling problem. It’s a framing problem. The question security tools are forced to answer is, “Is this request bad?” But that’s rarely the right question. The real question is, “Should this action be allowed here and now?” Answering that requires application knowledge: who the user is, what state they’re in, what this action will trigger, and how it fits into the broader workflow.

When security moves into code, that context becomes available. Security logic runs alongside business logic and evaluates actions, not just requests. Decisions are made with full visibility into state and behavior. The result? Less guesswork, fewer global rules trying to approximate intent, and more precise controls that evolve with the application through the same deploys that ship new features.

Why Arcjet is built this way

Arcjet Shield WAF is still a web application firewall, but like everything in Arcjet, it runs inside the application rather than at the perimeter, so security decisions are made with full application context.

Arcjet is designed around the idea that security is part of the application, not something bolted on around it. Instead of treating protection as an external system, Arcjet provides security logic that lives alongside your code.

Because this logic runs in the same environment as application code, it can reason about user sessions, state, and behavior directly, rather than inferring intent from network traffic alone. Security becomes something the application participates in, not just something it passes through.

All Arcjet protections follow this model. Security ships the same way the rest of the application does, so changes stay aligned with product behavior. There’s no separate system to keep in sync and no translation layer between how the application works and how it’s protected.

This approach doesn’t replace every other control - security works best in layers. Arcjet fills a gap that perimeter-first tools can’t: making informed decisions at the point where application behavior is defined.

Why Application Context Matters for Security

Applications are not just collections of endpoints, they encode intent, define which actions make sense, which sequences are valid, and which behavior is acceptable. When security operates without access to that intent, it is forced to approximate. So it can block obvious abuse, but it struggles with cases that look normal on the surface and cause harm through context.

Arcjet is built to close that gap. By running security logic in the same environment as application code, it can use the context that already exists: user sessions, state, and behavior. Decisions are based on what the application is actually doing, not just on what a request looks like.

That alignment makes security more resilient. As the application changes, Arcjet changes with it, because security ships the same way the rest of the code does. It stays in step with how the application works, instead of trying to infer it from the outside.

Subscribe by email

Get the full posts by email every week.