Hacker DirectoryHacker Directory

WEB / WEB-12.03

Browser storage options

Duru’s reading preference, open-tab draft, offline notes, and cover cache are different kinds of state. Choosing storage determines the data model, completion signal, and possibility of loss.

CORE · RED · BLUE · ARTIFACT · PRACTICE

1. Core · Mechanism

Read every scenario without motion

Transaction commit

  1. Draft in memory

    Duru sees the new note; no local commit has occurred. Tab B is unopened.

    Tab A / UI
    UI: unsaved draft
    Local IndexedDB
    Record: previous note
    Tab B
    Unopened
  2. Asynchronous write pending

    The local API scheduled a request inside a transaction. This is not an HTTP transfer.

    Tab A / UI
    UI: saving locally
    Local IndexedDB
    Transaction: pending; previous note committed
    Tab B
    Unopened
  3. Request success, transaction pending

    The request succeeded, but the transaction result is not yet known. The UI does not switch to saved.

    Tab A / UI
    UI: saving locally
    Local IndexedDB
    Request success; transaction pending
    Tab B
    Unopened
  4. Transaction complete

    The transaction committed. The new note is a local record; server acceptance is outside this scene.

    Tab A / UI
    UI: saving locally
    Local IndexedDB
    Committed: new note
    Tab B
    Unopened
  5. UI shows local success

    The completion result was processed; only “saved locally” is justified.

    Tab A / UI
    UI: saved locally
    Local IndexedDB
    Committed: new note
    Tab B
    Unopened

Transaction abort

  1. Previous record is preserved

    The new draft is in memory; the old note is the last committed record.

    Tab A / UI
    UI: unsaved draft
    Local IndexedDB
    Committed: previous note
    Tab B
    Unopened
  2. First request succeeds

    Although the first write reported success, a second change in the same transaction has not completed.

    Tab A / UI
    UI: saving locally
    Local IndexedDB
    Request success; transaction pending
    Tab B
    Unopened
  3. Transaction abort

    In this branch, an unhandled later error aborts the transaction; its changes roll back.

    Tab A / UI
    UI: saving locally
    Local IndexedDB
    Aborted; committed previous note
    Tab B
    Unopened
  4. Draft remains unsaved

    The UI handles failure; the memory draft is preserved without claiming a local save.

    Tab A / UI
    UI: unsaved; retry/export available
    Local IndexedDB
    Aborted; committed previous note
    Tab B
    Unopened

Opener copy

  1. A’s page session

    This case does not use IndexedDB; A holds the initial sessionStorage value.

    Tab A / UI
    sessionStorage: first draft
    Local IndexedDB
    Unused in this case
    Tab B
    Unopened
  2. B opens with an opener

    The same-origin opener relationship is retained. B receives an initial copy; no network transfer occurs.

    Tab A / UI
    sessionStorage: first draft
    Local IndexedDB
    Unused in this case
    Tab B
    sessionStorage: first draft
  3. B changes its own copy

    B writes the second draft. The sessionStorage areas are not one live shared record.

    Tab A / UI
    sessionStorage: first draft
    Local IndexedDB
    Unused in this case
    Tab B
    sessionStorage: second draft
  4. A sees the same value after reload

    In this fixture, A’s page session continues; reload does not delete A’s first draft.

    Tab A / UI
    sessionStorage: first draft; reload survived
    Local IndexedDB
    Unused in this case
    Tab B
    sessionStorage: second draft

ACTORS · STATE · DATA FLOW

Where should the data live?

In the Atlas reading app, a theme preference is a small string. A book note can be a record containing a title, text, and update time. An offline cover is a different type: an HTTP Response. Deciding to keep something in the Browser does not resolve these differences.

These APIs do not automatically attach their contents to matching HTTP requests as cookies do. Application code reads the data and explicitly chooses whether to send it. A locally stored note and a server-accepted note have separate status; this lesson first examines local storage.

localStorage: small string state for an origin

localStorage stores string keys and values, and tabs of the same origin use a shared storage area. An origin is determined by scheme, host, and port; HTTP and HTTPS versions have separate storage. Paths such as /books and /profile are not separate security compartments.

In ordinary use, data can remain after the Browser reopens and has no automatic expiry time. Persistence is not a guarantee against user deletion, privacy settings, or private-browsing behavior. It suits small, reproducible preferences such as a theme; it is a weak assumption for the only copy of a critical document.

