Hacker DirectoryHacker Directory

WEB / WEB-10.S02

Output contexts, XSS and DOM trust boundaries

Why does the same string need different controls in text, HTML, attributes, JSON and URLs?

CORE · RED · BLUE · ARTIFACT · PRACTICE

1. Core · Mechanism

Read every scenario without motion

Literal title

  1. Local data prepared

    The source ledger holds Tea & Bread for ordinary h2.textContent. Literal-text selection is still pending. The cards are responsibilities in one local model; no actual DOM or network executes.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    No DOM outcome
  2. Context selection

    The renderer selects the final consumer context; this local lookup is neither network traffic nor a document message.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Context selected; interpretation pending
  3. Local text setter

    The original string is selected for ordinary h2.textContent; no HTML entity encoding or fragment parsing is performed.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Text setter ready; no HTML parse
  4. Literal outcome

    The selected declaration expects literal Text; benign markup syntax is not structure or active code in this branch.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Literal: Tea & Bread

Markup as text

  1. Local data prepared

    The source ledger holds <em>Tea &amp; Bread</em> with h2.textContent selected as consumer. This branch selects no formatting parser. The model will retain tag/entity spelling literally; there is no active code.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    No DOM outcome
  2. Context selection

    The renderer selects the final consumer context; this local lookup is neither network traffic nor a document message.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Context selected; interpretation pending
  3. Local text setter

    The original string is selected for ordinary h2.textContent; no HTML entity encoding or fragment parsing is performed.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Text setter ready; no HTML parse
  4. Literal outcome

    The selected declaration expects literal Text; benign markup syntax is not structure or active code in this branch.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Literal: <em>Tea &amp; Bread</em>

HTML text source

  1. Local data prepared

    The source is Tea & Bread; this branch will select entity encoding for fixed h2 HTML source. Its result is serialization plus an expected text value, not an observation from an executed HTML parser.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    No DOM outcome
  2. Context selection

    The renderer selects the final consumer context; this local lookup is neither network traffic nor a document message.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Context selected; interpretation pending
  3. Local entity encoding

    The selected benign title is encoded for HTML text source; a native string transformation is not an actual parser.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Source prepared: Tea &amp; Bread
  4. Expected parse boundary

    The fixed h2 source declaration expects Tea & Bread text; no actual HTML parser or DOM is executed.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Expected h2 text: Tea & Bread

URL policy denied

  1. Local data prepared

    The query title is Tea & Bread; the candidate targets reader.example.net while the approved origin is atlas.example.org. Local destination policy is pending; query encoding approves no host and opens no connection.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    No DOM outcome
  2. Context selection

    The renderer selects the final consumer context; this local lookup is neither network traffic nor a document message.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Context selected; interpretation pending
  3. Local URL gate

    The fixed HTTPS example URL is parsed; its origin does not match the owned-origin policy, so link assignment is denied.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    URL denied; no assignment
  4. No link outcome

    The DOM link remains unchanged and no URL is opened; encoding is not destination approval.

    Local source ledger
    Source value ready; plain data
    Local DOM view
    Link unchanged; no network

ACTORS · STATE · DATA FLOW

An output context selects an interpreter

The same Tea & Bread string can reach an h2 text node, serialized HTML text, a quoted title attribute, JSON data or a URL query, each with a different consumer. Ordinary h2.textContent does not invoke an HTML parser and should receive the original string. Serialized HTML text protects its ampersand with entity encoding, while a quoted ordinary title attribute also needs its outer quote boundary protected. Keep attribute names fixed: a decision for a plain text title attribute does not automatically apply to URL or event-handler attributes. Context describes the final consumer’s interpretation, not the original input type.

Reflected/stored and DOM describe different axes

Reflected data returns through the ordinary request/response cycle; stored data comes from persistence for later display. This lifetime distinction does not determine encoding: storing user-controlled text does not make it trusted markup. DOM-based risk concerns client code moving a source value into an interpreting sink, and either reflected or stored data can enter that client-side path. XSS requires conditions such as untrusted influence, executable interpretation and execution under document authority. The harmless em formatting declaration or Tea & Bread result here is not evidence of XSS; it teaches mechanisms and control boundaries.

Read source provenance together with sink purpose

The source ledger can identify an ordinary query value, fetched book JSON or a stored-title alias; arriving from the same origin does not turn a title into an HTML instruction. The sink ledger distinguishes literal textContent, innerHTML fragment parsing and URL parsing plus destination policy. JSON serialization creates a value representation; placing it into inline script or event-handler source introduces a different context problem. This fixture parses JSON as data and generates no dynamic script. URLSearchParams protects query-value boundaries but does not approve URL scheme, origin or path. Source-to-sink processing remains local within the browser.

