WEB / WEB-10.S04
Service worker cache, object trust and DOM clobbering
Why do offline content, own properties and named DOM references require different trust decisions?
1. Core · Mechanism
Read every scenario without motion
Offline hit
- Client ready
The controlled client needs public book-A; no cache result has been selected.
- Atlas client
- No title; controlled v2
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Local GET preparation
The UI prepares its fixed read request; preparation is not network receipt.
- Atlas client
- GET book-A ready
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Local fetch event
Worker event dispatch and cache lookup are local; no packet goes to the origin.
- Atlas client
- GET book-A ready
- Worker + Cache
- Cache candidate; shape pending
- Origin server
- No request; no read
- Local admission gate
Exact key, public classification, app expiry and own schema pass for this declared entry.
- Atlas client
- GET book-A ready
- Worker + Cache
- Cache response ready; schema=2
- Origin server
- No request; no read
- Local response return
The stored response is supplied locally; body interpretation is still a separate step.
- Atlas client
- Local body ready; source=cache
- Worker + Cache
- Cache response ready; schema=2
- Origin server
- No request; no read
- Literal title outcome
Tea & Bread is expected as plain text; an offline hit does not prove fresh server permission.
- Atlas client
- Literal Tea & Bread; offline
- Worker + Cache
- Cache response ready; schema=2
- Origin server
- No request; no read
Network fallback
- Client ready
The selected client uses a controlled worker; this branch has no stored entry.
- Atlas client
- No title; controlled v2
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Read prepared
A fixed GET for public book-A is prepared; the browser has received no response.
- Atlas client
- GET book-A ready
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Local cache miss
The worker checks the miss locally and prepares an ordinary GET to the owned origin.
- Atlas client
- GET book-A ready
- Worker + Cache
- Cache miss; network GET ready
- Origin server
- No request; no read
- Origin received request
The prepared GET has arrived; the server public-read gate has not completed.
- Atlas client
- GET book-A ready
- Worker + Cache
- Cache miss; network GET ready
- Origin server
- GET arrived; public gate pending
- Local read response
The server prepares declared public book JSON without using a private session body.
- Atlas client
- GET book-A ready
- Worker + Cache
- Cache miss; network GET ready
- Origin server
- Public JSON response ready
- Response at worker
The HTTP response has arrived at the worker; shape and cache admission remain local decisions.
- Atlas client
- GET book-A ready
- Worker + Cache
- Network body arrived; gate pending
- Origin server
- Public JSON response ready
- Local schema/version
The fixed body passes the selected own-field schema; no storage write is claimed here.
- Atlas client
- GET book-A ready
- Worker + Cache
- Public title response ready; v2
- Origin server
- Public JSON response ready
- Local client outcome
The worker returns its normal response locally and an ordinary title outcome is expected.
- Atlas client
- Literal Tea & Bread; network
- Worker + Cache
- Public title response ready; v2
- Origin server
- Public JSON response ready
Shape denied
- Offline start
The client is offline with a stored candidate; the server has received no request.
- Atlas client
- No title; controlled v2
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Local read intent
The fixed title read is prepared without claiming an opened server connection.
- Atlas client
- GET book-A ready
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Local candidate check
The declared candidate lacks the expected own field; an inherited label cannot satisfy admission.
- Atlas client
- GET book-A ready
- Worker + Cache
- Own shape denied; offline
- Origin server
- No request; no read
- Local fallback
Offline policy selects a plain unavailable result rather than accepting the candidate.
- Atlas client
- GET book-A ready
- Worker + Cache
- Offline fallback ready
- Origin server
- No request; no read
- Client fallback outcome
The client gets a local unavailable outcome, not server authorization denial or a new response.
- Atlas client
- Title unavailable; no origin read
- Worker + Cache
- Offline fallback ready
- Origin server
- No request; no read
Named element
- Fixed DOM ledger
The benign atlasBookForm/bookTitle declaration separates local names and value types.
- Atlas client
- No title; controlled v2
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Named reference
Expected Window/form lookup returns element references rather than a title string.
- Atlas client
- Form/control references; not text
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Local type gate
The app uses retained local configuration, not coercion of an element reference to a string.
- Atlas client
- Local config own title validated
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Local explicit value
Expected control .value and own config title are plain data with different owners.
- Atlas client
- Plain title Tea & Bread ready
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
- Expected text outcome
An ordinary text outcome is expected; no actual named DOM getter or clobbering executes.
- Atlas client
- Literal Tea & Bread; local only
- Worker + Cache
- atlas-v2; public entry
- Origin server
- No request; no read
ACTORS · STATE · DATA FLOW
Worker, cache and client hold separate state
Atlas should read the public title Tea & Bread offline. A worker is not a page script with DOM access: it participates in fetch events for controlled clients from its own worker context and can select a response. Secure-context, same-origin registration and script-directory maximum-scope rules apply; Service-Worker-Allowed can adjust the path limit. That path is not a substitute for an origin security boundary between users. Registration success, installed/waiting/active state and control of a document are separate observations. A page opened before initial activation may require reloading by default; clients.claim and skipWaiting are separate lifecycle choices.
A cache hit does not decide freshness or permission
Cache API stores request/response pairs in named caches managed by the application. It differs from the HTTP cache: Cache-Control freshness or no-store alone does not manage this storage lifecycle. The app chooses admission, versioning, updates, deletion and fallback; browser quota or eviction can remove data. Atlas-v2 admits only public JSON book-A at an exact URL with schemaVersion=2. This selected application gate does not implement Cache.match; Vary and matching options also matter in a real implementation. Parsing a cached body establishes neither fresh permission nor revocation. Mixing session-bound data into this public offline policy could disclose it after account changes or logout.
Own properties and named DOM lookups differ
Require own id/title/schemaVersion fields and expected types on the JSON book object. A readable displayMode inherited from a prototype is not necessarily an input field; Object.hasOwn distinguishes these cases. Prototype pollution concerns unsafe writes or merges changing shared/inherited state later consumed elsewhere; JSON parsing alone is not presented as performing that change. DOM clobbering concerns HTML id/name entering application lookups through legacy Window/form named access, not prototype mutation. The fixed atlasBookForm and bookTitle control produce element references through named access, not title strings. Local module configuration, explicit element lookup and expected element/value types separate these namespaces. Freezing trusted own objects is shallow and is not a general fix for window/document.
2. Red · Trust & Impact
Read every scenario without motion
Offline authority
- Public cache entry
The atlas-v2 public entry with schemaVersion 2 is selected at now 100. Even with online false, the normal row expects cache; the server receives no new request for this display.
- Cache and object ledger
- Public cache / now100 < expires120
- Eligibility and ownership
- Not yet evaluated
- Consumer result
- Not yet evaluated
- HTTP-header distinction
The entry carries Cache-Control no-store, but this Cache API example evaluates eligibility through appExpiresAt. HTTP freshness or automatic header enforcement is not its rule.
- Cache and object ledger
- Public cache / now100 < expires120
- Eligibility and ownership
- Application expiration ≠ HTTP cache policy
- Consumer result
- Not yet evaluated
- Do not count refreshed authz
Returning a cache body is not current server authorization. A stale public title is not automatically compromise; private admission requires separate user/context lifecycle design.
- Cache and object ledger
- Public cache / now100 < expires120
- Eligibility and ownership
- Application expiration ≠ HTTP cache policy
- Consumer result
- Offline body ≠ fresh server grant
- Shape-denied branch
shape-denied has entryPresent true but ownShape false and selects offline-fallback. Local rejection cannot be labelled a server-permission result; there is no network here.
- Cache and object ledger
- Entry present / shape false / fallback
- Eligibility and ownership
- Application expiration ≠ HTTP cache policy
- Consumer result
- Offline body ≠ fresh server grant
- Retain controller assumption
controlledDeclared true and workerVersion 2 are starting assumptions. Scope /books/ is not a hard security boundary or evidence that an old client automatically switched to v2.
- Cache and object ledger
- Entry present / shape false / fallback
- Eligibility and ownership
- Declared controller v2 / lifecycle separate
- Consumer result
- Offline body ≠ fresh server grant
- Report a bounded selection
Cache, network and fallback remain tied to application eligibility. The data ledger does not establish actual Service Worker execution, Cache API writes or durable storage.
- Cache and object ledger
- Entry present / shape false / fallback
- Eligibility and ownership
- Declared controller v2 / lifecycle separate
- Consumer result
- Finite cache selection / runtime unmeasured
Property identity
- Inherited option
inheritedFixture reads displayMode cards but hasOwnDisplayMode false. A readable property does not establish membership in the own-data contract.
- Cache and object ledger
- Read cards / hasOwn false
- Eligibility and ownership
- Not yet evaluated
- Consumer result
- Not yet evaluated
- Closed own schema
The book consumer checks own id, title and schemaVersion fields and types. Presence and value/type differ; Object.hasOwn grants no business permission.
- Cache and object ledger
- Read cards / hasOwn false
- Eligibility and ownership
- Own fields + value/type / no grant
- Consumer result
- Not yet evaluated
- Shallow-freeze effect
outerFrozen is true and nestedFrozen false, with nestedRevisionAfterUpdate 3. Freezing the outer object does not make nested data immutable or retain review scope.
- Cache and object ledger
- Read cards / hasOwn false
- Eligibility and ownership
- Own fields + value/type / no grant
- Consumer result
- Outer frozen / nested revision3
- DOM named reference
atlasBookForm lookup expects HTMLFormElement and bookTitle expects HTMLInputElement. Element references and control value Tea & Bread differ in type; a window name is not local config.
- Cache and object ledger
- Form reference / control reference / value string
- Eligibility and ownership
- Own fields + value/type / no grant
- Consumer result
- Outer frozen / nested revision3
- Separate two mechanisms
DOM named lookup is not prototype mutation. jsonParseMutatesPrototype is false; inherited lookup and HTML namespace lookup have different source and consumer conditions.
- Cache and object ledger
- Form reference / control reference / value string
- Eligibility and ownership
- Prototype lookup ≠ DOM named lookup
- Consumer result
- Outer frozen / nested revision3
- Use explicit references
The consumer reads title through its localConfig reference rather than trusting a truthy global named property as a string. Neither the normal form nor inherited option is presented as executed attack evidence.
- Cache and object ledger
- Form reference / control reference / value string
- Eligibility and ownership
- Prototype lookup ≠ DOM named lookup
- Consumer result
- Explicit config / typed own data
ASSUMPTION · BOUNDARY · CONTROL
Mistaking a persisted response for current authority
A normal cache hit can supply content without a network request, given a selected controlled worker/client and eligible stored entry. The broken assumption is that the server reevaluated freshness or authorization during this display. Effects include stale revisions or, with incorrect admission, private content displayed in the wrong user context; a stale public title is not automatically a compromise. A new worker version does not prove deletion of older entries or control of old clients. Keep public/private classification, content revision, key/context and account lifecycle explicit. The offline shape-denied branch selects a local fallback; no server request means no server permission-denial evidence.
A readable property may not be expected data
Accepting object.title or a Window-level name merely because it is truthy can create a trust error. Prototype paths and DOM named properties require different conditions: unsafe object-state propagation versus relevant HTML names consumed by application code. Impact depends on the confused field and its eventual sink; a named form or benign inherited option is not itself an exploit. The fixed book form is ordinary and harmless, showing only the type distinction between references and title strings. Controls include a closed own-field schema, explicit local configuration and markup namespace policy. Shallow freezing does not make nested objects immutable, and an XSS sanitizer configuration does not automatically resolve every named-property risk.
3. Blue · Secure development & defense
Read every scenario without motion
Selection controls
- Read four outcomes
The rows predict cache for normal/named-dom, network for an online missing entry and fallback for shape-denied. The named-dom scene does not fetch a cache entry; it only examines the fixed form/control reference. Keep matrix expectations separate from the mechanism actually modeled.
- Selection ledger
- Cache/cache / network / fallback
- Source and type review
- Not yet evaluated
- Regression result
- Not yet evaluated
- Cache eligibility record
Request URL, atlas-v2, public classification, schemaVersion 2 and application expiration are retained together. No origin request log does not mean no cache body was seen.
- Selection ledger
- Cache/cache / network / fallback
- Source and type review
- URL/version/public/schema/expiration
- Regression result
- Not yet evaluated
- Own-object control
ownObject carries id book-A, title Tea & Bread and schemaVersion 2. An inherited option with hasOwn false and shallow-freeze behavior are separate ownership controls.
- Selection ledger
- Cache/cache / network / fallback
- Source and type review
- URL/version/public/schema/expiration
- Regression result
- Own fields / inherited false / shallow freeze
- Named-DOM expectation
namedDOM executed is false; form/control interfaces are expected results only. The distinction prevents native object-property tests from being presented as actual DOM named lookup.
- Selection ledger
- Expected interfaces / DOM unexecuted
- Source and type review
- URL/version/public/schema/expiration
- Regression result
- Own fields / inherited false / shallow freeze
- Separate missing field and type
Object.hasOwn can be true for a null value; an accepted schema checks title string and revision types separately. A normal own-object success is not assumed for inherited input.
- Selection ledger
- Expected interfaces / DOM unexecuted
- Source and type review
- Presence ≠ valid value/type
- Regression result
- Own fields / inherited false / shallow freeze
- Bounded regression evidence
Finite selection and object ownership are verified separately. Without Cache API, HTML parser or Service Worker lifecycle execution, these controls prove neither offline-browser success nor current authz.
- Selection ledger
- Expected interfaces / DOM unexecuted
- Source and type review
- Presence ≠ valid value/type
- Regression result
- Finite object/cache predicates / browser separate
Controller gap
- Empty origin log
The artifact carries supplied row predicates for a cache-selection function plus object/lookup ledgers. The network branch models origin receipt; normal and shape-denied never reach origin. named-dom is not another execution of its cache row: it isolates element-reference versus value-string interpretation. Declared controller/version and expected lookups are not actual browser measurements. There is no origin request record while the client shows a title. A normal cache flow can return a body without contacting origin; an empty log cannot establish absence of content.
- Selection ledger
- Title known / origin receipt absent
- Source and type review
- Not yet evaluated
- Regression result
- Not yet evaluated
- Missing controller identity
Current controller, worker version and response-source records are missing. The profile v2 assumption cannot fill the gap; registration and control are separate stages.
- Selection ledger
- Title known / origin receipt absent
- Source and type review
- Current controller/version/source missing
- Regression result
- Not yet evaluated
- Leave source unknown
Cache/network/fallback selection remains UNKNOWN. Displaying Tea & Bread alone establishes neither own-field schema correctness nor private-state lifecycle success.
- Selection ledger
- Title known / origin receipt absent
- Source and type review
- Current controller/version/source missing
- Regression result
- Response source/eligibility UNKNOWN
- Required source information
Client controller, version, request key, cache name and response source must be associated with the same display. Fallback reason and application expiration differ from server receipt.
- Selection ledger
- Needed: controller/version/key/source/reason
- Source and type review
- Current controller/version/source missing
- Regression result
- Response source/eligibility UNKNOWN
- Local-lookup boundary
Finding a named form reference does not prove local configuration changed. Config reference and consumer type evidence are needed; property lookup is not server authorization.
- Selection ledger
- Needed: controller/version/key/source/reason
- Source and type review
- Config reference/type separate from authz
- Regression result
- Response source/eligibility UNKNOWN
- Purpose of new review
New evidence must answer content source and object ownership separately. This event retains the title observation without adding controller, cache-eligibility or DOM-lookup success claims.
- Selection ledger
- Needed: controller/version/key/source/reason
- Source and type review
- Config reference/type separate from authz
- Regression result
- Retain title / collect scoped source evidence
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Observe the cache decision at its owner
The artifact carries supplied row predicates for a cache-selection function plus object/lookup ledgers. The network branch models origin receipt; normal and shape-denied never reach origin. named-dom is not another execution of its cache row: it isolates element-reference versus value-string interpretation. Declared controller/version and expected lookups are not actual browser measurements. Observe client controller identity, worker/cache version, URL, response source and fallback reason together. An absent origin access-log request does not prove the client displayed no content; a cache hit does not reach the origin. Admission evidence records selected public classification and schema revision without logging secrets or private bodies. Response cloning and event.waitUntil support storage work, but a successful clone proves neither successful nor durable writing. Update regression separates old controlled clients, waiting workers, eviction/miss and application-version mismatch. Deleting one named cache does not prove full storage cleanup; any private offline state needs an explicit logout/account-switch lifecycle.
Constrain field ownership and DOM namespaces
Validate accepted own keys and types at the book consumer. Map or null-prototype dictionaries reduce inherited lookup for dynamic data but grant no business permission. Object.hasOwn can return true for null or undefined, so separate presence from value/type checks. Read trusted configuration through local references rather than global named DOM values. The fixed form/control lookup separates the expected interface from its .value string; duplicate names may produce collections or past-name behavior rather than a single string. Regression distinguishes ordinary own objects, missing own fields, benign inherited options and named element references. Pure fixtures or Node property observations do not substitute for DOM named access or Service Worker Cache API execution.
NOW APPLY IT TO THE CASE
Does a cache hit mean fresh server authority or a trusted object?
Atlas v2 public cache selects book-A at now 100 with appExpiresAt 120. A mismatching own-field shape yields offline fallback. A normal form-name lookup expects an HTMLFormElement, while inherited displayMode is an object lookup result; neither is a title string or server authority.
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
Offline cache and object ownership ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"profile": {
"origin": "https://atlas.example.org",
"script": "/books/sw.js",
"scope": "/books/",
"controlledDeclared": true,
"workerVersion": 2,
"cacheName": "atlas-v2",
"publicOnly": true,
"actualSW": false,
"actualCache": false,
"actualDOM": false,
"actualNetwork": false
},
"request": {
"method": "GET",
"url": "https://atlas.example.org/books/book-A.json"
},
"entry": {
"url": "https://atlas.example.org/books/book-A.json",
"cacheName": "atlas-v2",
"visibility": "public",
"body": {
"id": "book-A",
"title": "Tea & Bread",
"schemaVersion": 2
},
"httpHeaders": {
"Cache-Control": "no-store"
},
"appExpiresAt": 120
},
"rows": [
{
"case": "normal",
"now": 100,
"online": false,
"entryPresent": true,
"ownShape": true,
"expected": "cache"
},
{
"case": "network",
"now": 100,
"online": true,
"entryPresent": false,
"ownShape": true,
"expected": "network"
},
{
"case": "shape-denied",
"now": 100,
"online": false,
"entryPresent": true,
"ownShape": false,
"expected": "offline-fallback"
},
{
"case": "named-dom",
"now": 100,
"online": false,
"entryPresent": true,
"ownShape": true,
"expected": "cache"
}
],
"objectLedger": {
"expectedOwnFields": [
"id",
"title",
"schemaVersion"
],
"ownObject": {
"id": "book-A",
"title": "Tea & Bread",
"schemaVersion": 2
},
"inheritedFixture": {
"prototype": {
"displayMode": "cards"
},
"own": {
"title": "Tea & Bread"
},
"readDisplayMode": "cards",
"hasOwnDisplayMode": false
},
"shallowFreeze": {
"outerFrozen": true,
"nestedFrozen": false,
"nestedRevisionAfterUpdate": 3
},
"jsonParseMutatesPrototype": false
},
"namedDOM": {
"declaration": "<form name=\"atlasBookForm\"><input name=\"bookTitle\" value=\"Tea & Bread\"></form>",
"windowLookupExpected": "HTMLFormElement",
"formLookupExpected": "HTMLInputElement",
"controlValueExpected": "Tea & Bread",
"localConfig": {
"title": "Tea & Bread"
},
"lookupGrantsTrust": false,
"executed": false
},
"boundaries": {
"cacheUsesHTTPFreshnessAutomatically": false,
"pathIsHardSecurityBoundary": false,
"offlineResultIsFreshAuthorization": false,
"freezeIsDeep": false,
"DOMNamedLookupIsPrototypeMutation": false
}
}- profile
- Selected controlled-worker and public-only declaration.
- entry
- Request key, application expiration and HTTP headers are distinct.
- rows
- Cache, network or local fallback choice.
- objectLedger
- Own/inherited fields and shallow-freeze expectations.
- namedDOM
- Fixed benign form/control lookup types; not browser execution.
Synthetic public response and predeclared object/DOM ledger; no Cache API or HTML parser executes.
Two reality checks
Cache-Control no-store controls all offline storage.
Cache API is separate author-managed storage requiring admission and deletion policy.
DOM clobbering is the same mutation as prototype pollution.
Named DOM references and prototype-chain state are different namespaces and mechanisms.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
An offline entry exists, but ownShape=false. Does the cache policy accept its body?
- Yes. Offline mode removes all schema checks.
- No. The outcome is offline-fallback.
Read the explained answer
No. The outcome is offline-fallback. — The shape-denied row has entryPresent=true, but withholds the cached body because the object condition failed.
displayMode is readable, but hasOwnDisplayMode=false. Why can the nested revision change while the outer object is frozen?
Selected lesson artifact fields; teaching example
- objectLedger.inheritedFixture
- {"prototype":{"displayMode":"cards"},"own":{"title":"Tea & Bread"},"readDisplayMode":"cards","hasOwnDisplayMode":false}
- objectLedger.shallowFreeze
- {"outerFrozen":true,"nestedFrozen":false,"nestedRevisionAfterUpdate":3}
- Lookup can find an inherited property; freeze is shallow here.
- Every readable property is own; freezing the outer object freezes all nested data.
Read the explained answer
Lookup can find an inherited property; freeze is shallow here. — displayMode comes from the prototype. outerFrozen=true with nestedFrozen=false permits nestedRevisionAfterUpdate=3.
The cache entry has Cache-Control: no-store. Why do explicit Cache API storage and appExpiresAt need a separate policy, and what does named DOM lookup not establish?
Read the explained answer
Cache API does not automatically apply HTTP freshness; the application checks version, expiry and public eligibility. An offline body is not fresh server authorization. Named lookup may return an HTMLFormElement/HTMLInputElement, but that does not establish trusted local config. Named DOM lookup and prototype mutation are different mechanisms. This artifact does not execute a real service worker, cache or DOM operation.
□ I did not equate Cache API policy with HTTP-cache rules.
□ I did not treat named DOM lookup as config or object trust.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- MDN — Using Service WorkersBasic architecture: initial/replacing worker; registration scope; Custom responses and response clone/waitUntil
- W3C Service Workers — cache lifetimes and path restriction§5.2 Understanding Cache Lifetimes; §6.5 Path restriction (non-normative)
- MDN — Cache interfaceDescription: explicit updates/deletion, storage eviction, Vary matching, HTTP-header independence
- MDN — Object.hasOwnDescription; Direct versus inherited properties; null-prototype objects
- MDN — Object.freezeDescription: own data/accessor properties; shallow freezing
- OWASP — Prototype Pollution PreventionSuggested protection: Map/Set, null-prototype objects, freeze/seal limits
- WHATWG HTML — named Window access§7.2.2.3 supported names and single element/collection; explicit lookup guidance
- WHATWG HTML — form named properties§4.10.3 supported property names; named control getter/past-names map
- OWASP — DOM Clobbering PreventionMitigation #1 namespace isolation; #3 local trusted configuration/shallow freeze; #5–8 explicit/local variables and type checking