Web Storage calls are synchronous. Serializing large data and writing frequently can delay other JavaScript work. Converting an object to a JSON string does not turn the API into a transactional object database.

sessionStorage: tab state that does not always start empty

sessionStorage is partitioned by origin and top-level browsing context, usually the tab. Duru’s form draft in tab A is not continuously shared with tab B. The same page session can survive reload and page restore; refreshing a page is not a storage-clearing command.

If a new window has an opener, its sessionStorage can initially be copied from the opener’s area. The copies then remain independent: writing “second draft” in B does not change “first draft” in A. An initial copy does not mean the tabs share live state. Opening without an opener relationship removes this copying assumption.

Closing the tab/window ends the page session. The word “session” here does not mean the application’s authenticated session; its name says nothing about the lifetime of the server login record.

IndexedDB: asynchronous records and transactions

IndexedDB offers structured records, object stores, and indexes; reads and writes use asynchronous requests. Duru’s offline book notes are a more natural IndexedDB candidate than a theme string. Supported values are stored using structured-clone rules; arbitrary JavaScript object behavior is not preserved. In particular, custom prototype chains and property descriptors are not copied; do not expect a retrieved record to preserve class methods or readonly behavior. Store explicit data fields and define behavior in application code.

A write request’s success event does not mean its containing transaction has completed. Later work in that transaction can fail, and abort can roll back all changes in the transaction. The UI should tie “saved locally” to transaction complete; request success alone is an early signal.

Transaction atomicity defines the commit/abort boundary for changes made together. Permanent physical disk writes are a separate concern; Browser and durability behavior can vary. Transaction complete also says nothing about sending the data to a server.

Cache Storage is distinct from the HTTP cache

Cache Storage holds Request/Response pairs through named caches. An application can store an offline cover Response and select it later. The Cache API is available in window and worker contexts; it does not have to run inside a service worker. It requires a secure context.

Separate normal HTTP-cache freshness rules from the Cache API’s explicit lifecycle. The Cache API does not derive automatic expiry from Cache-Control. A cover Response carrying max-age does not mean its named-cache entry deletes itself when that age passes; the application needs an update and cleanup policy.

A cache miss can be an ordinary outcome. The application can display an offline placeholder or use its normal fetch path when connected. A cache entry is neither a durable record nor a substitute for user authorization.

Capacity and privacy are part of the data flow

Browser storage is finite. A write can fail because of quota; default best-effort IndexedDB and Cache Storage data can be evicted under storage pressure. A persistent-storage request depends on the Browser’s decision and does not prevent user deletion. Do not assume one universal capacity number for every API and Browser.

Storage access in third-party contexts depends on privacy and partitioning policies. Do not generalize a top-level first-party example to every embedded context. An access error, a missing key, and a cache miss are different events.

After a failure, the correct product behavior is to show the local draft as pending/unsaved and provide a retry or export option, rather than reporting success. Important local state also needs a separate synchronization and loss-boundary design.

2. Red · Trust & Impact

Read every scenario without motion

Tab copy

  1. The local state class was selected

    The sessionStorage example carries the initial note-draft values for two tabs. A tab-bound lifetime does not make the data a secret vault or verified identity.

    Browser storage
    Origin: https://atlas.example
    Local record
    Data not examined
    Consumption boundary
    No server permission
  2. The initial data was read

    Tab B opened with an opener receives an initial copy of first draft. Subsequent records in tabs A and B are not a single shared state.

    Browser storage
    Origin: https://atlas.example
    Local record
    A: first draft; B: first draft
    Consumption boundary
    No server permission
  3. The change boundary was shown

    Tab B changes to second draft while tab A remains at first draft. The initial copy is not a continuing synchronization relationship.

    Browser storage
    Origin: https://atlas.example
    Local record
    A: first draft; B: second draft
    Consumption boundary
    No server permission
  4. Local data was not treated as permission

    These values are application input accessible to scripts running in the browser. Presence of a preference or note does not show that a server authenticated a user or granted resource permission.

    Browser storage
    Origin: https://atlas.example
    Local record
    Local data, not authority
    Consumption boundary
    Identity and policy remain required
  5. The consumption control was separated

    If data is submitted to a server, identity, business fields and object/action policy are evaluated there. This scene shows no network submission or server acceptance.

    Browser storage
    Origin: https://atlas.example
    Local record
    Content remains data
    Consumption boundary
    Server outcome unknown
  6. Trust promotion was stopped

    Tab isolation and cloning describe their own data boundaries. Independent drafts do not establish approval in tab A for a change made in tab B.

    Browser storage
    Origin: https://atlas.example
    Local record
    Local data boundary explicit
    Consumption boundary
    No authority established