2. Red · Trust & Impact

Read every scenario without motion

Wrong context

  1. Ordinary data source

    query-title, saved-title and json-title carry the same Tea & Bread value. Their reflected, stored and response lifetimes differ, but all remain data.

    Source and representation
    Three source aliases / trust data
    Interpreter boundary
    Not yet evaluated
    Visible outcome
    Not yet evaluated
  2. Text API boundary

    For ordinary h2.textContent, Tea & Bread is direct text with no HTML pre-encoding. The Tea &amp; Bread HTML-source representation belongs to another consumer.

    Source and representation
    Three source aliases / trust data
    Interpreter boundary
    Text value ≠ serialized HTML representation
    Visible outcome
    Not yet evaluated
  3. Wrong entity display

    encodedIntoTextContentExpected is literal Tea &amp; Bread. This wrong-context transformation corrupts the title; it is not an executed XSS result or a discovered vulnerability.

    Source and representation
    Three source aliases / trust data
    Interpreter boundary
    Text value ≠ serialized HTML representation
    Visible outcome
    Literal entity text / wrong representation
  4. Second encoding

    DoubleEncoding once yields Tea &amp; Bread and twice Tea &amp;amp; Bread. Running the same encoder again treats representation as data; it adds no trust label.

    Source and representation
    Once amp; / twice amp;amp;
    Interpreter boundary
    Text value ≠ serialized HTML representation
    Visible outcome
    Literal entity text / wrong representation
  5. Choose context explicitly

    HTML text, quoted attribute, JSON data and URL query encoders serve different tasks. Element and attribute names remain application-owned; source lifetime does not select a sink.

    Source and representation
    Once amp; / twice amp;amp;
    Interpreter boundary
    Source → transform → final interpreter
    Visible outcome
    Literal entity text / wrong representation
  6. Retain the bounded outcome

    This ordinary string demonstrates representation differences. It does not certify every input, HTML parsing or script execution; the final interpreter requires separate review.

    Source and representation
    Once amp; / twice amp;amp;
    Interpreter boundary
    Source → transform → final interpreter
    Visible outcome
    Representation known / execution unmeasured

Destination policy

  1. Encoded URL

    owned-url and url-denied use the same Tea+%26+Bread encoding for q. Encoding preserves the title without deciding whether the application approves the host.

    Source and representation
    Same q encoding / different origins
    Interpreter boundary
    Not yet evaluated
    Visible outcome
    Not yet evaluated
  2. URL policy decision

    The policy selects HTTPS, the Atlas origin and /books. The reader.example.net candidate is rejected locally; successful decoding does not replace destination permission.

    Source and representation
    Same q encoding / different origins
    Interpreter boundary
    Owned true / reader false
    Visible outcome
    Not yet evaluated
  3. Do not invent a connection

    No network or DOM-link result is produced for the denied URL. href consumes a URL; not parsing HTML does not remove destination-policy requirements.

    Source and representation
    Same q encoding / different origins
    Interpreter boundary
    Owned true / reader false
    Visible outcome
    Local URL deny / no connection
  4. Markup expectation

    Benign em bytes are expected to remain literal in textContent, while fragmentTreeExpected describes em with a Tea & Bread Text child. They represent different interpreter choices.

    Source and representation
    Literal text ≠ expected em tree
    Interpreter boundary
    Owned true / reader false
    Visible outcome
    Local URL deny / no connection
  5. Do not merge taxonomy axes

    Reflected/stored lifetime and server-output/client-DOM interpretation are independent axes. Storage does not approve markup, and same-origin response is not executable-safety certification.

    Source and representation
    Literal text ≠ expected em tree
    Interpreter boundary
    Lifetime and interpretation independent
    Visible outcome
    Local URL deny / no connection
  6. Name the conditions

    Benign formatting differences are not labelled exploitation without untrusted influence and active semantics. The control here separates data provenance from final-interpreter policy.

    Source and representation
    Literal text ≠ expected em tree
    Interpreter boundary
    Lifetime and interpretation independent
    Visible outcome
    Benign structure ≠ demonstrated XSS

ASSUMPTION · BOUNDARY · CONTROL

A harmless parser result is not a security certificate

A fixed em formatting declaration is expected to produce emphasis structure in a markup parser, whereas the same bytes assigned to ordinary textContent remain literal markup syntax. The difference explains interpretation without creating an executable attack or reproducing a vulnerability. A real risk assessment separately needs untrusted influence and active semantics. Correct rendering of one benign title does not establish safety for an entire input domain; a generic escaped label is insufficient without source, transformation, sink and surrounding grammar.

