Skip to content

Stop persisting raw Cashu proofs in localStorage#438

Open
ayushshrivastv wants to merge 2 commits into
shopstr-eng:mainfrom
ayushshrivastv:secure-cashu-proof-storage
Open

Stop persisting raw Cashu proofs in localStorage#438
ayushshrivastv wants to merge 2 commits into
shopstr-eng:mainfrom
ayushshrivastv:secure-cashu-proof-storage

Conversation

@ayushshrivastv

@ayushshrivastv ayushshrivastv commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Metadata Leakage: When a user redeems a Cashu token, Shopstr merges the newly received Proof[ ]objects into the existing wallet state and writes the entire array directly into localStorage under the tokens key. These are not harmless balance records; elsewhere in the app the proofs are matched by proof.secret, confirming that the stored objects contain live bearer secrets.

Cashu proofs are effectively spendable ecash, so persisting them unencrypted in browser storage exposes wallet funds to any JavaScript running in the Shopstr origin. An XSS bug, compromised frontend dependency, or malicious extension can exfiltrate the stored proofs and redeem them elsewhere, resulting in direct theft of the user’s Cashu balance.

FILE: components/utility-components/claim-button.tsx (line ~208)

  // components/utility-components/claim-button.tsx
  const uniqueProofs = proofs.filter(
    (proof: Proof) => !tokens.some((token: Proof) => token.C === proof.C)
  );

  await publishProofEvent(
    nostr!,
    signer!,
    tokenMint,
    uniqueProofs,
    "in",
    tokenAmount.toString()
  );

  const tokenArray = [...tokens, ...uniqueProofs];
  localStorage.setItem("tokens", JSON.stringify(tokenArray)); // dangerous:
  persists raw Cashu proofs in browser-readable storage

Before this PR, raw Cashu proofs were being written directly into browser localStorage under the tokens key from wallet, claim, checkout, and recovery flows. Since those proofs contain the spendable secrets themselves, that meant the wallet balance was being persisted as plaintext in browser storage.

After this PR, I changed that flow so Cashu proofs are kept only in runtime memory for the active session instead of being saved in localStorage. I also updated the existing wallet mutation paths to use one shared helper for token updates, added cleanup for any legacy persisted proof arrays on read, and added regression tests to make sure the wallet still works with the new storage behavior.

@vercel

vercel Bot commented Apr 19, 2026

Copy link
Copy Markdown

@ayushshrivastv is attempting to deploy a commit to the shopstr-eng Team on Vercel.

A member of the Team first needs to authorize it.

@GautamBytes GautamBytes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants