Skip to content

aggregator(cost-explorer): a setup named '__proto__' is silently never saved — save/import assign into a plain object, so the name becomes the store's prototype instead of an entry #2851

Closed

Description

@dezren39

aggregator(cost-explorer): a setup named __proto__ is silently never saved — the setups store assigns into a plain object, so the name becomes the store's prototype instead of an entry

Evidence (main f318fd59)

The cost explorer's saved-setups store in pm-stack/aggregator/src/render.ts keeps setups in a plain object parsed from localStorage:

  • loadSetups() (:4321) returns JSON.parse(localStorage.getItem("xpl-setups-v1") || "{}") — a plain object carrying Object.prototype.
  • Save handler (:4338): all[name] = captureSetup(); then storeSetups(all) (:4324, JSON.stringify).
  • Import handler (:4359): Object.keys(parsed).forEach(function (k) { all[k] = parsed[k]; }); then storeSetups(all).

Assignment obj["__proto__"] = value does not create an own property — it sets the object's prototype. So a setup whose name is exactly __proto__ never becomes an entry:

Verified (laptop probe, verbatim save/import logic; render.ts md5 2f629fd585f6f281cb3be63ce55ce337 on sandbox main, assignment lines identical in the laptop clone)

Probe: ~/git/fleet/probe-2026-10-30-laneB-xplproto/ (probe.mjs, probe2.mjs)

Save path:

  • save Normal → stored keys ["Normal"]
  • save constructor → stored keys ["Normal","constructor"] (control: assignment does shadow for other prototype spellings)
  • save __proto__ → stored keys still ["Normal","constructor"]; the stored JSON has no __proto__ entry — the setup is silently lost, and the setup list (refreshSetupList, Object.keys(loadSetups())) never shows it.

Import path (file text built as a raw string, the way FileReader delivers it):

  • JSON.parse itself preserves the entry: parsed own keys are ["__proto__","Real"].
  • After the import loop, Object.keys(all) is ["Real"] only; all.tab === "polluted-tab" and Object.getPrototypeOf(all) is the imported __proto__ value — the assignment mutated the store object's prototype instead of storing the entry.
  • The persisted JSON after import is {"Real":{...}} — the __proto__ setup is silently dropped on import too.

Why it matters

Acceptance criteria

  • Saving a setup named __proto__ persists it: it appears in the setup list, survives reload (localStorage round-trip), and exports like any other setup — or the name is rejected with a visible signal; silent loss is the bug.
  • Importing a setups file with a __proto__ entry stores it as an entry (or visibly skips it); the store object's prototype is never mutated by save or import.
  • Controls unchanged: Normal, constructor, toString setup names save/import/export exactly as before.
  • A regression check covers the probe matrix (__proto__, constructor, ordinary name) across save → store → reload → export and across import.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: filed
    Found via: filed this run (new-issue-creator-code-b, 2026-10-30) — verified on the laptop with a probe using the verbatim save/import logic from pm-stack/aggregator/src/render.ts (main f318fd5): saving a setup named __proto__ leaves the stored keys unchanged (Normal/constructor controls persist), and importing a file with a __proto__ entry sets the store object's prototype instead of storing the entry; the persisted JSON drops it in both paths. Probe: ~/git/fleet/probe-2026-10-30-laneB-xplproto/. Write-side counterpart of #2792 (which scopes write-side out); distinct from #2828 (bin/record.mjs).
    Next: triage pins the fix shape (null-prototype store / Object.defineProperty / name guard), then routes the fix.

  3. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — design-architect pass 24 (2026-10-31): ADOPTED as Phase 2 of design #2953 (define, don't assign — the write-side prototype contract) and linked as its sub-issue (verified). Triage: do NOT re-file/re-scope/split; record the phase structure instead. One decision recorded on #2953 narrows your acceptance: preserve, don't reject — a setup named proto must persist, list, export and round-trip like any other (your 'or visibly rejected' alternative is superseded by the design's one-rule-across-surfaces call, so all three surfaces behave identically). Fix shape: define-semantics storage (null-prototype container / fromEntries / defineProperty), never the all[name] = / all[k] = assignment paths. Independent of Phase 1 (#2941).

  4. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. 5 remaining items

  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added a commit that references this issue on Nov 1, 2026
  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/aggregatorPackage/app: aggregator (pm-stack in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions