07-Security-Testing / 07.04.WUI-Security

07.04.WUI Security

07.04. WUI Security

Context

The WUI uses a Pinia store (src/stores/app.ts) that persists certain state to localStorage to survive hard redirects during Stripe/PayPal payment flows and Tesla OAuth authorization. This document explains why this is the standard approach and not a security threat.


What IS Persisted to localStorage

Key Example Value Purpose
cpt-payment-id pi_3Abc... Stripe/PayPal payment intent ID
cpt-selected-plan "basic" Selected pricing tier
cpt-payment-method "stripe" Payment gateway used
cpt-current-step "oauth" Wizard position for resume
cpt-selected-brand-id "tesla" Vehicle brand selection
cpt-selected-model-id "model3" Vehicle model selection

What is NOT Persisted (Correctly Kept Transient)

Field Storage Why
stripeClientSecret memory only Stripe client_secret — sensitive credential
sessionId memory only Tesla OAuth session — one-time use, 15min TTL
vehicleReport memory only User's vehicle data — PII
vehicleData memory only Vehicle specs — tied to user identity

Reference: src/stores/app.ts lines 57-59.


Why Payment IDs in localStorage Are Safe

1. Payment Intent IDs Are Non-Sensitive

A Stripe Payment Intent ID (pi_3Abc...) cannot be used to: - Charge anyone or initiate new payments - Access card numbers, CVVs, or billing addresses - Modify or cancel existing payments - Retrieve sensitive customer data

Stripe explicitly documents that Payment Intent IDs are safe to expose client-side. They are lookup references, not authorization tokens. The same applies to PayPal order IDs.

The actual sensitive credential — Stripe's client_secret — is correctly stored in memory only (stripeClientSecret ref, line 57) and is never written to any persistent storage.

2. This Is the Standard Integration Pattern

Payment gateways (Stripe, PayPal) and OAuth providers (Tesla, Google, etc.) perform hard browser redirects that completely unload the SPA. When the user returns:

Every SPA that integrates payment gateways uses localStorage or sessionStorage to preserve flow context across these redirects. This pattern appears in:

3. The Alternative Is Worse

Without persistence, a user returning from Stripe/PayPal/Tesla would:

  1. Land on a blank page with no context
  2. Lose their wizard position (forced back to landing)
  3. Have no way to correlate the completed payment with the OAuth step
  4. Potentially attempt duplicate payments out of confusion

This is both a UX failure and a financial risk (duplicate charges, abandoned flows).

4. XSS Is the Only Real Attack Vector

localStorage is bound by the Same-Origin Policy — only JavaScript running on the exact same origin (protocol + domain + port) can read it. The only way an attacker can access localStorage data is through a Cross-Site Scripting (XSS) vulnerability on the application's own domain.

However, if an attacker achieves XSS, they can read all client-side storage equally:

Storage Mechanism Accessible via XSS? Accessible Cross-Origin?
localStorage Yes No
sessionStorage Yes No
Cookies (JS-accessible) Yes No (with SameSite)
JavaScript variables Yes No

The defense against this is preventing XSS, not avoiding localStorage. The WUI mitigates XSS through:


Threat Model Summary

Threat Risk Mitigation
Attacker reads localStorage Low Same-Origin Policy blocks cross-origin access
XSS reads payment IDs Low Payment IDs are non-sensitive lookup references
XSS reads stripeClientSecret None Not in localStorage, memory-only
XSS reads Tesla session None Not in localStorage, memory-only
XSS reads vehicle report None Not in localStorage, memory-only
Stale data after tab close Low Data is non-sensitive; reset() clears all
Shared computer reads storage Low Only plan name and payment ID visible

Optional Improvement: sessionStorage

One minor hardening option: migrate from localStorage to sessionStorage for payment-related keys. The behavior difference:

Feature localStorage sessionStorage
Survives redirects Yes Yes (same tab)
Survives tab close Yes No (auto-cleared)
Shared across tabs Yes (same origin) No (per-tab isolation)
Security model Same-Origin Policy Same-Origin Policy

sessionStorage would auto-clear stale payment state when the user closes the tab, reducing the window of data exposure on shared computers. However, functionally both are equivalent in terms of security since neither is accessible cross-origin.

Trade-off: sessionStorage would break the flow if the user opens the callback in a new tab (some mobile browsers do this for OAuth redirects). localStorage is the safer default for redirect-based flows.


Conclusion

Persisting payment intent IDs and UI selections to localStorage is the industry-standard pattern for SPAs integrating payment gateways and OAuth providers. The sensitive credentials (stripeClientSecret, sessionId, vehicleReport) are correctly kept in transient memory. No security remediation is required.