A trust label cannot replace context information

Persistence changes a saved title’s provenance, not its output trust. A same-origin response, successful JSON parse or HTML entity encoder proves neither business identity nor safety for every output context. Passing HTML-text-encoded data into textContent can display the literal entity text Tea &amp; Bread; that is a wrong-context/usability failure, not a claim that XSS occurred. URL encoding also cannot replace an approved-destination policy. The fixed external HTTPS example is denied locally without opening a connection. The Red lens identifies the narrow question each label actually answers.

3. Blue · Secure development & defense

Read every scenario without motion

Context controls

  1. Select five consumers

    These records come from a finite source/encoder/policy ledger. contexts carries representations and interpretation expectations, sourceRows lifetimes, and urlRows local destination decisions. With actualDOM=false and actualHTMLParser=false, expected trees/text are not new browser observations; URL denial selects no link assignment. Contexts list ordinary textContent, serialized HTML text, quoted attribute, JSON and URL query for the same title. Representation and interpretation are read together for each row.

    Output ledger
    Five representations / explicit consumers
    Context review
    Not yet evaluated
    Regression evidence
    Not yet evaluated
  2. Compare encoder results

    The ampersand becomes amp; in HTML text, and quotes become quot; in the quoted title. JSON string parsing and URL query decoding must recover the ordinary title.

    Output ledger
    Five representations / explicit consumers
    Context review
    HTML text/attribute; JSON/URL roundtrip
    Regression evidence
    Not yet evaluated
  3. Separate sink behavior

    h2.textContent has parsesHTML false, innerHTML true, and href has URL-policy context. One escaped label cannot describe all three consumers.

    Output ledger
    Five representations / explicit consumers
    Context review
    HTML text/attribute; JSON/URL roundtrip
    Regression evidence
    Text / HTML fragment / URL policy
  4. Source lifetime record

    Query, saved and JSON-title provenance are recorded separately from the final sink. Changing source lifetime for the same ordinary value should retain literal text binding.

    Output ledger
    Lifetime recorded / data remains data
    Context review
    HTML text/attribute; JSON/URL roundtrip
    Regression evidence
    Text / HTML fragment / URL policy
  5. Bound the negative control

    URL denial, double encoding and wrong textContent representation are separate negative controls. None proves browser script execution or testing of the entire HTML grammar.

    Output ledger
    Lifetime recorded / data remains data
    Context review
    Separate bounded negatives / no execution claim
    Regression evidence
    Text / HTML fragment / URL policy
  6. Question answered by the control

    Regression associates source, transform, sink and interpretation with one data flow. Native finite encoding and browser parsing are not substituted for each other.

    Output ledger
    Lifetime recorded / data remains data
    Context review
    Separate bounded negatives / no execution claim
    Regression evidence
    Data-flow context retained / browser separate

Interpreter gap

  1. Observe only the title

    These records come from a finite source/encoder/policy ledger. contexts carries representations and interpretation expectations, sourceRows lifetimes, and urlRows local destination decisions. With actualDOM=false and actualHTMLParser=false, expected trees/text are not new browser observations; URL denial selects no link assignment. The review sees only displayed Tea & Bread, without source or final-setter records. The same visible text can come through different representations and interpreters.

    Output ledger
    Display known / sink provenance missing
    Context review
    Not yet evaluated
    Regression evidence
    Not yet evaluated
  2. Separate the expected tree

    benignMarkup fragmentTreeExpected is a supplied expectation. Treating it as a DOM capture would attribute results to an HTML parser that did not run here.

    Output ledger
    Display known / sink provenance missing
    Context review
    Expected tree ≠ captured DOM
    Regression evidence
    Not yet evaluated
  3. Leave execution unknown

    Even with correct display, actualHTMLParser and activeMarkup are false. The record concludes neither executed XSS nor universal sink safety; interpreter evidence is missing.

    Output ledger
    Display known / sink provenance missing
    Context review
    Expected tree ≠ captured DOM
    Regression evidence
    Interpreter execution unmeasured
  4. Required provenance

    Source alias, transformation and final API name are needed. Serialized HTML must be distinguished from textContent binding, and URLs need a separate destination-policy record.

    Output ledger
    Needed: source/transform/sink/policy
    Context review
    Expected tree ≠ captured DOM
    Regression evidence
    Interpreter execution unmeasured
  5. Make a narrow comparison

    Normal encoder results are compared only for the provided strings. Missing sink evidence is not filled with the h2 profile or generalized to script/style contexts.

    Output ledger
    Needed: source/transform/sink/policy
    Context review
    Finite strings / no invented sink
    Regression evidence
    Interpreter execution unmeasured
  6. New evidence boundary

    New evidence must identify the consumer receiving the representation. This review retains ordinary display without adding browser execution or general safety claims.

    Output ledger
    Needed: source/transform/sink/policy
    Context review
    Finite strings / no invented sink
    Regression evidence
    Retain display / collect interpreter evidence