Data record

  1. The local state class was selected

    The IndexedDB example holds plain note data for book-17. The storage API name or origin boundary does not make that record trusted authority for a server.

    Browser storage
    Origin: https://atlas.example
    Local record
    Data not examined
    Consumption boundary
    No server permission
  2. The initial data was read

    The fields are bookId=book-17 and note=A harmless reading draft. This is note data; the artifact issues no tenant or role grant.

    Browser storage
    Origin: https://atlas.example
    Local record
    bookId=book-17; plain data
    Consumption boundary
    No server permission
  3. The change boundary was shown

    Structured clone carries plain data fields; a custom prototype chain and property descriptors are not preserved. Function values are not supported.

    Browser storage
    Origin: https://atlas.example
    Local record
    Prototype/descriptors not preserved
    Consumption boundary
    No server permission
  4. Local data was not treated as permission

    These values are application input accessible to scripts running in the browser. Presence of a preference or note does not show that a server authenticated a user or granted resource permission.

    Browser storage
    Origin: https://atlas.example
    Local record
    Local data, not authority
    Consumption boundary
    Identity and policy remain required
  5. The consumption control was separated

    If data is submitted to a server, identity, business fields and object/action policy are evaluated there. This scene shows no network submission or server acceptance.

    Browser storage
    Origin: https://atlas.example
    Local record
    Content remains data
    Consumption boundary
    Server outcome unknown
  6. Trust promotion was stopped

    Tab isolation and cloning describe their own data boundaries. Losing a prototype does not make a record safe or permitted; validation continues at its consumption point.

    Browser storage
    Origin: https://atlas.example
    Local record
    Local data boundary explicit
    Consumption boundary
    No authority established

ASSUMPTION · BOUNDARY · CONTROL

A storage name is not a security boundary

In a defensive review, first ask which scripts can access the data. localStorage, sessionStorage, and IndexedDB are accessible to JavaScript running in the origin; tab isolation does not make sessionStorage a secret vault. A secret included in a frontend bundle has already been delivered to the client.

Do not treat placing a session identifier or long-lived bearer credential in localStorage as the same decision as storing a preference. OWASP emphasizes the risk of script access. An HttpOnly cookie limits reading through JavaScript’s cookie API; it does not solve every application security issue, and the Browser can still send the cookie on matching requests.

Treat local records as input

A tenant or role field in Duru’s local note record does not make it trusted authority for the server. Local data can be stale, malformed, or changed by the user. Security review focuses on how the server validates that data.

For caches, review whether personal Responses are bound to an inappropriate shared key and whether old content remains visible after an account change. This lesson’s cache fixture contains only a public cover; personal content needs a separate identity and cleanup policy.

3. Blue · Secure development & defense

Read every scenario without motion

Abort result

  1. A write observation was selected

    Review starts at the request-success stage for the IndexedDB note. Data existing in UI memory and the storage transaction outcome are separate states.

    Write record
    book-17 write observation
    Transaction review
    Completion not examined
    UI conclusion
    No saved conclusion
  2. The request result was separated

    The artifact shows transaction=pending and uiSaved=false at request-success. Success of one request does not establish a commit for the whole transaction.

    Write record
    book-17 write observation
    Transaction review
    request-success; pending
    UI conclusion
    uiSaved=false
  3. Final transaction evidence was read

    This branch has a transaction-abort record. transaction becomes aborted, oldRecord=previous note remains and uiSaved=false is retained.

    Write record
    book-17 write observation
    Transaction review
    transaction=aborted
    UI conclusion
    uiSaved=false
  4. The saved indicator was bounded

    An abort does not support saying the new note was committed. Preservation of the old record does not imply that the new in-memory draft also disappeared.

    Write record
    book-17 write observation
    Transaction review
    No commit
    UI conclusion
    Unsaved state retained
  5. The selector expectation was read

    The main XML fixture counts 0 records combining saved=true with transaction!=committed. A separate positiveFixtureXml demonstrates that inconsistency with one row; it does not change the main branch outcome.

    Write record
    book-17 write observation
    Transaction review
    Main count=0; positive=1
    UI conclusion
    Selector and product outcome separate
  6. The persistence conclusion was preserved

    This branch shows an abort and retention of the old record. A quota cause, visible user warning and server acceptance are not added without separate evidence.

    Write record
    book-17 write observation
    Transaction review
    Abort observed
    UI conclusion
    New record not committed

