September 23, 2026
Security Context Dies at Boundaries
Five real-world findings and a reusable method for finding vulnerabilities where trust gets lost between components.

By Berkan SAL
11 min read
Five real-world findings and a reusable method for finding vulnerabilities where trust gets lost between components.
After spending enough time reviewing security-sensitive systems, individual vulnerabilities start looking less individual.
The technologies change.
One bug is in a browser-facing development server.
Another is in a WebSocket stream.
Another appears in an identity platform.
Another lives inside SSRF protection.
Another is buried in a DeFi oracle calculation.
Yet the same structural failure keeps appearing:
A security property exists on one side of a boundary, but does not survive the transition to the other side.
Identity disappears.
Provenance disappears.
Network semantics become strings.
Graph relationships become local booleans.
Independent measurements collapse into the same value.
The security check may still exist.
But the context that gave the check meaning is gone.
I have started treating this as a distinct research pattern:
Boundary-State Auditing
The idea is simple:
Whenever data, authority, identity, or security evidence crosses a boundary, ask what gets lost, rewritten, merged, inherited, or silently defaulted.
This article develops that idea using five security findings I worked on:
- Nuxt โ CVE-2026โ49993
- Nezha โ GHSA-q6xx-5vr8-p898
- authentik โ GHSA-h6c5-mpvq-j4jc
- Formbricks โ ENG-1326 / PR #8946
- Olas โ Code4rena H-02
They look unrelated.
I do not think they are.
Security Is Usually Correct Locally
The dangerous thing about boundary bugs is that each component often looks reasonable in isolation.
Consider a system with:
Component A
โ
Component BComponent A
โ
Component BComponent A may correctly establish some security fact:
user = Aliceuser = Aliceor:
request came from same originrequest came from same originor:
price A is the current pool priceprice A is the current pool priceor:
this IP belongs to network Xthis IP belongs to network XThen information crosses a boundary.
And Component B receives a reduced representation:
stream UUIDstream UUIDor:
missing headersmissing headersor:
string prefixstring prefixor:
uint160uint160The security meaning existed before the transformation.
Afterward, only the representation remains.
That is where interesting bugs begin.
Case 1 โ Browser โ Server
CVE-2026โ49993
The first example came from Nuxt's webpack/rspack development server.
The protection attempted to determine whether a request was same-origin using several browser signals:
Sec-Fetch-Site
Origin
RefererSec-Fetch-Site
Origin
RefererThis seems like redundancy.
If one signal is missing, another may still identify the initiator.
The interesting condition appeared when the browser crossed several behavioral boundaries simultaneously.
A request could arrive with:
Sec-Fetch-Site = absent
Origin = absent
Referer = absentSec-Fetch-Site = absent
Origin = absent
Referer = absentThe previous logic treated the exhaustion of all security evidence as a compatibility case.
Conceptually:
if (securityMetadataExists) {
validate();
}
return true;if (securityMetadataExists) {
validate();
}
return true;The attacker did not need to forge trusted metadata.
The attacker needed the metadata to disappear.
The vulnerable transition was:
Browser security context
โ
HTTP request
โ
context missingBrowser security context
โ
HTTP request
โ
context missingThe server still had a request.
It no longer had enough evidence about where the request came from.
Yet the absence of evidence became permission.
The patch changed that boundary condition: when all origin signals are missing, requests are accepted only when the development server is loopback-bound.
The broader pattern is:
When provenance is optional in transit, audit the state produced when all provenance disappears.
Case 2 โ Creation โ Consumption
GHSA-q6xx-5vr8-p898
Nezha exposed another version of the same structural problem.
A user could create a terminal or file-manager stream.
At creation time, authorization existed:
Alice
โ authorized for
Server X
โ creates
Stream SAlice
โ authorized for
Server X
โ creates
Stream SBut the temporary stream stored essentially:
SSnot:
S belongs to AliceS belongs to AliceLater, another request attached to:
/ws/terminal/S/ws/terminal/SThe application could answer:
Does S exist?Does S exist?but it had lost the context necessary to answer:
Does S belong to the current principal?Does S belong to the current principal?The transition looked like:
Alice + authority + resource
โ
stream creation
โ
UUIDAlice + authority + resource
โ
stream creation
โ
UUIDA rich security object had been compressed into an identifier.
The UUID survived.
The principal did not.
This is what I described as a principal-unbound capability.
The fix restored the missing relationship:
Stream
โโโ creatorUserIDStream
โโโ creatorUserIDNow the second phase can reconstruct the original security invariant.
The broader pattern is:
Whenever authorization occurs in one request and an operation is consumed in another, trace which parts of the original principal context survive.
Case 3 โ Object โ Graph
GHSA-h6c5-mpvq-j4jc
authentik exposed a different boundary.
Here the information was not lost between requests.
It was lost between levels of abstraction.
A delegated administrator might legitimately have permission to modify:
Group AGroup AA local authorization decision therefore asks:
Can Alice manage Group A?Can Alice manage Group A?But groups exist in a hierarchy.
Roles can be assigned.
Privileges can be inherited.
Membership creates new edges.
So the real security system looks more like:
Alice
โ manages
Group A
โ parent / role / membership
Group B
โ
Superuser privilegeAlice
โ manages
Group A
โ parent / role / membership
Group B
โ
Superuser privilegeThe immediate object can be unprivileged while its reachable graph is highly privileged.
The boundary here is:
local object state
โ
graph-effective statelocal object state
โ
graph-effective stateChecking only:
group.is_superusergroup.is_superusercan miss:
group โ parent โ superuser groupgroup โ parent โ superuser groupLikewise, permission to edit a group does not necessarily imply permission to connect that group to a powerful role.
The vulnerable system reasoned locally.
The privilege system behaved transitively.
The broader pattern is:
Whenever security relationships form a graph, audit the privilege delta of the resulting graph โ not only the object being modified.
Case 4 โ Representation โ Network Semantics
Formbricks ENG-1326 / PR #8946
Formbricks' webhook SSRF protection showed yet another boundary mismatch.
The security policy was about networks:
224.0.0.0/4
fe80::/10
100.64.0.0/10224.0.0.0/4
fe80::/10
100.64.0.0/10But parts of the implementation represented those networks using regular expressions and string prefixes.
For example, conceptually:
/^224\.//^224\./was documented as:
224.0.0.0/4224.0.0.0/4But those are not equivalent.
The regex describes:
224.0.0.0/8224.0.0.0/8while the CIDR covers:
224.0.0.0
through
239.255.255.255224.0.0.0
through
239.255.255.255Likewise:
startsWith("fe80:")startsWith("fe80:")does not represent the full semantics of:
fe80::/10fe80::/10The policy lived in network space.
The implementation lived in string space.
The transition was:
network semantic object
โ
text representation
โ
security decisionnetwork semantic object
โ
text representation
โ
security decisionInformation was lost during representation.
The eventual remediation moved the classifier toward actual CIDR semantics using Node's net.BlockList.
The broader pattern is:
If a security property belongs to a structured semantic domain, audit every place it is reduced to text.
Strings do not inherently understand networks.
Just as they do not inherently understand paths, origins, domains, identities, or authorization.
Case 5 โ Measurement โ Decision
Olas Code4rena H-02
The Olas finding demonstrates perhaps the most subtle form.
A TWAP deviation check needed two independent values:
spot pricespot priceand:
historical TWAPhistorical TWAPThe security property exists because those measurements come from different temporal states.
Conceptually:
current market
โ
spot
historical market
โ
TWAP
spot vs TWAP
โ
security decisioncurrent market
โ
spot
historical market
โ
TWAP
spot vs TWAP
โ
security decisionBut a variable overwrite caused the value representing the spot price to be replaced with the TWAP-derived value.
The program still had two variables.
The code still had a comparison.
But semantically:
TWAP
โโโโ value A
โโโโ value BTWAP
โโโโ value A
โโโโ value BThe boundary between two independent measurements had collapsed.
So:
A vs BA vs Bbecame:
X vs XX vs Xand:
deviation = 0deviation = 0The security check ran successfully.
It simply had no causal connection to market manipulation anymore.
The broader pattern is:
Whenever a security control compares independent observations, trace their provenance until the final decision and verify that independence survives.
Five Bugs, Five Things That Disappeared
Put these findings side by side.
Nuxt
Lost:
request provenancerequest provenanceBoundary:
browser โ HTTP serverbrowser โ HTTP serverNezha
Lost:
principal ownershipprincipal ownershipBoundary:
resource creation โ resource consumptionresource creation โ resource consumptionauthentik
Lost:
transitive privilege contexttransitive privilege contextBoundary:
local object โ authorization graphlocal object โ authorization graphFormbricks
Lost:
network semanticsnetwork semanticsBoundary:
CIDR/network โ textual representationCIDR/network โ textual representationOlas
Lost:
measurement independencemeasurement independenceBoundary:
data acquisition โ security comparisondata acquisition โ security comparisonDifferent software.
Same family of reasoning failure.
Security Context Compression
A useful way to think about this is security context compression.
Systems constantly compress rich objects into simpler representations.
For example:
Authenticated principal
โ user ID
Authorized terminal session
โ UUID
Network
โ string
Privilege graph
โ boolean flag
Verified measurement
โ integerAuthenticated principal
โ user ID
Authorized terminal session
โ UUID
Network
โ string
Privilege graph
โ boolean flag
Verified measurement
โ integerCompression is necessary.
Software cannot carry the entire universe of context everywhere.
The problem appears when the discarded information was necessary to preserve the security invariant.
Mathematically, imagine:
RichState = RRichState = Rand a transformation:
f(R) = rf(R) = rIf two security-distinct states:
R1 โ R2R1 โ R2collapse into the same representation:
f(R1) = f(R2)f(R1) = f(R2)then downstream code may no longer be able to distinguish safe from unsafe behavior.
That is an extremely interesting audit boundary.
Ask What Became Indistinguishable
This gives us a useful vulnerability-research question:
Which two states that should have different security outcomes become indistinguishable after this transformation?
For Nuxt:
legitimate request with missing metadatalegitimate request with missing metadataand:
cross-origin malicious request with suppressed metadatacross-origin malicious request with suppressed metadatacould become:
no security headersno security headersFor Nezha:
Alice using stream SAlice using stream Sand:
Bob using stream SBob using stream Scould become:
valid UUID Svalid UUID SFor an authorization graph:
ordinary child groupordinary child groupand:
child group that reaches superuser privilegechild group that reaches superuser privilegemay both locally appear:
is_superuser = falseis_superuser = falseFor an SSRF classifier:
fe80::1fe80::1and:
fe9f::1fe9f::1may differ under a textual prefix despite sharing the same relevant network classification.
For Olas:
spot = manipulated
TWAP = historicalspot = manipulated
TWAP = historicalcollapsed into:
TWAP vs TWAPTWAP vs TWAPSecurity bugs often hide inside these equivalence classes.
Boundary-State Auditing
This suggests a systematic methodology.
When reviewing a security-sensitive system, identify every important boundary.
Examples:
browser โ backend
frontend โ API
API โ worker
request 1 โ request 2
tenant โ shared service
parser โ consumer
string โ structured object
object โ graph
oracle โ protocol
agent โ tool
tool โ external service
sandbox โ hostbrowser โ backend
frontend โ API
API โ worker
request 1 โ request 2
tenant โ shared service
parser โ consumer
string โ structured object
object โ graph
oracle โ protocol
agent โ tool
tool โ external service
sandbox โ hostThen perform the following analysis.
Step 1 โ Write the Security Invariant
Before looking for bypasses, state what must remain true.
Examples:
Only the creator may attach to this stream.
Cross-origin pages must not read development output.
A delegated administrator must not create greater privilege than they possess.
Webhook requests must not reach internal infrastructure.
Liquidity operations must stop when spot price deviates too far from TWAP.Only the creator may attach to this stream.
Cross-origin pages must not read development output.
A delegated administrator must not create greater privilege than they possess.
Webhook requests must not reach internal infrastructure.
Liquidity operations must stop when spot price deviates too far from TWAP.If you cannot articulate the invariant, it is difficult to know which context matters.
Step 2 โ Identify the Evidence Supporting It
For each invariant, determine which information makes the decision possible.
Example:
Only creator may attachOnly creator may attachrequires:
currentUser
streamOwnercurrentUser
streamOwnerSame-origin protection may require:
Sec-Fetch-Site
Origin
Referer
Host
network bindingSec-Fetch-Site
Origin
Referer
Host
network bindingTWAP protection requires:
spot measurement
historical referencespot measurement
historical referenceGraph authorization requires:
actor permissions
target node
reachable privilege edgesactor permissions
target node
reachable privilege edgesThese are the security inputs.
Step 3 โ Follow Them Across Boundaries
Now trace every transformation.
For example:
current user
โ
session middleware
โ
stream creation
โ
UUID
โ
WebSocket attachmentcurrent user
โ
session middleware
โ
stream creation
โ
UUID
โ
WebSocket attachmentAsk at each step:
Does user identity still exist?Does user identity still exist?For SSRF:
URL
โ
URL parser
โ
hostname
โ
DNS
โ
address
โ
family classification
โ
CIDR classification
โ
socketURL
โ
URL parser
โ
hostname
โ
DNS
โ
address
โ
family classification
โ
CIDR classification
โ
socketAsk:
Does the destination retain the same meaning at every layer?Does the destination retain the same meaning at every layer?Step 4 โ Look for Six Boundary Failures
I currently find six transformations particularly interesting.
1. Drop
Security metadata disappears.
Origin โ absentOrigin โ absent2. Rewrite
A value changes semantic meaning.
spotPrice variable โ TWAP pricespotPrice variable โ TWAP price3. Compress
A rich object becomes a weaker identifier.
authorized stream โ UUIDauthorized stream โ UUID4. Inherit
Privilege arrives indirectly.
child group โ parent โ adminchild group โ parent โ admin5. Reinterpret
Two components parse the same representation differently.
validator IP semantics โ network stack semanticsvalidator IP semantics โ network stack semantics6. Fallback
Failure of the security mechanism selects a weaker mode.
TWAP unavailable โ trust spotTWAP unavailable โ trust spotThese six operations are excellent places to hunt.
Step 5 โ Test the Empty State
A large number of security bugs hide in absence.
Ask:
What if identity is missing?
What if the header is missing?
What if the role is missing?
What if the oracle fails?
What if the type cannot be classified?
What if there is no history?What if identity is missing?
What if the header is missing?
What if the role is missing?
What if the oracle fails?
What if the type cannot be classified?
What if there is no history?Then determine whether the system behaves:
fail closedfail closedor:
fall back to weaker assumptionsfall back to weaker assumptionsThe empty state deserves first-class test coverage.
Step 6 โ Test the Alias State
Another powerful test is aliasing.
Can two supposedly distinct security values become the same thing?
Examples:
expected == attacker-controlled
spot == TWAP
resource ID == credential
tenant ID == caller-supplied ID
validated path object == original unsafe stringexpected == attacker-controlled
spot == TWAP
resource ID == credential
tenant ID == caller-supplied ID
validated path object == original unsafe stringSecurity comparisons depend on meaningful separation.
Aliasing destroys that separation.
Step 7 โ Test the Transitive State
For graph-like systems, direct relationships are not enough.
Ask:
What becomes reachable?What becomes reachable?Not:
What changed directly?What changed directly?A mutation may create:
User โ GroupUser โ Groupwhich creates:
User โ Group โ Parent โ Role โ AdminUser โ Group โ Parent โ Role โ AdminThe dangerous state can be multiple hops away.
Step 8 โ Prove the Guard Can Reject
This is one of my favorite checks.
Take every important security mechanism and ask:
Can I construct a realistic hostile state that causes this check to deny the operation?
A TWAP guard should demonstrably reject a manipulated spot price.
An authorization check should demonstrably reject the wrong user.
An SSRF classifier should reject addresses throughout the entire forbidden CIDR, including boundaries.
A same-origin guard should reject a browser-reachable cross-origin request.
If you cannot make the protection fire, you may have discovered something important.
Security Controls Need Causal Influence
A security control should have authority over the protected action.
Conceptually:
hostile state
โ
security observation
โ
decision
โ
dangerous operationhostile state
โ
security observation
โ
decision
โ
dangerous operationIf hostile changes do not affect the decision:
hostile state changes
โ
decision stays constanthostile state changes
โ
decision stays constantthen the control may be disconnected.
Likewise, if the decision correctly rejects the operation but side effects already occurred before authorization, the control is too late.
A good security review therefore follows the entire chain:
source
โ representation
โ transformation
โ policy
โ decision
โ side effectsource
โ representation
โ transformation
โ policy
โ decision
โ side effectnot merely the if statement in the middle.
Boundaries in Modern AI Systems
I think this methodology becomes especially relevant for AI agents.
Agent infrastructure contains boundaries everywhere:
User
โ
Agent
โ
Planner
โ
Tool
โ
Connector
โ
Credential
โ
External systemUser
โ
Agent
โ
Planner
โ
Tool
โ
Connector
โ
Credential
โ
External systemSecurity context can disappear at any transition.
Examples:
Approval context
User approved operation XUser approved operation Xbecomes:
tool call IDtool call IDDoes the executor still know exactly what was approved?
Principal context
User A started agent run RUser A started agent run Rbecomes:
run_id = UUIDrun_id = UUIDCan User B attach to R?
Sanitization boundary
policy engine validates sanitized argumentspolicy engine validates sanitized argumentsbut:
executor receives original argumentsexecutor receives original argumentsCredential graph
User can edit Agent A
Agent A can attach Tool B
Tool B carries admin credential CUser can edit Agent A
Agent A can attach Tool B
Tool B carries admin credential CCan editing the agent make credential C reachable?
Provenance loss
trusted system instruction
untrusted retrieved text
user input
tool outputtrusted system instruction
untrusted retrieved text
user input
tool outputmay all eventually become:
plain context tokensplain context tokensThe entire field is full of security-context compression.
A Boundary Audit Table
For complicated systems, I like the idea of maintaining something like:
BoundaryContext BeforeRepresentation AfterSecurity QuestionBrowser โ serverorigin provenanceHTTP headersWhat if headers disappear?Session creation โ WebSocketprincipal + authorityUUIDIs the UUID bound to creator?Group โ hierarchylocal permissionsgraph edgesWhat privilege becomes reachable?URL โ socketnetwork destinationtext/parser outputDoes interpretation change?Oracle โ guardindependent measurementsnumeric variablesDid provenance collapse?Agent โ toolapproval + principaltool requestDoes authority survive delegation?
This makes invisible assumptions visible.
And assumptions are usually where the interesting research lives.
Security Bugs Often Live Between Correct Components
This is perhaps the most useful conclusion.
Component A can be correct.
Component B can be correct.
The transformation between them can still be unsafe.
For example:
URL parser is correct.
CIDR library is correct.
But the application's conversion between them is wrong.URL parser is correct.
CIDR library is correct.
But the application's conversion between them is wrong.Or:
initial authorization is correct.
WebSocket implementation is correct.
But ownership is lost between creation and attachment.initial authorization is correct.
WebSocket implementation is correct.
But ownership is lost between creation and attachment.Or:
oracle is correct.
deviation math is correct.
But a variable overwrite destroys independence between inputs.oracle is correct.
deviation math is correct.
But a variable overwrite destroys independence between inputs.This is why boundary auditing is powerful.
Traditional review often asks:
Which component contains the bug?Which component contains the bug?Boundary review asks:
Which security property failed to survive the handoff?Which security property failed to survive the handoff?Those are not the same question.
From Bug Hunting to Invariant Hunting
Individual payloads are temporary.
Security invariants are reusable.
Instead of collecting:
SSRF payloads
header bypasses
IDOR parameters
oracle tricksSSRF payloads
header bypasses
IDOR parameters
oracle tricksI want to collect questions.
Questions like:
What context disappears here?
What does this identifier actually authorize?
What privilege becomes reachable after this edge is created?
Do validator and consumer interpret this value identically?
Are these two supposedly independent values still independent?
What happens when the security mechanism fails?
Can the security guard ever reject a real hostile state?What context disappears here?
What does this identifier actually authorize?
What privilege becomes reachable after this edge is created?
Do validator and consumer interpret this value identically?
Are these two supposedly independent values still independent?
What happens when the security mechanism fails?
Can the security guard ever reject a real hostile state?Those questions transfer between technologies.
That is what makes them useful.
Conclusion
The five findings discussed here span web security, authorization, identity infrastructure, SSRF, and smart contracts.
Their implementation details are unrelated.
Their failure mode is not.
In every case, a security property depended on context.
Then some boundary changed that context:
provenance disappeared
ownership disappeared
privilege became transitive
network semantics became strings
independent measurements became aliasesprovenance disappeared
ownership disappeared
privilege became transitive
network semantics became strings
independent measurements became aliasesThe system continued operating.
The security invariant did not.
That leads to the research principle I now use:
Find boundaries where rich security context is transformed into a weaker representation, then test which security distinctions disappear during that transformation.
Do not only audit values.
Audit their provenance.
Do not only audit objects.
Audit relationships.
Do not only audit checks.
Audit whether hostile state can influence their decisions.
And whenever one subsystem hands something to another, ask the question that keeps producing interesting results:
What did we know before this boundary that we no longer know afterward?
That missing information may be the vulnerability.
Research referenced in this article
Nuxt โ CVE-2026โ49993 / GHSA-x6qj-4h56โ5rj5
Same-origin protection bypass when Sec-Fetch-Site, Origin, and Referer are all absent.
Nezha โ GHSA-q6xx-5vr8-p898 Cross-user terminal/file-manager stream hijacking caused by stream UUIDs without creator binding.
authentik โ GHSA-h6c5-mpvq-j4jc Privilege escalation through delegated group/user management and effective authorization relationships.
Formbricks โ ENG-1326 / PR #8946 Webhook SSRF classification gaps caused by regex/string-prefix representations of network ranges.
Olas โ Code4rena H-02 TWAP deviation protection rendered ineffective after a variable overwrite caused the security comparison to collapse onto the same underlying value.