IMPLEMENTATION · SECURITY CONTROL · VERIFICATION

Keep plain text in plain text APIs

These records come from a finite source/encoder/policy ledger. contexts carries representations and interpretation expectations, sourceRows lifetimes, and urlRows local destination decisions. With actualDOM=false and actualHTMLParser=false, expected trees/text are not new browser observations; URL denial selects no link assignment. Use ordinary textContent or createTextNode for titles without HTML pre-encoding, keeping element and attribute names application-owned. Assigning a normal title through a DOM attribute API supplies a string value; producing serialized HTML source requires separate quoted-attribute encoding. Do not collapse HTML source, URL components and JSON data into one global sanitize function. Native runtime calculations can check transformations of the finite benign strings here, but those observations do not certify every parser grammar or browser sink.

Regressions for provenance and the final interpreter

Record each value’s source alias, storage lifetime, transformation, final sink and expected interpretation. Ordinary reflected/stored titles should give the same literal outcome, and benign formatting in the textContent branch should remain Text. Decoding JSON data into a title string differs from generating script source. URL tests should recover the encoded title and separately enforce the selected https/owned-origin/books-path policy. A local denial produces no link outcome. The educational model executes no browser HTML parsing, XSS or arbitrary student code; control-flow evidence and finite native encoding observations are recorded separately.

NOW APPLY IT TO THE CASE

Does correct encoding transfer to every output context?

Tea & Bread uses different representations in textContent, HTML source, a quoted title attribute, JSON and a URL query. The benign em example remains literal when written as text but has a structural fragment expectation. Storage or same origin does not select the final interpreter.

Order response receipt, parsing, callbacks and visible DOM outcome in the case. Try explaining the retained old title through this lesson’s browser-state distinctions.

Open the related case file ↗

READ THE EXAMPLE TOGETHER

Tea & Bread output-context ledger

Select a field and examine what it supports and where the evidence ends.

Read the raw artifact and field explanations
{
  "profile": {
    "title": "Tea & Bread",
    "quoteTitle": "Tea & \"Bread\"",
    "ordinaryElement": "h2",
    "attributeName": "title",
    "actualDOM": false,
    "actualHTMLParser": false,
    "activeMarkup": false,
    "network": false
  },
  "sourceRows": [
    {
      "alias": "query-title",
      "lifetime": "reflected",
      "value": "Tea & Bread",
      "trust": "data"
    },
    {
      "alias": "saved-title",
      "lifetime": "stored",
      "value": "Tea & Bread",
      "trust": "data"
    },
    {
      "alias": "json-title",
      "lifetime": "response",
      "value": "Tea & Bread",
      "trust": "data"
    }
  ],
  "contexts": [
    {
      "kind": "ordinary-textContent",
      "input": "Tea & Bread",
      "representation": "Tea & Bread",
      "interpretation": "literal Text node",
      "preEncoded": false
    },
    {
      "kind": "serialized-HTML-text",
      "input": "Tea & Bread",
      "representation": "Tea &amp; Bread",
      "interpretation": "text in fixed h2 source",
      "browserParsed": false
    },
    {
      "kind": "quoted-title-attribute",
      "input": "Tea & \"Bread\"",
      "representation": "Tea &amp; &quot;Bread&quot;",
      "interpretation": "normal fixed title attribute",
      "browserParsed": false
    },
    {
      "kind": "JSON-data",
      "input": "Tea & Bread",
      "representation": "\"Tea & Bread\"",
      "interpretation": "parsed string value; not embedded script"
    },
    {
      "kind": "URL-query-value",
      "input": "Tea & Bread",
      "representation": "q=Tea+%26+Bread",
      "interpretation": "one q value; policy independent"
    }
  ],
  "benignMarkup": {
    "bytes": "<em>Tea &amp; Bread</em>",
    "textContentExpected": "<em>Tea &amp; Bread</em>",
    "fragmentTreeExpected": {
      "tag": "em",
      "children": [
        {
          "text": "Tea & Bread"
        }
      ]
    },
    "executabilityClaim": false
  },
  "doubleEncoding": {
    "original": "Tea & Bread",
    "once": "Tea &amp; Bread",
    "twice": "Tea &amp;amp; Bread",
    "encodedIntoTextContentExpected": "Tea &amp; Bread",
    "riskClaim": "wrong representation, not executed XSS"
  },
  "urlPolicy": {
    "scheme": "https",
    "origin": "https://atlas.example.org",
    "path": "/books"
  },
  "urlRows": [
    {
      "case": "owned-url",
      "candidate": "https://atlas.example.org/books?q=Tea+%26+Bread",
      "expected": true
    },
    {
      "case": "url-denied",
      "candidate": "https://reader.example.net/books?q=Tea+%26+Bread",
      "expected": false
    }
  ],
  "sinkRows": [
    {
      "sink": "h2.textContent",
      "parsesHTML": false,
      "context": "ordinary element text"
    },
    {
      "sink": "innerHTML",
      "parsesHTML": true,
      "context": "HTML fragment"
    },
    {
      "sink": "href",
      "parsesHTML": false,
      "context": "URL and destination policy"
    }
  ],
  "taxonomy": {
    "lifetimeAxis": [
      "reflected",
      "stored"
    ],
    "interpretationAxis": [
      "server output",
      "client DOM"
    ],
    "axesIndependent": true,
    "benignMarkupProvesXSS": false
  },
  "boundaries": {
    "storageMakesMarkupTrusted": false,
    "sameOriginMakesMarkupTrusted": false,
    "jsonIsInlineScriptEncoder": false,
    "urlEncodingApprovesDestination": false,
    "textContentSafeForScriptStyle": false
  }
}
contexts
Different representations/consumers for the same harmless value.
sourceRows
Reflected/stored/response provenance remains data.
benignMarkup
Fixed em declaration; text and structure expectations differ, with no execution.
doubleEncoding
Wrong-context display difference, not a claimed XSS outcome.
urlRows
Fixed reserved example URLs are evaluated only by local policy.

