September 23, 2026
Negative-Space Security: How Missing Browser Headers Led to CVE-2026โ49993 in Nuxt
Sometimes the most interesting security input is not a malicious value. It is no value at all.

By Berkan SAL
7 min read
Sometimes the most interesting security input is not a malicious value. It is no value at all.
Security testing naturally focuses on values.
What happens if an attacker controls Origin?
Can Referer be spoofed?
Can Sec-Fetch-Site contain an unexpected value?
What if a parser receives malformed input?
Those are all useful questions. But while reviewing a same-origin protection in Nuxt's development server, I ran into a different class of problem:
What happens when the security signal simply does not exist?
That question eventually resulted in CVE-2026โ49993 / GHSA-x6qj-4h56โ5rj5, an incomplete-fix bypass affecting Nuxt's webpack and rspack development servers.
GitHub credits me, Berkan SAL (@Uhudsavasindankacanokcu2), for reporting the issue through the Vercel Open Source HackerOne program. The advisory assigns it CVSS 4.0 5.9 (Moderate) with high confidentiality impact.
But the CVE itself is only half of what interested me.
The more reusable lesson is a testing pattern I think of as negative-space security:
Do not only test what happens when security metadata contains attacker-controlled values. Test what happens when the metadata disappears entirely.
The Problem Nuxt Was Trying to Solve
Development servers are often treated as local tooling, but the browser creates an unusual security boundary around them.
Imagine a developer running:
nuxt dev --host 0.0.0.0nuxt dev --host 0.0.0.0The development server is now reachable from the local network.
If the developer subsequently visits a malicious website, that website may try to make the browser interact with services running on the developer's machine or LAN.
This is particularly relevant to development servers because their generated JavaScript can contain information a developer never intended to expose to another origin.
Nuxt had already dealt with variants of this problem before CVE-2026โ49993.
An earlier vulnerability showed that webpack/rspack development output could be loaded cross-origin by a classic <script> element. Another subsequent fix attempted to enforce a same-origin policy using browser request metadata.
Conceptually, the logic looked roughly like this:
if Sec-Fetch-Site exists:
allow only same-origin or direct navigation
otherwise:
inspect Origin or Referer
if Origin/Referer exists:
compare its host with Host
otherwise:
allowif Sec-Fetch-Site exists:
allow only same-origin or direct navigation
otherwise:
inspect Origin or Referer
if Origin/Referer exists:
compare its host with Host
otherwise:
allowThat last state was important.
The implementation could not simply reject every request lacking browser security headers, because legitimate requests may come from environments such as command-line clients, HMR machinery, or direct navigation.
So absence had been treated as a compatibility condition.
And compatibility created the interesting security state.
The Missing-Headers State
The previous fix used three pieces of metadata:
Sec-Fetch-Site
Origin
RefererSec-Fetch-Site
Origin
RefererAt first glance, this feels reasonably redundant.
If one signal disappears, perhaps another one identifies where the request came from.
But security properties should not be inferred from how many checks exist.
The real question is:
Can an attacker reach a state in which every check loses its evidence at the same time?
In this case, yes.
The Nuxt advisory documents three browser behaviors that combine to create exactly that state:
Sec-Fetch-Sitemay be absent when targeting an HTTP resource on a non-loopback, non-potentially-trustworthy address.- A classic non-CORS
<script>request does not necessarily carryOrigin. - The referring page can suppress
Refererusing a no-referrer policy.
Individually, none of these behaviors is particularly surprising.
Together, however, they produce:
Sec-Fetch-Site: <absent>
Origin: <absent>
Referer: <absent>Sec-Fetch-Site: <absent>
Origin: <absent>
Referer: <absent>The important property is not a malformed header.
It is the absence vector:
(None, None, None)(None, None, None)And the previous protection interpreted that state as allowed.
Why This Is Different From Spoofing a Header
A common security-review mindset is:
What attacker-controlled value makes this condition return true?What attacker-controlled value makes this condition return true?For example:
if (origin === trustedOrigin) {
allow()
}if (origin === trustedOrigin) {
allow()
}We naturally test unusual values, encoding differences, parser inconsistencies, null bytes, capitalization, alternate URLs, and normalization problems.
But consider a different structure:
if (securitySignal) {
return validate(securitySignal)
}
return trueif (securitySignal) {
return validate(securitySignal)
}
return trueThere may be no malicious value required.
The interesting input is:
undefinedundefinedThis is why I find the vulnerability more useful as a research pattern than merely as a Nuxt bug.
The bypass existed in the control flow surrounding the security evidence rather than inside the validation of that evidence.
Turning Browser Semantics Into an Exploit Condition
The request shape could be produced with an ordinary cross-origin script load using a suppressed referrer:
<meta name="referrer" content="no-referrer">
<script
src="http://VICTIM_LAN_IP:3000/_nuxt/app.js"
referrerpolicy="no-referrer">
</script><meta name="referrer" content="no-referrer">
<script
src="http://VICTIM_LAN_IP:3000/_nuxt/app.js"
referrerpolicy="no-referrer">
</script>The official advisory describes how such a request can reach a Nuxt development server while lacking all three signals used by the protection.
Once the generated webpack bundle executes in the attacker's page, compiled module functions can be inspected through the global webpack chunk structure, including via function stringification.
The result is a confidentiality issue: built source can become visible to a malicious page under the affected network and browser conditions.
The attack specifically concerned webpack/rspack development servers reachable through a non-loopback bind such as --host; Nuxt's default Vite builder was not affected.
The Patch Is More Interesting Than It Looks
The final fix did not add a fourth header.
Instead, it changed what absence means.
The patched logic essentially says:
const initiator = origin || referer
if (!secFetchSite && !initiator) {
return isLoopbackHost(host)
}const initiator = origin || referer
if (!secFetchSite && !initiator) {
return isLoopbackHost(host)
}Nuxt's PR #35200 changed the missing-all-signals state so that it is accepted only when the development server is loopback-bound.
A request with:
Sec-Fetch-Site = absent
Origin = absent
Referer = absent
Host = localhostSec-Fetch-Site = absent
Origin = absent
Referer = absent
Host = localhostcan remain compatible.
But:
Sec-Fetch-Site = absent
Origin = absent
Referer = absent
Host = 192.168.x.xSec-Fetch-Site = absent
Origin = absent
Referer = absent
Host = 192.168.x.xis rejected.
The patch also added explicit tests for localhost, 127.0.0.1, ::1, LAN addresses, missing Host, and the no-security-signals condition itself.
The affected branches were fixed in webpack/rspack builder versions 4.4.7 and 3.21.7.
This is a better security model because it does not attempt to magically recover provenance after all provenance signals have disappeared.
Instead, it asks whether the remaining environment is intrinsically constrained enough to tolerate ambiguity.
From One CVE to a General Testing Method
The part I want to keep from this research is not the specific browser trick.
It is the methodology.
Suppose a security decision uses signals:
S1, S2, S3S1, S2, S3Most testing explores something like:
S1 = malicious
S2 = malicious
S3 = maliciousS1 = malicious
S2 = malicious
S3 = maliciousor combinations of malformed values.
But there is another state space:
S1 = โ
S2 = valid
S1 = โ
S2 = โ
S3 = valid
S1 = โ
S2 = โ
S3 = โ
S1 = โ
S2 = valid
S1 = โ
S2 = โ
S3 = valid
S1 = โ
S2 = โ
S3 = โ
The interesting question becomes:
What authority does the system grant as evidence monotonically disappears?
This can expose an entire family of fail-open conditions.
The Security Signal Lattice
One way I now like to reason about these systems is as a small lattice of evidence.
Imagine a request normally has:
Authentication
Origin metadata
Network identity
Request provenanceAuthentication
Origin metadata
Network identity
Request provenanceStart removing evidence.
[A, O, N, P]
[A, O, N, -]
[A, O, -, -]
[A, -, -, -]
[-, -, -, -][A, O, N, P]
[A, O, N, -]
[A, O, -, -]
[A, -, -, -]
[-, -, -, -]At each transition, ask:
Does privilege stay the same, decrease, or unexpectedly increase?
Ideally, uncertainty should reduce what the system is willing to do.
A suspicious pattern is:
more evidence โ validation
less evidence โ fallback
no evidence โ allowmore evidence โ validation
less evidence โ fallback
no evidence โ allowThat is a security inversion.
The less the application knows, the more permissive it becomes.
Where Else I Would Look for This Pattern
The idea applies far beyond Origin and Referer.
Reverse proxies
Applications frequently trust:
X-Forwarded-For
X-Forwarded-Host
X-Forwarded-Proto
ForwardedX-Forwarded-For
X-Forwarded-Host
X-Forwarded-Proto
ForwardedResearch often focuses on forging them.
Also test what happens when proxies selectively remove them.
Does the backend fall back to a more trusted interpretation?
Authentication middleware
Consider:
Authorization header
session cookie
identity injected by upstream proxyAuthorization header
session cookie
identity injected by upstream proxyIf the upstream identity header disappears, does the service reject the request?
Or does it quietly switch into a legacy authentication mode?
Cloud infrastructure
Security decisions may depend on:
instance identity
metadata headers
service-account claims
mTLS identity
network sourceinstance identity
metadata headers
service-account claims
mTLS identity
network sourceAgain, missing evidence deserves its own test cases.
Webhooks
A receiver might support:
signature present โ validate signature
signature absent โ legacy unsigned webhooksignature present โ validate signature
signature absent โ legacy unsigned webhookThat second branch is often more interesting than breaking the signature algorithm.
AI agents and tool execution
This pattern may become increasingly relevant in agent systems.
Imagine a tool call carrying:
user identity
conversation identity
approval state
provenance
policy decisionuser identity
conversation identity
approval state
provenance
policy decisionTesting only forged provenance values is incomplete.
We should also ask:
What does the executor do when provenance information fails to propagate between agents?
A missing security context should not silently become an unrestricted context.
A Practical Negative-Space Checklist
When I encounter a security-sensitive branch, I now want to enumerate four states for every signal:
1. Expected value
2. Explicitly hostile value
3. Malformed / ambiguous value
4. Missing value1. Expected value
2. Explicitly hostile value
3. Malformed / ambiguous value
4. Missing valueThen I look at combinations.
For three signals, do not stop at:
A bad
B good
C goodA bad
B good
C goodTry:
A missing
B good
C good
A missing
B missing
C good
A missing
B missing
C missingA missing
B good
C good
A missing
B missing
C good
A missing
B missing
C missingAnd do not simulate these states only by manually deleting HTTP headers in a proxy.
That can create impossible requests.
The stronger question is:
Can the real platform legitimately generate this state?
For CVE-2026โ49993, the useful part was connecting three legitimate browser behaviors into one security-relevant absence state.
That distinction matters.
A theoretical missing-header condition is a code-review observation.
A browser-reachable missing-header condition is a vulnerability.
Incomplete Fixes Are Especially Good Targets
There is another research lesson here.
CVE-2026โ49993 was an incomplete fix for an earlier Nuxt advisory.
Security patches provide unusually valuable information to researchers because they reveal the maintainers' security model.
A patch effectively says:
We believe the dangerous state is X.
We believe checking Y prevents X.We believe the dangerous state is X.
We believe checking Y prevents X.That gives the researcher two questions:
Can X exist without Y?
Can Y disappear while X remains dangerous?Can X exist without Y?
Can Y disappear while X remains dangerous?Instead of immediately hunting for another unrelated bug, it is often worth treating a security patch as a hypothesis and trying to falsify it.
Look at:
- assumptions introduced by the fix,
- compatibility fallbacks,
- parser boundaries,
- missing-value behavior,
- alternate protocols,
- alternate browser primitives,
- different network contexts,
- state combinations not represented by regression tests.
The goal is not simply to bypass a patch.
The goal is to understand the security invariant the patch is trying to restore, then determine whether that invariant actually holds.
The Broader Lesson
The bug that became CVE-2026โ49993 was small in terms of code.
The reasoning behind it is more general.
Security systems increasingly make decisions from ambient context:
headers
cookies
browser metadata
proxy metadata
identity claims
network properties
execution provenanceheaders
cookies
browser metadata
proxy metadata
identity claims
network properties
execution provenanceDevelopers usually think about those signals when they are present.
Attackers should think about their entire lifecycle:
Who creates the signal?
When is it omitted?
Who can suppress it?
Can an intermediary strip it?
Does another component recreate it?
What happens when parsing fails?
What happens when every fallback is exhausted?Who creates the signal?
When is it omitted?
Who can suppress it?
Can an intermediary strip it?
Does another component recreate it?
What happens when parsing fails?
What happens when every fallback is exhausted?The last question is often neglected:
What does the system believe when it has no evidence left?
In the vulnerable Nuxt logic, the answer was effectively:
allowallowAfter the fix, uncertainty on a network-reachable development server becomes:
denydenyThat is the change that matters.
Conclusion
CVE-2026โ49993 is a useful reminder that security bypasses do not always require crafting increasingly exotic input.
Sometimes the path is the opposite.
Remove input.
Remove metadata.
Remove assumptions one by one.
Then observe what remains.
I have started treating this as negative-space security testing: systematically investigating security decisions under the absence of the contextual signals they normally depend on.
For researchers, a useful question to add to the standard checklist is:
What does this security control do when every signal it expects disappears simultaneously?
Occasionally, the empty state is the most privileged input of all.
Disclosure / References
The vulnerability discussed here is publicly disclosed as CVE-2026โ49993 / GHSA-x6qj-4h56โ5rj5. GitHub's Nuxt advisory credits Berkan SAL (@Uhudsavasindankacanokcu2) as a reporter through the Vercel Open Source HackerOne program and documents the affected versions, exploitation conditions, remediation, and patch.
The issue was an incomplete fix for GHSA-6m52-m754-pw2g / CVE-2026โ45670, whose earlier remediation introduced the Origin / Referer fallback subsequently analyzed here.