WEB / WEB-20.S01
Server-side fetch, SSRF boundaries and egress policy
Bind server network authority to narrow business destinations.
1. Core · Mechanism
Read every scenario without motion
Approved catalog
- Client ready
The client prepares a read for public note-A; a server-owned profile fixes GET /public-notes/note-A. The message has not reached the Fetch gate; no destination or egress decision has occurred.
- Client
- Request ready
- Fetch gate
- Not evaluated yet
- Public catalog
- Fixed public record ready
- Server received request
The server received the ordinary request; resource permission and current context still need separate evaluation.
- Client
- Request ready
- Fetch gate
- Request arrived; gate pending
- Public catalog
- Fixed public record ready
- Local gate decision
Approved configured origin/path, supplied destination and egress evidence passed; the public read request is ready.
- Client
- Request ready
- Fetch gate
- Origin/path match; destination/egress accepted; GET ready
- Public catalog
- Fixed public record ready
- Catalog received request
The catalog received only the ordinary note-A read; its fixed public title reply is ready, without claiming a real network result.
- Client
- Request ready
- Fetch gate
- Origin/path match; destination/egress accepted; GET ready
- Public catalog
- Request arrived; public reply ready
- Server received record
The server received the supplied public catalog title record; its local projection has not yet been constructed.
- Client
- Request ready
- Fetch gate
- Public record arrived; projection pending
- Public catalog
- Request arrived; public reply ready
- Local projection
The server selected only the title field; other server context fields were not added to the public response, and this is local processing.
- Client
- Request ready
- Fetch gate
- Title projection accepted; reply ready
- Public catalog
- Request arrived; public reply ready
- Public reply arrived
The client received the Tea & Bread public reply; message arrival changed only its record and did not transfer server permission.
- Client
- Public body arrived: Tea & Bread
- Fetch gate
- Title projection accepted; reply ready
- Public catalog
- Request arrived; public reply ready
- Local view record
The client completed its public title record locally; a subsequent operation requires a fresh server-side decision.
- Client
- Public title stored locally
- Fetch gate
- Title projection accepted; reply ready
- Public catalog
- Request arrived; public reply ready
Unapproved origin
- Client ready
The client prepares a read for public note-A; a server-owned profile fixes GET /public-notes/note-A. The message has not reached the Fetch gate; no destination or egress decision has occurred.
- Client
- Request ready
- Fetch gate
- Not evaluated yet
- Public catalog
- Fixed public record ready
- Server received request
The server received the ordinary request; resource permission and current context still need separate evaluation.
- Client
- Request ready
- Fetch gate
- Request arrived; gate pending
- Public catalog
- Fixed public record ready
- Local gate decision
Origin unapproved fails the fixed fetch profile; there is no catalog GET dispatch or public title body.
- Client
- Request ready
- Fetch gate
- Origin unapproved; no dispatch; denial ready
- Public catalog
- Fixed public record ready
- Client received denial
The client received only the denial decision; no catalog dispatch, public title body, delivery or write occurred.
- Client
- Denial arrived; no body/delivery/write
- Fetch gate
- Origin unapproved; no dispatch; denial ready
- Public catalog
- Fixed public record ready
Evidence missing
- Client ready
The client prepares a read for public note-A; a server-owned profile fixes GET /public-notes/note-A. The message has not reached the Fetch gate; no destination or egress decision has occurred.
- Client
- Request ready
- Fetch gate
- Not evaluated yet
- Public catalog
- Fixed public record ready
- Server received request
The server received the ordinary request; resource permission and current context still need separate evaluation.
- Client
- Request ready
- Fetch gate
- Request arrived; gate pending
- Public catalog
- Fixed public record ready
- Local gate decision
Destination evidence unknown fails the fixed fetch profile; there is no catalog GET dispatch or public title body.
- Client
- Request ready
- Fetch gate
- Destination evidence unknown; no dispatch; denial ready
- Public catalog
- Fixed public record ready
- Client received denial
The client received only the denial decision; no catalog dispatch, public title body, delivery or write occurred.
- Client
- Denial arrived; no body/delivery/write
- Fetch gate
- Destination evidence unknown; no dispatch; denial ready
- Public catalog
- Fixed public record ready
Egress denied
- Client ready
The client prepares a read for public note-A; a server-owned profile fixes GET /public-notes/note-A. The message has not reached the Fetch gate; no destination or egress decision has occurred.
- Client
- Request ready
- Fetch gate
- Not evaluated yet
- Public catalog
- Fixed public record ready
- Server received request
The server received the ordinary request; resource permission and current context still need separate evaluation.
- Client
- Request ready
- Fetch gate
- Request arrived; gate pending
- Public catalog
- Fixed public record ready
- Local gate decision
Egress known denied fails the fixed fetch profile; there is no catalog GET dispatch or public title body.
- Client
- Request ready
- Fetch gate
- Egress known denied; no dispatch; denial ready
- Public catalog
- Fixed public record ready
- Client received denial
The client received only the denial decision; no catalog dispatch, public title body, delivery or write occurred.
- Client
- Denial arrived; no body/delivery/write
- Fetch gate
- Egress known denied; no dispatch; denial ready
- Public catalog
- Fixed public record ready
ACTORS · STATE · DATA FLOW
Whose authority does the HTTP client use?
A browser executes a page’s JavaScript request within its own origin and credential rules. Server-side fetch uses the application process’s network reachability. The same fetch name does not make these environments the same security boundary. In the public catalog example, the learner selects note-A; the server maps that business identifier to its registered catalog endpoint. The client selects neither an arbitrary URL nor credentials to forward.
The SSRF boundary is where an external business request becomes a decision about where the server connects and for what purpose. Passing a read request’s input schema does not grant destination permission. Undici’s lack of browser CORS checks by default illustrates the distinction: absent ACAO is not a network policy that prevents the server’s request. The application must make its own permission and egress decisions.
URL, destination and connection form one decision chain
This profile fixes origin https://catalog.example.org, path /public-notes/note-A and method GET on the server. A URL parser separates protocol, hostname, port and pathname; an origin alone contains no path permission. The same origin’s /help is a valid URL but not this operation’s endpoint. Re-parsing the constructed URL must preserve the approved components; parser consistency is part of the design when strings cross service boundaries.
Approval of a domain and approval of the destination actually used for a connection are different evidence. Resolution, connection selection, retries and fallbacks must remain within the same destination policy. The diagram’s approved-set-A is only an alias; it performs no real DNS lookup, address classification, certificate validation or socket establishment. Missing destination evidence cannot be treated as success. Automatic redirects are disabled in this profile; changed requirements need a fresh decision for each hop’s destination, method and forwarded context.
Egress and the response boundary
An application allowlist states which business operation should call which service. Network egress independently limits the process’s available routes. Collapsing both into one boolean hides the layer at which evidence was obtained. The artifact supplies separate destination and egress evidence; a correct URL with an approved destination still produces no dispatch when egress is denied.
The normal branch reads only a public title; the response is not interpreted as a new identity or instruction. Response size, time and redirect count also belong to the client’s resource profile. The example’s 256 bytes and 10 ticks are teaching budgets rather than universal limits. Production must enforce them in the real HTTP client lifecycle; this example tests fixed URL components and a finite decision table before dispatch.
2. Red · Trust & Impact
Read every scenario without motion
Endpoint scope
- The approved profile is specific
The profile specifies GET, the catalog.example.org origin and /public-notes/note-A path together. Approval is more specific than a domain string.
- URL / profile
- method=GET
- Destination / egress
- path=/public-notes/note-A
- Trust boundary
- Server-owned profile
- Same origin, different path
The /help entry shares the origin but has accepted=false. Successful parsing does not establish compliance with the selected endpoint contract.
- URL / profile
- origin=https://catalog.example.org
- Destination / egress
- path=/help / accepted=false
- Trust boundary
- URL parsing != permission
- The approved path is separately accepted
The exact configured /public-notes/note-A profile has accepted=true. This acceptance does not extend to /help or another endpoint.
- URL / profile
- path=/public-notes/note-A
- Destination / egress
- component accepted=true
- Trust boundary
- Scope is one profile
- Destination and egress are separate conditions
The normal row supplies true for both approval fields. Endpoint matching does not replace either field; each decision has its own input.
- URL / profile
- destinationApprovedDeclared=true
- Destination / egress
- egressApprovedDeclared=true
- Trust boundary
- Two separate approval inputs
- Dispatch is a teaching-model outcome
Normal expectedDispatch=true and expectedDelivery=true are supplied model outcomes. Profile.actualHTTP=false prevents claiming that a real request was sent.
- URL / profile
- expectedDispatch=true
- Destination / egress
- actualHTTP=false
- Trust boundary
- No real connection observed
- Reading does not grant write authority
ExpectedWrite=false. Reading the public catalog response grants neither local-note write authority nor server egress permission through browser CORS.
- URL / profile
- expectedWrite=false
- Destination / egress
- browserCORSIsEgress=false
- Trust boundary
- The authority boundary is preserved
Egress denied
- The URL matches the profile
The egress-denied row uses the same configuredURL. URL matching is unchanged, but the outcome also depends on other fields.
- URL / profile
- configuredURL=approved public path
- Destination / egress
- Approval fields not inspected yet
- Trust boundary
- Profile matching is not the only condition
- Destination approval is true
DestinationApprovedDeclared=true is supplied. It does not establish admission by egress policy.
- URL / profile
- destinationApprovedDeclared=true
- Destination / egress
- Egress examined separately
- Trust boundary
- Destination != egress
- Egress approval is false
EgressApprovedDeclared=false is the selected rejection condition. Approved destination and network exit permission differ in this record.
- URL / profile
- destinationApprovedDeclared=true
- Destination / egress
- egressApprovedDeclared=false
- Trust boundary
- Rejection condition is known
- The model does not dispatch
ExpectedDispatch=false and expectedDelivery=false prevent the model from producing a delivered body under rejected egress.
- URL / profile
- expectedDispatch=false
- Destination / egress
- expectedDelivery=false
- Trust boundary
- No body
- Do not call this a real firewall outcome
ActualFirewall=false identifies a supplied-predicate outcome rather than observed firewall or network enforcement.
- URL / profile
- egressApprovedDeclared=false
- Destination / egress
- actualFirewall=false
- Trust boundary
- Declared decision and enforcement differ
- Preserve the correct failure boundary
This branch stops at egress approval. Do not import missing-destination evidence or body success from another row.
- URL / profile
- known egress rejection
- Destination / egress
- expectedBody=null
- Trust boundary
- Evidence stays with the same row
ASSUMPTION · BOUNDARY · CONTROL
Broken assumption: valid input means permitted destination
The Red perspective questions necessary conditions instead of sending attempts to a destination. note-A can satisfy its schema while the endpoint configuration must still match the approved origin and path. Unapproved docs.example.org is an ordinary documentation origin; being public does not make it the destination of this catalog operation. The unknown-origin branch produces neither a catalog message nor a title body.
Validation is evidence only about the component it checks. Parsing a URL does not establish a reviewed DNS result or a validated TLS peer. If evidence connecting resolver selection to the client’s actual destination is absent, record the absence explicitly. Carrying null separately from false in missing-evidence distinguishes an unknown condition from a known denial while preserving the same no-dispatch boundary.
Finding the layer where a control stops applying
The second question is whether a decision for one hop silently carries into another. The artifact does not follow redirects and records no new dispatch to an ordinary different documentation origin. This is not an executed redirect test. A system that needs redirects must separately demonstrate that every hop fits its approved profile and that forwarded context remains restricted.
In egress-denied, the business mapping is correct but egressApprovedDeclared=false records a known denial of that route. Continuing the request would disregard that control’s outcome. A review finding should not inflate its impact into access to everything: record the denied gate, reachable layer and permitted data separately. This lesson provides no URL corpus, destination discovery or vulnerability reproduction against another service.
3. Blue · Secure development & defense
Read every scenario without motion
Approval unknown
- The configured URL is unchanged
The missing-evidence row uses the approved catalog URL. Configuration matching does not establish a recorded destination decision.
- Decision record
- configuredURL=approved public path
- Connection / budget
- Destination evidence not inspected yet
- Evidence scope
- Configuration and decision differ
- The destination field is null
DestinationApprovedDeclared=null is neither true nor false. Missing evidence establishes neither approval nor rejection of the destination.
- Decision record
- destinationApprovedDeclared=null
- Connection / budget
- egressApprovedDeclared=true
- Evidence scope
- Destination outcome UNKNOWN
- Egress true does not fill the missing field
EgressApprovedDeclared=true belongs to another condition. Its positive value cannot be copied into the destination field.
- Decision record
- destinationApprovedDeclared=null
- Connection / budget
- egressApprovedDeclared=true
- Evidence scope
- Decisions cannot substitute for each other
- No dispatch with missing evidence
ExpectedDispatch=false and expectedBody=null stop the model without proving that the missing destination decision was false.
- Decision record
- expectedDispatch=false
- Connection / budget
- expectedBody=null
- Evidence scope
- Fail closed differs from known rejection
- Report UNKNOWN separately
Egress-denied records known false while this row records null. Equal no-dispatch outcomes do not imply equal decision knowledge.
- Decision record
- destination=null / egress=true
- Connection / budget
- dispatch=false
- Evidence scope
- Missing destination evidence
- Name the required observation
This row needs its destination-approval record. Another request’s successful body or browser CORS setting cannot supply it.
- Decision record
- destination evidence required
- Connection / budget
- browserCORSIsEgress=false
- Evidence scope
- Evidence must belong to the same request
Redirect evidence
- The redirect names a separate target
RedirectLedger names docs.example.org as targetOrigin. Approval of the starting catalog profile does not approve the new origin.
- Decision record
- targetOrigin=https://docs.example.org
- Connection / budget
- New approval required
- Evidence scope
- Initial scope is bounded
- Automatic following is disabled
RedirectsFollowed=false and nextDispatch=false prevent automatic transition to an unapproved destination in this model.
- Decision record
- redirectsFollowed=false
- Connection / budget
- nextDispatch=false
- Evidence scope
- Redirect destination not fetched
- The address set is an alias
ConnectionEvidence uses the approved-set-A alias. ActualAddressesIncluded=false means the artifact contains no real connection addresses.
- Decision record
- addressSet=approved-set-A (alias only)
- Connection / budget
- actualAddressesIncluded=false
- Evidence scope
- An alias is not an address measurement
- Declared equality is not a measurement
ValidatedSetEqualsConnectedSetDeclared=true is a supplied relation. ActualResolutionPerformed=false prevents claiming verified DNS results or a connection destination.
- Decision record
- validatedSetEqualsConnectedSetDeclared=true
- Connection / budget
- actualResolutionPerformed=false
- Evidence scope
- Domain approval is not address approval
- Budget values are not enforcement
MaxResponseBytes=256, maxRedirects=0 and deadlineTick=10 are selected bounds. ActualRuntimeBudgetEnforced=false means real runtime enforcement is not established.
- Decision record
- maxResponseBytes=256 / maxRedirects=0
- Connection / budget
- actualRuntimeBudgetEnforced=false
- Evidence scope
- Configured budget and measurement differ
- Report configuration and observation separately
Report no dispatch to the new target, a declared address relation and unmeasured budget enforcement. These are not outcomes of real DNS, TLS or firewall tests.
- Decision record
- nextDispatch=false
- Connection / budget
- actualDNS=false / actualTLS=false
- Evidence scope
- Supplied decision record
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Constructing the endpoint from a business identifier
The Blue design gives the client a narrow identifier such as note-A. A server-owned registry binds it to a profile with fixed scheme, origin, path and method. Known URL parsing separates the configured endpoint for comparison with approved components. Reading a public title needs no credential forwarding; copying a generic header collection into another service is outside this example’s contract.
Assign separate owners and measurement moments to destination and egress approval. A retry or fallback must not select a new address outside the decision. The HTTP client profile disables redirects; a new requirement needs an explicit reviewed change. Byte, deadline and hop budgets complement each other, and one timeout proves neither of the others. Real client selection, resolver use and connection establishment need implementation review.
Checking negative evidence with a dispatch counter
The regression table accepts correct configuration; an unapproved origin, missing destination evidence and denied egress each leave catalog dispatch at zero. A denied read has no title body and performs no store write. A separate ordinary /help row on the correct origin demonstrates an endpoint boundary narrower than origin equality. The positive branch produces the fixed title projection.
The native observation covers only Node URL parsing of these fixed URLs and the finite permission predicate. Animation plays supplied message ordering through the shared pure reducer. Neither is reported as real DNS, TLS, network firewall or browser CORS enforcement. The review record preserves a failed control, unknown destination evidence and withheld body separately, without requiring secrets in URL or response logs.
NOW APPLY IT TO THE CASE
How do URL parsing, destination approval and egress approval differ?
Atlas models reading public note-A through a server-owned catalog profile. Learners inspect supplied decision records for missing conditions without connecting to a new destination.
Which components contribute to the case response? Show where UI, service, platform and external-provider outcomes complete along their respective hops.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Approved catalog fetch decision
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"profile": {
"origin": "https://catalog.example.org",
"path": "/public-notes/note-A",
"method": "GET",
"title": "Tea & Bread",
"inputCatalogId": "note-A",
"actualDNS": false,
"actualTLS": false,
"actualHTTP": false,
"actualFirewall": false
},
"rows": [
{
"case": "normal",
"label": [
"Onaylı catalog",
"Approved catalog"
],
"configuredURL": "https://catalog.example.org/public-notes/note-A",
"destinationApprovedDeclared": true,
"egressApprovedDeclared": true,
"expectedDispatch": true,
"expectedDelivery": true,
"expectedWrite": false,
"expectedBody": {
"title": "Tea & Bread"
}
},
{
"case": "unknown-origin",
"label": [
"Origin onaysız",
"Unapproved origin"
],
"configuredURL": "https://docs.example.org/public-notes/note-A",
"destinationApprovedDeclared": true,
"egressApprovedDeclared": true,
"expectedDispatch": false,
"expectedDelivery": false,
"expectedWrite": false,
"expectedBody": null
},
{
"case": "missing-evidence",
"label": [
"Kanıt eksik",
"Evidence missing"
],
"configuredURL": "https://catalog.example.org/public-notes/note-A",
"destinationApprovedDeclared": null,
"egressApprovedDeclared": true,
"expectedDispatch": false,
"expectedDelivery": false,
"expectedWrite": false,
"expectedBody": null
},
{
"case": "egress-denied",
"label": [
"Egress ret",
"Egress denied"
],
"configuredURL": "https://catalog.example.org/public-notes/note-A",
"destinationApprovedDeclared": true,
"egressApprovedDeclared": false,
"expectedDispatch": false,
"expectedDelivery": false,
"expectedWrite": false,
"expectedBody": null
}
],
"componentChecks": [
{
"url": "https://catalog.example.org/help",
"accepted": false,
"reason": "Unapproved path"
},
{
"url": "https://catalog.example.org/public-notes/note-A",
"accepted": true,
"reason": "Exact configured profile"
}
],
"redirectLedger": [
{
"redirectsFollowed": false,
"targetOrigin": "https://docs.example.org",
"nextDispatch": false,
"reason": "Automatic following disabled; a new destination needs independent approval"
}
],
"connectionEvidence": {
"addressSet": "approved-set-A (alias only)",
"validatedSetEqualsConnectedSetDeclared": true,
"actualAddressesIncluded": false,
"actualResolutionPerformed": false
},
"budget": {
"maxResponseBytes": 256,
"maxRedirects": 0,
"deadlineTick": 10,
"actualRuntimeBudgetEnforced": false
},
"boundaries": {
"browserCORSIsEgress": false,
"domainApprovalIsAddressApproval": false,
"URLParsingIsPermission": false
}
}- profile
- Server-owned origin/path/method and native-test boundary.
- rows
- Configuration, destination and egress evidence; dispatch/body/write outcomes.
- componentChecks
- Approved endpoint versus an ordinary different path on the same origin.
- redirectLedger
- Automatic following disabled; no new dispatch to a different ordinary origin.
- connectionEvidence
- Validated/connected-set relationship is a declared alias only; no DNS is executed.
- budget
- Example byte/deadline/hop budgets; no real runtime enforcement.
- boundaries
- Boundaries where CORS, domain approval and parsing do not replace permission.
Fixed public URL configuration and supplied destination/egress evidence; no real DNS/TLS/HTTP/firewall.
Two reality checks
Browser CORS protects server fetch.
The server client does not inherit that browser gate; destination and egress need separate decisions.
Origin approval validates the whole connection.
Path, address selection, retry/redirect and network route need distinct boundaries and evidence.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The configured origin matches, but destinationApprovedDeclared=null. Is the public-catalog GET dispatched?
- Yes. A valid HTTPS URL passes every egress policy.
- No. Dispatch and body delivery are withheld.
Read the explained answer
No. Dispatch and body delivery are withheld. — The missing-evidence branch has expectedDispatch=false. A true egress condition does not supply missing destination evidence.
Why is /help rejected even on the approved origin?
Selected lesson artifact fields; teaching example
- componentChecks
- [{"url":"https://catalog.example.org/help","accepted":false,"reason":"Unapproved path"},{"url":"https://catalog.example.org/public-notes/note-A","accepted":true,"reason":"Exact configured profile"}]
- profile.path
- /public-notes/note-A
- budget.maxRedirects
- 0
- The path differs; only the configured public-note path is admitted.
- Every URL on the same origin is automatically an equivalent destination.
Read the explained answer
The path differs; only the configured public-note path is admitted. — The component check records Unapproved path for /help. An origin match does not approve all paths or redirect targets.
Why are domain policy, connected address sets and response budgets separate evidence? Does browser CORS protect this server fetch?
Read the explained answer
URL policy chooses approved method/origin/path. Domain approval alone does not prove resolution or the address set actually connected to. This artifact contains an approved-set alias and a supplied equality condition, with no actual DNS/TLS/HTTP/firewall execution. Byte, redirect and deadline budgets are not measured enforcement. Browser CORS controls script exposure, not server-side egress.
□ I did not treat domain approval as address or connection evidence.
□ I did not treat CORS or declared budgets as real server-egress enforcement.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- OWASP SSRF PreventionCase 1 / Available protections / Application layer: redirect handling; Domain name: connection-time destination enforcement; URL: rebuild approved components · Case 1 / Network layer: legitimate flows and restricted routes
- Node v24.13.0 URL APIURL strings and URL objects; new URL(input[, base]); url.origin; url.pathname
- Undici specification complianceSpecification Compliance / CORS: no browser CORS checks by default