Inert fixed benign strings/markup and an expected tree, with finite encoder/URL/JSON native observations. No actual HTML parser/DOM, script interpretation, vulnerability test or network.

Two reality checks

Database content and same-origin JSON are trusted HTML.

Those establish provenance/representation, while the final sink determines text versus markup interpretation.

textContent gives the same safety guarantee on every element.

This lesson uses ordinary h2 Text behavior; script/style content has separate context rules.

YOUR TURN

Work through this story.

Predict the outcome, find the evidence boundary, and explain it in your own words.

Tea & Bread is encoded as Tea &amp; Bread before being assigned to h2.textContent. What text is expected?

  1. Tea & Bread; textContent decodes every entity as HTML.
  2. Tea &amp; Bread; the encoded representation becomes literal text.
Read the explained answer

Tea &amp; Bread; the encoded representation becomes literal text. — Ordinary textContent creates a Text node. Pre-encoding for HTML source makes the &amp; text visible here.

Why does the same title have different representations for HTML text, a quoted attribute, JSON and a URL query?

Selected lesson artifact fields; teaching example

contexts[1]
{"kind":"serialized-HTML-text","input":"Tea & Bread","representation":"Tea &amp; Bread","interpretation":"text in fixed h2 source","browserParsed":false}
contexts[2]
{"kind":"quoted-title-attribute","input":"Tea & \"Bread\"","representation":"Tea &amp; &quot;Bread&quot;","interpretation":"normal fixed title attribute","browserParsed":false}
contexts[3]
{"kind":"JSON-data","input":"Tea & Bread","representation":"\"Tea & Bread\"","interpretation":"parsed string value; not embedded script"}
contexts[4]
{"kind":"URL-query-value","input":"Tea & Bread","representation":"q=Tea+%26+Bread","interpretation":"one q value; policy independent"}
  1. Each context has different syntax and delimiter rules.
  2. JSON serialization is a safe encoder for every output context.
Read the explained answer

Each context has different syntax and delimiter rules. — HTML text may require &amp;, a quoted attribute &quot;, and a form query %26. JSON string serialization is not one encoder for all these contexts.

query-title, saved-title and json-title carry the same Tea & Bread data. Why do storage or same-origin not grant markup interpretation, and does URL encoding approve the destination?

Read the explained answer

Storage or a same-origin source does not make a string trusted markup. Ordinary h2.textContent creates literal text; an HTML-fragment sink parses markup and needs separate controls. Reflected/stored lifetime and server/client interpretation are independent axes. URL encoding can represent a query value correctly, but destination policy still checks scheme/origin/path. The harmless markup example is not evidence of executed XSS.

□ I did not treat storage or origin as markup trust.

□ I separated URL representation from destination permission.

Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples

Sources and scope