Request success

  1. A write observation was selected

    Review starts at the request-success stage for the IndexedDB note. Data existing in UI memory and the storage transaction outcome are separate states.

    Write record
    book-17 write observation
    Transaction review
    Completion not examined
    UI conclusion
    No saved conclusion
  2. The request result was separated

    The artifact shows transaction=pending and uiSaved=false at request-success. Success of one request does not establish a commit for the whole transaction.

    Write record
    book-17 write observation
    Transaction review
    request-success; pending
    UI conclusion
    uiSaved=false
  3. Final transaction evidence was read

    This branch examines only request-success; neither transaction-complete nor transaction-abort evidence is supplied. The final outcome remains missing evidence.

    Write record
    book-17 write observation
    Transaction review
    transaction=pending
    UI conclusion
    uiSaved=false
  4. The saved indicator was bounded

    Unknown completion does not support a saved indicator. Pending is not evidence of failure either; completion must be obtained separately.

    Write record
    book-17 write observation
    Transaction review
    Completion unknown
    UI conclusion
    Unsaved state retained
  5. The selector expectation was read

    The main XML fixture counts 0 records combining saved=true with transaction!=committed. A separate positiveFixtureXml demonstrates that inconsistency with one row; it does not change the main branch outcome.

    Write record
    book-17 write observation
    Transaction review
    Main count=0; positive=1
    UI conclusion
    Selector and product outcome separate
  6. The persistence conclusion was preserved

    Request success is known; the transaction outcome remains UNKNOWN. Memory, transaction commit and server acceptance are not collapsed into one “saved” claim.

    Write record
    book-17 write observation
    Transaction review
    Request success observed
    UI conclusion
    UNKNOWN: completion

IMPLEMENTATION · SECURITY CONTROL · VERIFICATION

Tie the storage choice to the state type

Consider localStorage for a small reproducible preference, sessionStorage for a temporary tab draft, IndexedDB for related structured offline records, and Cache Storage for application-managed offline Responses. These are starting choices justified by lifetime and data model, not mandatory pairings.

Tie each success indicator to its boundary. A draft visible in memory, IndexedDB transaction completion, and server acceptance should be separate states. State which one is meant when telling the user that something is saved.

Make failure branches real product states

Preserve the draft in memory and keep the unsaved warning after quota failure or transaction abort. Route cache misses to normal fallback. Cleared Browser storage is not grounds for creating a new authenticated session.

In an isolated fixture, observe the initial opener copy separately from later independent writes. For IndexedDB, inspect the final transaction result rather than one request; a synthetic branch where one of two related changes fails can illustrate the boundary.

Reduce stored secrets and authority

Do not deliver server secrets to the frontend; choose session-credential transport through a separate threat model and prefer established platform session support. Validate business fields stored in client storage on the server. Finding a preference key is not proof of login.

Define cache-version and purge policies, and do not manage private content under the same assumptions as public assets. Observe eviction and access failures using nonsecret categories; raw session credentials or a user’s note text are unnecessary telemetry.

NOW APPLY IT TO THE CASE

How do local data, independent tabs and commits differ?

An Atlas note draft illustrates different boundaries through sessionStorage and IndexedDB. The scene explains why local data is not authority and why a saved indicator needs the appropriate completion evidence.

Which owner holds the case’s browser data? Explain why presence or deletion of a client value differs from the server-side session outcome.

Open the related case file ↗

READ THE EXAMPLE TOGETHER

Atlas local-state decision ledger

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

