Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

purgeSignedOutClientCaches's try wraps the whole loop, so one throwing removeItem silently cancels the rest of the sign-out sweep #5777

Closed

Description

@os-zhuang

Found while implementing #5763 (sweepStore's try wraps the whole loop — same defect, sign-in path). Same defect class, different function and different caller (sign-out rather than sign-in), so it is filed rather than fixed on that PR — #5763's own scope note rules out widening into a general refactor of the module, and this function lives in a different file (AuthProvider.tsx, not ActiveOrganizationStorage.ts).

The shape

packages/auth/src/AuthProvider.tsx, purgeSignedOutClientCaches():

function purgeSignedOutClientCaches(): void {
  if (typeof sessionStorage !== 'undefined') {
    try {
      // Snapshot the keys first (`Object.keys`) — removing entries during a
      // live index walk shifts the ones behind it and skips half of them.
      // Same idiom as the `MarketplacePackagePage` purge loop.
      for (const key of Object.keys(sessionStorage)) {
        if (key.startsWith(METADATA_SEED_CACHE_PREFIX)) {
          sessionStorage.removeItem(key);
        }
      }
    } catch {
      /* storage unavailable */
    }
  }
  ActiveOrganizationStorage.clear();
}

The try wraps the whole loop, not each removeItem. A removeItem that throws on key n aborts the walk (and — unlike sweepStore — this loop is a prefix filter, so it also never reaches the still-unvisited non-matching keys after n, though those were never candidates for removal anyway), so metadata-seed-cache keys after n are never swept and the failure is swallowed.

Why it matters

This is called from AuthProvider on sign-out (purgeSignedOutClientCaches(); before ActiveOrganizationStorage.clear()). Per its own docstring, the entries it clears are MetadataProvider's org-scoped seed cache — a permission-filtered app list that #5198 classifies as a cross-principal disclosure risk, not mere staleness, when it survives into the next session on a shared browser. A partial sweep leaves an arbitrary subset of that cache behind, depending on Object.keys iteration order and where the throw lands.

Reachability

Not demonstrated here either, for the same reason #5731 and #5763 could not reach it from a browser: the state needed is Object.keys succeeding while some removeItem throws. #5763's triage graded that card anyway because the remedy restores an invariant and needs no product decision — the identical argument applies here.

Possible shape

Same remedy as #5763: move the try inside the loop so one uncooperative key costs one key; keep Object.keys(sessionStorage) inside its own guard. Whether a failed sweep should additionally be reported (console.warn, mirroring #5731 and #5763's choice) is the same open question, for the sign-out path this time — #5763's PR discusses why sign-out specifically cannot act on the failure (session already ended).

Scope note

Deliberately not fixed on the #5763 PR — different file, different caller, and #5763's own card text rules out widening into a general storage-error-handling refactor of the module.

Blocked-by: none. Related: #5763, #5731, #5664, #5198.

Activity

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 23, 2026
  2. added theissue type on Aug 23, 2026
  3. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Triage: pm:queue + domain:ui, type Bug — inherits #5763's grading and remedy basis: same defect class (whole-loop try), remedy restores an invariant with no product decision, and unproven browser reachability did not gate the sibling, so it does not gate this one. The sign-out variant is the sharper half: the surviving entries are the org-scoped seed cache #5198 classifies as a cross-principal disclosure risk on shared browsers.

    Remedy shape per the card: try inside the loop (one uncooperative key costs one key), Object.keys under its own guard; the report-vs-silent question follows #5763's PR discussion for the sign-out path. Same-day churn: #5763's PR merged today — branch from a ref carrying it and re-verify AuthProvider.tsx anchors.


    Generated by Claude Code

  4. claude commented on Aug 23, 2026

    @claude
    Contributor

    Claim — domain:ui execution seat.

    Dispatching this round (R27, concurrency 5). Triage's same-day-churn note is discharged: #5763's PR #5779 merged at 07:56:17Z, so its landed answer to the report-vs-silent question is on main and the dev reads it there rather than from the PR discussion.


    Generated by Claude Code

  5. added 2 commits that reference this issue on Aug 23, 2026
    da392fb
    c0b7876
  6. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    os-dev-report

    {
      "issue": 5777,
      "status": "done",
      "branch": "claude/issue-5777-signout-purge-per-key-try",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5816",
      "premise_still_valid": true,
      "summary": "purgeSignedOutClientCaches() in packages/auth/src/AuthProvider.tsx wrapped its whole removeItem loop in one try, so a removeItem throwing on key n silently aborted the sweep and left every objectui:metadata:* key after n unswept. Moved the try to guard only the Object.keys(sessionStorage) snapshot, gave each removeItem its own try, and added a console.warn naming any unswept keys on a partial sweep -- mirroring #5763's landed sweepStore shape on main (ff2d54717) exactly, including its report-not-quarantine choice, since this function likewise owns no reads for the metadata seed cache it walks.",
      "tests": "pnpm exec vitest run packages/auth/src/__tests__/signOut-client-cache-purge-5198.test.tsx --maxWorkers=2 (run from repo root per AGENTS.md, never package-cwd): 6/6 passed on the fix. Reverse-verification on a real commit: committed the fix (da392fbbd), then git checkout HEAD~1 -- packages/auth/src/AuthProvider.tsx to restore the pre-fix whole-loop-try shape and reran the same file -> 1 failed / 5 passed, with the new partial-failure test failing exactly as expected (expected '[{\"name\":\"account\"}]' to be null); the other 5 pass either way since they don't exercise a throwing removeItem. Restored via git checkout claude/issue-5777-signout-purge-per-key-try -- packages/auth/src/AuthProvider.tsx, confirmed git diff HEAD --stat empty, reran -> 6/6 passed. Full package pnpm exec vitest run packages/auth/ --maxWorkers=2 -> 24 files / 251 tests passed, on c0b7876b (PR head). pnpm --filter '@object-ui/auth' type-check (tsc --noEmit && tsc -p tsconfig.test.json, after building the dependency closure with pnpm --filter '@object-ui/auth^...' build) -> clean. node scripts/check-changeset-presence.mjs, check-control-bytes.mjs, check-type-check-coverage.mjs, check-lint-coverage.mjs -> all passed. eslint on the two changed files -> 0 errors, 3 pre-existing warnings unrelated to this diff (react-hooks/set-state-in-effect at lines 300/760/767, outside the edited region). CI on the PR is in_progress at report time; PM to read gate convergence.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. added a commit that references this issue on Aug 23, 2026
    2dd9443
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:queue

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions