←Research
Researchsecurity11 min read

Seven authentication bypasses that keep shipping in 2025 and 2026: the same architectural antipatterns, rewritten in new frameworks

Modern frameworks keep reimplementing the same seven authentication bypass patterns. From hardcoded credentials to missing origin checks, the bugs are structural, not accidental, and the AI tooling boom is accelerating the cycle.

Authentication bypass is not a vulnerability class. It is an architectural design pattern, one that modern frameworks implement with the same reliability as the features they advertise.

Over the past eighteen months I have audited authentication layers in AI agent platforms, MCP servers, developer tooling and open-source dashboards. The same seven structural flaws appear repeatedly, each mapping to a CWE that MITRE catalogued years ago, most of them over a decade old. These are not novel attacks. They are not zero-days. They are design decisions that make authentication optional by default and hope the operator will fix it in production. Most operators do not.

This post catalogues the seven patterns, explains why they keep recurring and asks the uncomfortable question: why does an industry that knows OWASP A07 by heart keep shipping the same mistakes?

The seven patterns

Each pattern below is a distinct architectural choice that results in authentication bypass. They overlap in some implementations but are structurally separable. A project can exhibit several simultaneously.

1. Hardcoded default credentials (CWE-798 / CWE-1188)

The most common and the most inexcusable. The application ships with a default username and password, often documented in the README. The credential is identical across every installation. Anyone who reads the documentation can log in.

I found this in MCPHub, where every fresh installation created an admin account with the password admin123. The README included it. The quickstart guide included it. The Docker Compose file mapped the port externally by default. The fix generates a cryptographically random password per instance using crypto.randomBytes(18), but the window between the project's first release and the merged fix was months of every deployment sharing the same credential.

MCPHub is not unusual. This pattern appears in IoT dashboards, monitoring tools, database administration panels and, increasingly, in AI agent management interfaces where the speed from "git clone" to "running in production" is measured in minutes.

2. Missing origin validation on local daemons (CWE-346)

A service binds to 127.0.0.1 and assumes that locality implies trust. It opens a WebSocket or HTTP endpoint, accepts connections from any origin and exposes its full API surface to whatever happens to be running in the user's browser.

I found this in AIPex, where a MCP bridge daemon on port 9223 accepted WebSocket connections from any origin. A malicious web page could open a connection and invoke over thirty browser automation tools: creating tabs, capturing screenshots, executing scripts, simulating keyboard input. Browsers always attach an Origin header on WebSocket connections from web pages, and JavaScript cannot forge it. Checking that header is a thirty-line function. It was not present.

The assumption that localhost is a trust boundary has been wrong since browsers started making cross-origin requests, which is to say, it has been wrong for the entire history of the modern web. Yet every generation of developer tooling rebuilds it fresh.

3. Permissive CORS as the default (CWE-942)

Closely related to missing origin validation but distinct in mechanism. The server sets Access-Control-Allow-Origin: * or origin: true in its CORS configuration, turning what should be a cross-origin restriction into a cross-origin invitation.

MCPHub did this alongside its hardcoded credentials: fully permissive CORS with origin: true in Express, meaning any web page could make authenticated API requests to the management interface. The combination is particularly toxic. Hardcoded credentials alone require the attacker to know the endpoint. Permissive CORS alone requires the attacker to have valid credentials. Together, both barriers vanish.

This is not a configuration error. It is a deliberate default chosen because restrictive CORS causes development-time friction. The developer sets origin: true during prototyping, the application reaches users before the configuration is tightened and the permissive default becomes the production posture.

4. Environment variable credential exposure (CWE-214)

The application accepts API keys, tokens or passwords via environment variables, then leaks them through process listings, debug endpoints, error messages or CLI argument logging.

I documented this in gptme, where API keys passed as environment variables appeared in process argument lists visible to any user on the same system. The pattern is endemic in AI tooling because LLM API keys are expensive, long-lived and required by nearly every component. Projects treat environment variables as a secret store without understanding that the process table, /proc, crash dumps and container orchestration logs all have read access to that "store".

The fix is straightforward: read from files, use a secrets manager, or at minimum strip sensitive values from any surface that can be inspected. But the pattern persists because environment variables are the path of least resistance for twelve-factor application design, and most frameworks do nothing to prevent their subsequent exposure.

5. Authentication checks in the wrong layer (CWE-284)

The application has authentication, but it is implemented at the presentation layer rather than the business logic or data access layer. The API endpoints behind the UI have no authentication at all.

This manifests as: a login page that gates the frontend but does not protect the backend API; a middleware that checks tokens on some routes but not others; an authorisation decision made in JavaScript that runs in the browser. The server trusts that the client will only send requests the UI would permit, which is another way of saying the server does not check at all.

In agent frameworks, this pattern is accelerated by the fact that many tools are designed to be called programmatically by an LLM, not interactively by a human. The developer adds a UI with authentication for the human operator but leaves the tool-calling API, which has identical or greater privileges, completely unauthenticated.

6. Mutable trust anchors and implicit identity (CWE-345)

The system uses a mutable reference as proof of identity. A git tag, a package name, a DNS record, an unsigned manifest. The identity is asserted rather than verified, and anyone who can modify the reference can assume the identity.

I explored this at length in a previous post on supply chain authentication, where the same pattern appeared in Git tags, package registries and VS Code extension marketplaces. The recurring failure is that the reference (a version tag, a package name, a publisher identifier) is treated as immutable and authentic when it is neither.