Read the raw artifact and field explanations
{
  "origin": "https://atlas.example",
  "records": [
    {
      "api": "localStorage",
      "key": "reading-theme",
      "valueType": "string",
      "value": "light",
      "boundary": "origin; shared among its tabs",
      "completion": "synchronous return; access may fail"
    },
    {
      "api": "sessionStorage",
      "key": "note-draft",
      "valueType": "string",
      "tabAInitial": "first draft",
      "tabBWithOpenerInitial": "first draft",
      "tabBEdited": "second draft",
      "tabAAfterEdit": "first draft",
      "boundary": "origin + top-level tab; initial opener copy only"
    },
    {
      "api": "IndexedDB",
      "store": "book-notes",
      "record": {
        "bookId": "book-17",
        "note": "A harmless reading draft"
      },
      "stages": [
        {
          "event": "request-success",
          "transaction": "pending",
          "uiSaved": false
        },
        {
          "event": "transaction-complete",
          "transaction": "committed",
          "uiSaved": true
        }
      ],
      "failureBranch": {
        "event": "transaction-abort",
        "transaction": "aborted",
        "oldRecord": "previous note",
        "uiSaved": false
      },
      "structuredCloneBoundary": {
        "storedDataClass": "plain data fields",
        "customPrototypeChainPreserved": false,
        "propertyDescriptorsPreserved": false,
        "functionValuesSupported": false
      }
    },
    {
      "api": "Cache Storage",
      "cacheName": "public-covers-v1",
      "request": "https://atlas.example/covers/book-17",
      "responseClass": "public cover placeholder",
      "responseCacheControl": "max-age=60",
      "automaticCacheApiExpiry": false,
      "missFallback": "offline placeholder"
    }
  ],
  "selector": {
    "xml": "<storage><write event=\"request-success\" transaction=\"pending\" saved=\"false\"/><write event=\"transaction-complete\" transaction=\"committed\" saved=\"true\"/><write event=\"transaction-abort\" transaction=\"aborted\" saved=\"false\"/></storage>",
    "xpath": "count(/storage/write[@saved=\"true\" and @transaction!=\"committed\"])",
    "expected": 0,
    "positiveFixtureXml": "<storage><write transaction=\"aborted\" saved=\"true\"/></storage>",
    "positiveExpected": 1
  }
}
boundary
The API visibility scope, not an authenticated authorization boundary.
transaction / uiSaved
Local success is tied to the transaction result, not merely request success.
automaticCacheApiExpiry
A named-cache expiry is not implied by Cache-Control.
selector
A local consistency selector over two synthetic XML fixtures; it is not a Browser-storage measurement.

Assumes a top-level first-party origin. Actual quota, disk flush, embedded privacy, and Browser storage API execution were not measured. Cover and record contents are inert placeholders.

Two reality checks

sessionStorage starts empty in every new tab and clears on reload.

An opener can cause an initial copy; storage areas are then independent. The same page session can survive reload and restore.

IndexedDB request success or Cache-Control max-age proves data will remain safely for the required time.

The IndexedDB transaction result is separate from physical durability. The Cache API does not derive automatic expiry from Cache-Control; quota, eviction, and user deletion remain separate concerns.

YOUR TURN

Work through this story.

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

IndexedDB request-success arrived but the transaction is pending. May the UI report saved?

  1. Yes; every successful request is a durable transaction commit.
  2. No; the selected saved gate waits for transaction-complete.
Read the explained answer

No; the selected saved gate waits for transaction-complete. — Request success does not rule out a later transaction abort; uiSaved=false preserves the pending state.

Why does editing tabB not change tabA after tabB receives an initial draft copy from its opener?

Selected lesson artifact fields; teaching example

records[1]
{"api":"sessionStorage","key":"note-draft","valueType":"string","tabAInitial":"first draft","tabBWithOpenerInitial":"first draft","tabBEdited":"second draft","tabAAfterEdit":"first draft","boundary":"origin + top-level tab; initial opener copy only"}
  1. Tab-scoped sessionStorage copies are independent afterward.
  2. An initial copy makes the two tabs share one object forever.
Read the explained answer

Tab-scoped sessionStorage copies are independent afterward. — tabBEdited is second draft while tabAAfterEdit remains first draft; origin+top-level-tab boundaries persist.

Does a public-cover response with max-age 60 automatically expire its Cache Storage entry? Also explain quota/server-record boundaries.

Read the explained answer

Cache Storage stores explicit Request/Response objects; automaticCacheApiExpiry=false. The application must establish eviction/version/eligibility policy. Quota/access failures are possible; a local entry is neither server-session authority nor a server-record commit. The offline placeholder is a selected miss fallback, not a remote-body observation.

□ I did not count Cache API as an HTTP-freshness engine.

□ I did not count local-storage success as a server commit.

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

Sources and scope