Hacker DirectoryHacker Directory

WEB / WEB-20.S01

Server-side fetch, SSRF boundaries and egress policy

Bind server network authority to narrow business destinations.

CORE · RED · BLUE · ARTIFACT · PRACTICE

1. Core · Mechanism

Read every scenario without motion

Approved catalog

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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

  1. 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
  2. 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
  3. 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
  4. 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

  1. 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
  2. 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
  3. 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
  4. 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

  1. 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
  2. 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
  3. 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
  4. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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?

  1. Yes. A valid HTTPS URL passes every egress policy.
  2. 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
  1. The path differs; only the configured public-note path is admitted.
  2. 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