In the authentication bypass context specifically, this pattern appears when an application trusts a token, cookie or header value because it is present, not because it has been cryptographically verified. The "authentication" is the existence of the value, not its validity. A request with Authorization: Bearer anything passes because the middleware checks for the header's presence without validating the token against a signing key.

7. Rate limiting as the only access control (CWE-307 / CWE-799)

The application relies on rate limiting to prevent brute-force attacks but implements authentication that is otherwise trivially bypassable. The rate limiter becomes the de facto access control, and a single index error, off-by-one or configuration mistake downgrades it from "slow to attack" to "no protection at all".

I found a variant of this in daily_stock_analysis, where the rate limiter was the primary barrier preventing unauthorised access. This is not what rate limiters are for. A rate limiter is a secondary control that slows down attacks against a primary authentication mechanism. When the rate limiter is the mechanism, any bypass of it, through header spoofing, IP rotation, logic errors or simple misconfiguration, grants immediate access.

Why these patterns recur

The seven patterns above are not bugs in the usual sense. They are not off-by-one errors or buffer overflows that require specific technical conditions to manifest. They are design decisions. Each one represents a moment where the developer chose the path that made the application work quickly over the path that made it work securely.

Three structural pressures drive this cycle.

Compressed development timescales. The AI tooling ecosystem rewards speed above everything. An MCP server or agent framework that reaches GitHub while the protocol is still novel captures early adopters. Security review, threat modelling and secure-by-default configuration take time. Time is the one resource these projects do not have.

Framework defaults that optimise for developer experience. Express sets permissive CORS because it reduces friction. WebSocket libraries accept all origins because restricting them requires understanding the same-origin policy. Docker maps ports externally because that is what makes the demo work. Each default is individually reasonable as a development convenience. In aggregate, they produce applications that are insecure by default at every layer.

The "configure in production" assumption. Nearly every project README includes a section titled "Security" or "Production Configuration" that instructs the operator to change the default password, restrict CORS origins, enable TLS and set proper access controls. This is a transfer of security responsibility from the developer to the operator, and it fails because most operators follow the quickstart guide, not the security hardening section. The README is not a security control.

What the CWE catalogue already knew

Every pattern in this list maps to a CWE that has existed for years. CWE-798 (hardcoded credentials) was published in 2010. CWE-346 (origin validation) has been in the catalogue since 2006. CWE-942 (permissive CORS) was formally added in 2019 but the underlying vulnerability class predates it by a decade.

The OWASP Top 10 has included "Identification and Authentication Failures" (currently A07:2021) in every revision since the project's inception. The knowledge is not missing. The tooling to detect these patterns exists. Static analysers flag hardcoded credentials. Dynamic scanners test for permissive CORS. Linters warn about missing authentication middleware.

The problem is not detection. The problem is that the economics of open-source development make insecure defaults cheaper than secure ones. Generating a random password per installation requires six lines of code and one design decision. Shipping admin123 requires zero of both. When the measure of success is GitHub stars and adoption velocity, the six-line version loses.

The AI tooling acceleration

The boom in AI agent frameworks, MCP servers and LLM tooling has accelerated this cycle to a degree that is worth calling out specifically.

Between late 2024 and early 2026, the MCP ecosystem went from specification draft to hundreds of server implementations. Many of these were written by developers building their first networked service, using LLM code generation to scaffold the project and publishing to GitHub the same day. The resulting code reliably exhibits patterns 1, 2, 3 and 5 from the list above because those are the patterns that LLM-generated boilerplate produces. The model generates code that works. "Works" and "is secure" are different requirements, and only one of them is visible in a demo.

The projects that avoid these patterns are the ones with maintainers who have prior experience shipping networked services. They know to generate random credentials, validate origins, restrict CORS and authenticate at the API layer. This is not arcane knowledge. It is baseline web security circa 2015. But the influx of new developers building agent tooling without that background has reset the clock, and the seven patterns are being reimplemented as if they were being discovered for the first time.

Structural fixes versus individual patches

I fix these individually when I find them. A PR to generate random credentials. A PR to validate the Origin header. A PR to restrict CORS. Each fix is correct, necessary and insufficient.

It is insufficient because the fix addresses the symptom in one project while the structural pressure that produced it remains in every other project. The next MCP server will ship with admin/admin123 because Express does not generate a random password and the LLM that scaffolded the project was trained on tutorials that hardcoded one.

The leverage point is not the individual project. It is the framework defaults, the scaffolding tools and the LLM training data. If Express's cors() middleware required an explicit origin list instead of accepting a boolean, permissive CORS would require effort rather than being the default. If WebSocket libraries rejected connections without a validated Origin header by default, CWE-346 would require opting out of security rather than opting in. If docker-compose.yml templates bound ports to 127.0.0.1 instead of 0.0.0.0, the network exposure of development defaults would shrink immediately.

None of these changes are technically difficult. They are politically difficult because they trade developer convenience for user security, and in open-source projects measured by adoption rate, that trade has a visible cost and an invisible benefit.

Where this leaves us

The seven patterns are not going away. They have survived fifteen years of OWASP guidance, a decade of automated scanning and an entire generation of secure development lifecycle toolkits. They persist because the incentive structure that produces them has not changed.

What has changed is the blast radius. A hardcoded credential on an IoT dashboard endangered one network. A hardcoded credential on an MCP server that controls an AI agent's access to external tools endangers every system that agent can reach. The same architectural flaw, amplified by the capabilities of the thing it fails to protect.

The cheapest authentication bypass is still the one that shipped as a feature.

Newsletter

One email a week. Security research, engineering deep-dives and AI security insights - written for practitioners. No noise.