Date: March 2026
Version: 0.4.0
Scope: Comprehensive review of all src/ files, manifest.json, and build configuration
0 Critical 0 High 0 Medium 0 Low open (4 accepted risks: XIV-2, XVI-7, XVI-8, XVI-9, XVI-10)
Hardening I–VIII (historical): vault encryption, CSP, rate limiting, origin checks, genesis hash verification, spending caps, WC chain guard, byte truncation.
Hardening IX (v0.1.3 — March 2026): MPP (Machine Payments Protocol) implementation + full re-audit. All new findings closed in same pass.
Hardening X (v0.1.4 — March 2026): AP2 (Google Agent Payments Protocol) implementation + 3-agent parallel audit. All new findings closed in same pass.
Hardening XI (v0.2.0 — March 2026): SpendingCapVault feature (AVM smart contract + WalletConnect owner actions). Full re-audit + Comet AI dual-validation. All 8 new findings closed in same pass.
Hardening XII (v0.3.0 — March 2026): Haystack Router DEX swap integration + WalletConnect signing hardening + ASA metadata cache. Full security audit + independent Comet CDP validation (all 7 claims CONFIRMED). All 3 new findings closed in same pass.
Hardening XIII (v0.3.1 — March 2026): WalletConnect swap reliability + MV3 vault lock hardening. Dead-session detection (settle delay + session expiry check + 2-min relay timeout), vault keep-alive during WC signing, MV3 SW suspension lock correctly surfaced to UI. All 4 new findings closed in same pass.
Hardening XIV (v0.4.0 — March 2026): chromeStorage WC adapter, 30-day mnemonic import, vault ASA support, contract recompile. Full security re-audit + independent Comet CDP validation. Findings:
- H5 (HIGH):
.envcontains live Haystack API key — verify never committed to git history. Status: OPEN — requires manual verification. - XIV-1 (MEDIUM): Secret keys (
getActiveSecretKey(),getAgentSecretKey()) not zeroed after signing —.fill(0)infinallyblocks recommended. Status: OPEN. - XIV-2 (MEDIUM): Haystack API key embedded in build bundle — treat as public, rate-limit server-side. Status: OPEN.
- XIV-3 (LOW): WC session topic logged verbatim in
web3wallet-handler.tsconsole.info — usesanitizeTopic(). Status: CLOSED. - Comet CDP validated: AES-GCM-256 + PBKDF2 600k meets OWASP 2025 guidance. 30-day mnemonic TTL is stricter than MetaMask/Phantom (no TTL). chromeStorage adapter is sandboxed per-extension.
Hardening XV (v0.4.0 — March 2026): Anti-phishing defences. All additive — no existing behaviour changed.
- Clipboard hijacking detection: onPaste handler compares pasted address against live clipboard content; warns if malware swapped the address.
- Homograph domain detection: content script flags non-ASCII Unicode in hostnames (Cyrillic а, Greek ο, etc.) before ARC-0027 enable; confusable character database included.
- Transaction simulation:
simulateTransaction()wraps algod/v2/transactions/simulatefor pre-sign preview of balance changes and failure detection. - Secret key wiping:
.fill(0)after signing in all 7 handler paths (message-handler, x402, mpp, ap2, swap, web3wallet). - Comet CDP independently validated anti-phishing architecture.
Hardening XVI (v0.4.0 — March 2026): Full security audit with 37 automated live tests + independent Comet CDP cross-validation. Findings:
- XVI-1 (HIGH): Authorization header forwarded on x402/MPP fetch retry — malicious 402 endpoint could capture Bearer tokens. Status: CLOSED.
Authorizationadded to stripped headers list in both MPP and x402 retry paths insrc/inpage/index.ts. - XVI-2 (MEDIUM):
sk.fill(0)not intry/finally— exception during signing skips key wipe. Status: CLOSED. All 4 affected handlers wrapped intry/finally:x402-handler.ts,mpp-handler.ts,ap2-handler.ts,swap-handler.ts. - XVI-3 (MEDIUM): Debug log entries persisted indefinitely in
chrome.storage.local. Status: CLOSED. 7-dayMAX_AGE_MSauto-expiry filter added todebug-log.tsflush cycle. - XVI-4 (MEDIUM): CSP
img-srcused wildcardhttps://*.walletconnect.comallowing any subdomain. Status: CLOSED. Pinned to explicit subdomains:verify,registry,explorer-api. - XVI-5 (LOW): 30+
console.log/warncalls in production — overridable by malicious page scripts. Status: CLOSED. Terser configured invite.config.tsto stripconsole.log/warn/info/debugin production builds;console.errorpreserved. - XVI-6 (LOW):
WC_PROJECT_IDsilently defaults to empty string. Status: CLOSED.console.errorvalidation added tosrc/background/index.tsservice worker startup. - XVI-7 (LOW):
tabspermission grantstab.url/tab.titlefor all tabs. Status: ACCEPTED. Required forchrome.tabs.query(chain-change broadcast) andchrome.tabs.get(origin validation in x402/MPP handlers). Cannot be replaced withactiveTab. - XVI-8 (INFO): Lock state detectable via error messages ("Wallet is locked" vs "not connected"). Comet CDP confirmed MetaMask has identical behaviour — standard practice, not exploitable.
- XVI-9 (INFO): Extension detectable via
window.algorand+web_accessible_resources. Comet CDP confirmed this is inherent to ARC-0027 spec and MV3 architecture — not a vulnerability unless exposed resources contain XSS. - XVI-10 (INFO):
postMessagetraffic between inpage and content scripts observable by page scripts. Comet CDP confirmed this is an unavoidable MV3 limitation. No secrets transit the channel — keys remain in background service worker only. - Comet CDP independently validated: PBKDF2 static salt is per-wallet unique (standard OWASP practice, not a weakness); Authorization header leak is a real credential theft vector;
safeCap()already hasNumber.isFiniteguard.
| ID | Severity | Status | Description |
|---|---|---|---|
| C1 | Critical | ✅ CLOSED | genesisID check before ARC27_SIGN_TXNS signing |
| C2 | Critical | ✅ CLOSED | Unlock rate-limit moved to chrome.storage.session |
| C3 | Critical | ✅ CLOSED | Vault persistence confirmed correct |
| H1 | High | ✅ CLOSED | Genesis hash verified on WALLET_SET_CHAIN |
| H2 | High | ✅ CLOSED | connectedSites encrypted in vault; migration on unlock |
| H3 | High | ✅ CLOSED | Per-origin x402 queue capped at 5 pending requests |
| H4 | High | ✅ CLOSED | mcp-client.ts spending cap uses safeCap() guard |
| M1 | Medium | ✅ CLOSED | uint64 overflow check in parseDecimalToAtomic |
| M2 | Medium | ✅ CLOSED | Duplicate txn errors swallowed; real failures propagate (x402 + MPP) |
| M3 | Medium | ✅ CLOSED | Session headers stripped on x402/MPP retry |
| M4 | Medium | ✅ CLOSED | Dead-code secureCompare() with timing leak removed |
| M5 | Medium | ✅ CLOSED | Spending caps read from meta.spendingCaps with safeCap() defaults |
| M6 | Medium | ✅ CLOSED | CSP connect-src added; default-src 'none' baseline set |
| M7 | Medium | ✅ CLOSED | ARC27_SIGN_AND_SEND validates Array.isArray(txns) before use |
| L1 | Low | ✅ CLOSED | HTTPS origin guard in routeToBackground() |
| L2 | Low | ✅ CLOSED | Extension pages not frameable; confirmed |
| L3 | Low | ✅ CLOSED | frame-ancestors 'none' in approval/index.html |
| L4 | Low | ✅ CLOSED | Storage quota errors caught and re-thrown |
| L5 | Low | ✅ CLOSED | CHAIN_SUBMIT_SIGNED requires unlocked wallet |
| L5* | Low | ✅ CLOSED | WC session localStorage cleared on lock |
| L6 | Low | ✅ CLOSED | CSP correct for MV3; confirmed |
| L7 | Low | ✅ CLOSED | Note field: encode-then-slice to 1000 bytes (x402, MPP, CHAIN_SEND_*) |
| L8 | Low | ✅ CLOSED | frame-src https://verify.walletconnect.org added to CSP |
| L9 | Low | ✅ CLOSED | algosdk v3 result.txid field name corrected |
| L10 | Low | ✅ CLOSED | MPP duplicate txn detection upgraded to anchored word-boundary regex |
| L11 | Low | ✅ CLOSED | alarms permission removed (was declared but never used) |
| L12 | Low | ✅ CLOSED | @modelcontextprotocol/sdk phantom dependency removed |
| I1 | Info | ✅ CLOSED | MPP amount > 0 + decimals 0–19 validated at parse time |
| I2 | Info | ✅ CLOSED | MPP TTL 6-min safety cleanup if popup crashes |
| I3 | Info | ✅ CLOSED | MPP recipient address truncated in approval UI |
| I4 | Info | ✅ CLOSED | Dual-protocol warning if both MPP + x402 headers present |
| SW-1 | Low | ✅ CLOSED | swap-handler.ts::parseDecimal — uint64 overflow check added |
| SW-2 | Low | ✅ CLOSED | executeSwap — address ownership + WC account type assertion before secret key use |
| FIND-B | Low | ✅ CLOSED | SwapPanel.tsx::parseDecimal — uint64 overflow guard added to match background |
| XIII-1 | Medium | ✅ CLOSED | WC swap: dead-session detection — 1.5 s relay settle + session.get() + expiry check + 2-min relay timeout with re-pair message |
| XIII-2 | Low | ✅ CLOSED | WC swap: vault auto-locks during signing wait — KEEP_ALIVE message + 30 s popup interval prevents MV3 5-min lock |
| XIII-3 | Medium | ✅ CLOSED | MV3 SW suspension silently locks vault — sendBg() detects "Wallet is locked" and fires algovou:wallet-locked DOM event; App.tsx shows unlock screen |
| XIII-4 | Low | ✅ CLOSED | WC swap: useWalletConnect hook diverged from wc-sign.ts (cached client, settle delay, session guard) — replaced with standalone wc-sign-group.ts mirroring proven pattern |
| H5 | High | ✅ CLOSED | .env verified never committed to git history (git log --all -- .env = empty); .gitignore covers .env, .env.local, .env.*.local |
| XIV-1 | Medium | ✅ CLOSED | Secret keys zeroed with .fill(0) after signing in all 7 handlers: message-handler (send, ASA send), x402-handler, mpp-handler, ap2-handler, swap-handler, web3wallet-handler |
| XIV-2 | Medium | ℹ️ ACCEPTED | Haystack API key compiled into bundle — accepted risk for client-side API keys; rate-limited server-side by Haystack |
| XIV-3 | Low | ✅ CLOSED | WC session topic truncated to 8 chars in console.info calls (topic.slice(0, 8)…) |
| XVI-1 | High | ✅ CLOSED | Authorization header stripped on x402/MPP fetch retry — prevents credential theft by malicious 402 endpoints |
| XVI-2 | Medium | ✅ CLOSED | sk.fill(0) wrapped in try/finally in x402, mpp, ap2, swap handlers |
| XVI-3 | Medium | ✅ CLOSED | Debug log 7-day auto-expiry (MAX_AGE_MS) added to flush cycle |
| XVI-4 | Medium | ✅ CLOSED | CSP img-src wildcards replaced with explicit WalletConnect subdomains |
| XVI-5 | Low | ✅ CLOSED | console.log/warn/info/debug stripped from production builds via terser |
| XVI-6 | Low | ✅ CLOSED | WC_PROJECT_ID validated non-empty at service worker startup |
| XVI-7 | Low | ℹ️ ACCEPTED | tabs permission required for chain-change broadcast and x402/MPP origin validation |
| XVI-8 | Info | ℹ️ ACCEPTED | Lock state oracle via error messages — same as MetaMask, standard practice |
| XVI-9 | Info | ℹ️ ACCEPTED | Extension fingerprinting — inherent to ARC-0027 spec |
| XVI-10 | Info | ℹ️ ACCEPTED | postMessage eavesdropping — MV3 architectural limitation, no secrets in transit |
src/background/mpp-handler.ts— parsesWWW-Authenticate: Paymentheader, builds/signs AVM txn, opens approval popupsrc/inpage/index.ts— MPP detection in fetch interceptor before x402 checksrc/approval/index.tsx—MppPagecomponent
Amount validation (I1): MPP amount > 0 and decimals within 0–19 now validated in decodeMppAvmRequest() — before queuing, not at sign time. Zero/negative amounts rejected immediately.
TTL cleanup (I2): handleMpp() sets a 6-minute setTimeout safety cleanup for _pendingMppRequests in case the popup crashes without sending MPP_APPROVE/MPP_REJECT.
Recipient truncation (I3): MppPage shows addr.slice(0,8)…addr.slice(-8) with full address in title tooltip.
Dual-protocol warning (I4): console.warn emitted if both WWW-Authenticate: Payment and PAYMENT-REQUIRED headers appear on the same 402 response.
Note byte truncation (L7): All note fields encode to UTF-8 bytes first, then .slice(0, 1000) — prevents splitting multi-byte characters.
Duplicate txn regex (L10): Upgraded from broad String.includes("already") to anchored word-boundary regex /\b(already in ledger|txn already exists|duplicate transaction|transaction already)\b/i in both x402 and MPP handlers.
Spending caps (H4, M5): safeCap() helper validates stored cap values before BigInt conversion — guards against zero, negative, NaN, or Infinity from corrupted storage. Applied to x402, MPP, and mcp-client handlers.
src/background/ap2-handler.ts— verifies CartMandate, builds/signs PaymentMandate (SHA-256 hash + ed25519), stores IntentMandatessrc/shared/types/ap2.ts— AP2 type definitionssrc/inpage/index.ts—window.algorand.ap2.requestPayment()andgetIntentMandates()src/approval/index.tsx—Ap2Pagecomponent with expiry countdown
| Property | Implementation |
|---|---|
| No AVM transaction | AP2 signs a credential only — no on-chain transaction submitted |
| CartMandate stored in full | Full CartMandate retained in PendingAp2Approval for correct SHA-256 hash |
| No MX prefix | signBytes called without ARC-1 MX prefix — AP2 credentials are not ARC-0027 operations |
| WalletConnect blocked | AP2 signing rejected for WC accounts (no vault key access) |
| Queue cap | Per-page limit of 5 pending AP2 approval requests |
| TTL cleanup | 6-minute safety cleanup if approval popup crashes |
| Expiry enforcement | Ap2Page disables Approve button when CartMandate.expiry has passed |
| IntentMandates | Stored in chrome.storage.session — cleared on browser close, capped at 100 entries |
provider-bridge.ts ARC27_SIGN_AND_SEND case now validates Array.isArray(payload.txns) before destructuring, preventing an uncaught TypeError if a malicious page passes null.
@modelcontextprotocol/sdk removed from package.json — was listed in dependencies but never imported. MCP interaction uses raw fetch() JSON-RPC calls in mcp-client.ts.
src/background/vault-store.ts— AVM SpendingCapVault contract interaction (deploy, setup, owner actions, state reads)src/popup/components/VaultPanel.tsx— WalletConnect vault deployment and owner action UI (2-round signing)src/background/message-handler.ts— 5 new vault case handlers + 3 WC submit handlers
WC vault transaction binding (H1): A compromised WalletConnect relay could substitute a different transaction (e.g. rekey, drain) after the background validates and returns an unsigned txn to the popup. Fix: _pendingVaultWcBinding Map stores the expected unsigned txn bytes (as base64) keyed by WC session topic with a 5-minute TTL. All three WC submit handlers (VAULT_WC_SUBMIT_CREATE, VAULT_WC_SUBMIT_SETUP, VAULT_WC_ACTION_SUBMIT) decode the signed txn, re-encode the unsigned bytes, and compare against the binding before submitting to the node. Binding is deleted immediately after validation.
URL-safe base64 decoding (H2): atob() rejects URL-safe base64 (uses - and _) returned by Defly and Lute wallets, causing silent failures. Fix: All WC txn decoding replaced with base64ToBytes() from @shared/utils/crypto, which normalises padding and URL-safe characters before decoding. Applied in all 3 WC submit handlers and all 3 decode sites in VaultPanel.tsx.
AP2 queue cap origin comparison (M1): Per-origin pending request cap was comparing full URLs — different paths on the same site counted as separate origins, bypassing the cap. Fix: cap now extracts .origin via new URL(url).origin for both the incoming request and each queued request. Falls back to exact string match when URL parsing fails (conservative, does not weaken the cap).
IntentMandates session storage (M2): AP2 IntentMandate objects contain payment metadata (amounts, merchant identity, address correlations) and were stored in chrome.storage.local — persisted unencrypted to disk. Fix: moved to chrome.storage.session (cleared when browser closes) with a 100-entry rolling cap. All three helpers (getIntentMandates, storeIntentMandate, removeIntentMandate) updated.
Withdraw address validation (L1): ownerWithdraw() would attempt an on-chain transaction with an invalid receiver address, wasting a fee. Fix: algosdk.isValidAddress(msg.receiver) pre-flight check added before calling ownerWithdraw().
Auto-lock reset on vault poll (L2): VAULT_GET_STATE is polled continuously by the VaultPanel but was not calling resetAutoLock(), so the auto-lock timer could fire while the user was actively using the vault UI. Fix: resetAutoLock() added to VAULT_GET_STATE handler.
µAlgo formatting precision (L3): The fmt() helper in vault-store used plain .toString() on the fractional µAlgo remainder, dropping leading zeros (e.g. 1_000_001n rendered as "1.1" instead of "1.000001"). Fix: .padStart(6, "0").replace(/0+$/, "") — pads to 6 digits then strips trailing zeros only.
Static getAlgodClient import (C3): Three vault WC submit handlers used await import("./chain-clients") dynamically, adding latency and risking silent breakage on module rename. Fix: getAlgodClient promoted to a static top-level import alongside other chain-client helpers.
All 8 findings validated by local grep scan (19/19 checks PASS) and independently by Comet AI dual-validation (19/19 checks PASS).
src/background/swap-handler.ts— DEX swap quote + execution; all signing stays in service workersrc/popup/components/SwapPanel.tsx— Swap UI; WC path runsRouterClientin popup withsignGroupIndexedsrc/shared/utils/asset-cache.ts— Persistent ASA metadata cache inchrome.storage.localsrc/popup/hooks/useWalletConnect.ts— NewsignGroupIndexedmethod for grouped WC signingmanifest.json—hayrouter.txnlab.devadded tohost_permissionsandconnect-src
SW-1 (Low) — uint64 overflow in swap-handler.ts::parseDecimal: The popup copy of parseDecimal lacked the AVM uint64 maximum check (> 18_446_744_073_709_551_615n) present in parseDecimalToAtomic in message-handler.ts. Extremely large amounts would reach Haystack/algosdk and produce an opaque error rather than a clear rejection. Fix: uint64 overflow check added to parseDecimal in swap-handler.ts.
SW-2 (Low) — No address ownership assertion in executeSwap: executeSwap accepted params.address from the popup without verifying it matched the active account's address. A stale popup state (e.g. account switched mid-session) could cause the background to sign a swap for a different vault key than expected. Not exploitable via content scripts (SWAP_EXECUTE is not in the inpage switch). Fix: executeSwap now reads walletStore.getMeta(), asserts activeAccount.address === params.address, and explicitly rejects walletconnect account types with a clear error before calling getActiveSecretKey().
FIND-B (Low) — SwapPanel.tsx::parseDecimal diverged from background: The UI copy of parseDecimal was missing the uint64 overflow guard added in SW-1, creating a maintenance divergence where a future UI-side enforcement path could silently accept overflowing values. Fix: uint64 overflow check added to SwapPanel.tsx::parseDecimal to keep both copies identical.
- WC session staleness guard: All three signing methods in
useWalletConnect.ts(signTransaction,signGroup,signGroupIndexed) now callclient.session.get(sessionTopic)before sending to the relay — throws immediately with an actionable "session expired, reconnect" error if the session is stale. - WC signing timeout: 60-second
Promise.raceon all three signing methods — prevents indefinite hang if the user's phone is unreachable. - ASA batch rate-limiting: Asset metadata fetches batched at 5 per 150ms gap to avoid 429 errors from Algorand indexer.
- Asset cache write error handling (W2):
writeAssetCachenow uses callback form ofchrome.storage.local.set()and checkschrome.runtime.lastError. - algodUri pinned in both paths: Both
swap-handler.ts(background) andSwapPanel.tsx(popup WC path) hardcodealgodUri: "https://mainnet-api.algonode.cloud"— prevents the RouterClient default (mainnet-api.4160.nodely.dev) from violating the manifest CSP. - SWAP_QUOTE/EXECUTE not in inpage switch: Confirmed —
routeToBackgroundinprovider-bridge.tshas no SWAP cases; dApps cannot trigger swaps on behalf of the user.
All 7 security claims independently validated by Comet CDP (all CONFIRMED). BUG-A (Comet finding: remaining undeclared) confirmed false positive — remaining is declared on line 90 of checkUnlockRate. FIND-C (genesis check variable binding) confirmed intentional — post-approval re-fetch of freshMeta is the designed defence against chain switches during approval.
| Component | Implementation |
|---|---|
| Key derivation | PBKDF2-SHA-256, 600,000 iterations (OWASP 2023), 32-byte salt |
| Vault encryption | AES-GCM-256, fresh random 12-byte IV per write |
| Session-key pattern | PBKDF2 runs once on unlock; CryptoKey held in memory, never extracted |
| Vault write mutex | withVaultLock() prevents concurrent corruption |
| SW suspension safety | onSuspend wipes _vaultData and _sessionKey |
| Origin authority | Background uses Chrome-provided sender.url, not msg.origin |
| Message bus | BgRequest union type enforced at compile time (TypeScript) |
| AP2 signing | algosdk.signBytes (ed25519) — no MX prefix, not an ARC-0027 operation |
| AP2 hashing | crypto.subtle.digest("SHA-256", ...) — Web Crypto API, no polyfill |
| x402/MPP note field | Encode to UTF-8 bytes first, then slice(0, 1000) |
| Spending caps | safeCap() validates before BigInt conversion; zero/negative → default |
| Duplicate txn detection | Anchored word-boundary regex, not substring search |
| Area | Finding |
|---|---|
| eval() | Zero occurrences; vm polyfill excluded from bundle |
| dangerouslySetInnerHTML | Not used anywhere |
| Hardcoded secrets | None; WC project ID is a public credential |
| Remote code execution | No dynamic import() of remote URLs |
| DOM access in service worker | None; background uses only Chrome APIs and fetch() |
| Clipboard without user gesture | All four clipboard writes are user-click handlers |
| Obfuscated code | None; all source is readable TypeScript |
| Supply chain | All dependencies are well-known packages; phantom dep removed |
| CSP unsafe-eval / unsafe-inline | Not present in script-src |
manifest.json ← modified v0.4.0 (version bump, CSP img-src tightened)
package.json ← modified v0.4.0 (terser dep added)
vite.config.ts ← modified v0.4.0 (terser console stripping)
src/background/index.ts ← modified v0.4.0 (WC_PROJECT_ID validation)
src/background/message-handler.ts
src/background/wallet-store.ts
src/background/vault-store.ts
src/background/swap-handler.ts ← modified v0.4.0 (sk wipe try/finally)
src/background/x402-handler.ts ← modified v0.4.0 (sk wipe try/finally)
src/background/mpp-handler.ts ← modified v0.4.0 (sk wipe try/finally)
src/background/ap2-handler.ts ← modified v0.4.0 (sk wipe try/finally)
src/background/mcp-client.ts
src/background/chain-clients.ts
src/background/approval-handler.ts
src/background/web3wallet-handler.ts
src/content/index.ts
src/content/provider-bridge.ts
src/inpage/index.ts ← modified v0.4.0 (Authorization header stripped on retry)
src/popup/App.tsx
src/popup/components/AccountView.tsx
src/popup/components/SwapPanel.tsx
src/popup/components/VaultPanel.tsx
src/popup/components/WalletConnectModal.tsx
src/popup/hooks/useWalletConnect.ts
src/approval/index.tsx
src/shared/constants.ts
src/shared/debug-log.ts ← modified v0.4.0 (7-day auto-expiry)
src/shared/utils/crypto.ts
src/shared/utils/asset-cache.ts
src/shared/utils/wc-storage.ts
src/shared/utils/wc-chrome-storage.ts
src/shared/utils/wc-sign-group.ts
src/shared/types/wallet.ts
src/shared/types/messages.ts
src/shared/types/approval.ts
src/shared/types/ap2.ts
src/shared/types/mpp.ts
src/shared/types/x402.ts
src/devtools/components/X402Inspector.tsx
- XHR interception (x402 for legacy XMLHttpRequest-based apps)
- WalletConnect account
wcChainmigration for pre-detection accounts - Spending cap configuration UI (currently hardcoded default of 10 VOI/ALGO)
- Resolution caching for enVoi (each lookup costs 1 VOI)
- Approval TTL countdown in the approval popup
_persistVaultData()with stored sessionCryptoKey(Phase 2 vault hardening)