New: AI Agent SDK is live — integrate Claude-powered wallet actions into your app. Read the docs
Threat Model

Threat Model

This document describes the threat boundary, attacker model, trust assumptions, and residual risks for Veil in STRIDE format. It is intended for security reviewers, auditors, and operators evaluating Veil for production use as part of the SCF tranche-2 security deliverable.

⚠️

Veil has not undergone a formal third-party security audit. The mitigations below represent the design intent and current implementation; some are marked planned and are not yet enforced on-chain.


Attacker Model

ActorCapabilityGoal
Remote attackerNetwork access, phishing sites, malicious JavaScriptSteal funds or authorise a transaction without the user’s biometric
Malicious anchor / serviceControl over anchor-issued assets, forged off-chain dataIssue fraudulent tokens or manipulate swap pricing
Compromised cloud / passkey syncRead access to iCloud Keychain or Google Password Manager backupRecover the passkey credential and sign on the user’s behalf
Malicious co-signerAn add_signer transaction the user was tricked into approvingExfiltrate funds after gaining on-chain signer status
Malicious smart contractInvoke Veil wallet via cross-contract callTrigger an authorised action without the user’s knowledge
Insider / supply-chainCompromise of an npm package or GitHub Actions runnerInject malicious SDK code or steal CI secrets
RPC relay abuserDirect HTTP access to public /api/rpc/mainnet proxyDrain metered mainnet RPC quota or execute unauthorized RPC methods
Cross-network attacker / state poisonerClient-side origin storage manipulation across network switchesCorrupt or clobber mainnet contract credentials using testnet states
Quantum adversary (future)Break P-256 ECDSA offlineRecover signing key from on-chain public key

STRIDE Threat Table

Spoofing

#ThreatComponentMitigationStatus
S-1Attacker forges a WebAuthn assertion with an arbitrary P-256 key__check_authContract rejects keys not in has_signer() registry✅ Implemented
S-2Phishing site harvests a passkey assertion for the real originWebAuthn / browserWebAuthn origin binding — clientDataJSON.origin is set by the authenticator and cannot be spoofed by a relying party✅ By spec
S-3Cloud-passkey sync compromise — attacker restores credential on a new devicePasskey sync providerPasskey private key is stored in the secure enclave; cloud sync stores an encrypted blob that requires re-authentication on the new device⚠️ Trust in sync provider
S-4Malicious anchor impersonates a legitimate issuerAsset trust lineVeil does not auto-establish trust lines; user must approve change_trust via biometric✅ Implemented
S-5Cross-network wallet identity spoofing (testnet credentials bleeding into mainnet)walletStorage / network.tsNamespaced storage slots (_mainnet suffix for mainnet, isolated reads/writes) prevent testnet credentials or addresses from being used on mainnet transactions (#628)✅ Implemented

Tampering

#ThreatComponentMitigationStatus
T-1Attacker modifies the transaction after the user signsXDR envelopeSoroban signature_payload is a deterministic hash of the auth entry; any modification changes the payload and breaks the challenge binding✅ Implemented
T-2Attacker tampers with clientDataJSON or authData in transitWebAuthn pipelineBoth fields are included in the ECDSA message hash (SHA256(authData ‖ SHA256(clientDataJSON))); tampering invalidates the signature✅ Implemented
T-3SDK is tampered in a supply-chain attacknpm packagepackage-lock.json pins exact versions; Dependabot keeps dependencies updated; GitGuardian scans for secret leaks⚠️ No sub-resource integrity yet
T-4Wraith indexer serves tampered transfer historyHorizon dataUI uses Horizon as a secondary source; discrepancies can be surfaced; full Horizon verification is planned⚠️ Partial
T-5Passkey registration overwrites an existing resident key without excludeCredentialssdk/src/core.tsRegistration populates excludeCredentials via collectKnownCredentials(store) to prevent authenticators from overwriting existing resident keys for the same RP ID (#627)✅ Implemented
T-6Accidental destruction of mainnet wallet during testnet “Reset wallet”clearActiveNetworkWalletReset operation explicitly removes only the keys corresponding to the active network slot (clearActiveNetworkWallet()), preserving the other network’s state (#628)✅ Implemented

Repudiation

#ThreatComponentMitigationStatus
R-1User denies authorising a transactionSmart contractEvery authorised action requires a valid WebAuthn assertion; clientDataJSON contains the exact signature_payload hash, creating a non-repudiable audit trail on-chain✅ By design
R-2Operator denies a Lens price query was servedLens oraclex402 payment receipts provide a timestamped record of each API call✅ Implemented

Information Disclosure

#ThreatComponentMitigationStatus
I-1Private key exfiltration via JavaScriptSecure enclaveWebAuthn create() returns only the public key; the private key never leaves the hardware enclave✅ By spec
I-2localStorage credential ID exposure (same-origin XSS)SDK / Storagecredential_id in localStorage reveals which passkey is registered; does not expose private key. PRF-derived fee-payer seeds are kept in sessionStorage and memory only, cleared on lock (#629)⚠️ Switch to IndexedDB + encryption in production
I-3Stellar public key linkage (address reuse)BlockchainStellar public keys are permanently visible on-chain; multi-account rotation is not yet supported⚠️ Residual risk
I-4Agent conversation history in memoryAgent serverChat history is held in-process memory only; no persistence layer for conversations✅ No DB persistence
I-5Metered upstream RPC key leakage in proxy error responsesRPC Proxyapp/api/rpc/mainnet/route.ts strips the upstream URL and key on failure, returning sanitized 502/503 responses without echoing provider credentials✅ Implemented

Denial of Service

#ThreatComponentMitigationStatus
D-1Flood Soroban RPC with junk transactionsSoroban RPCProtocol-level fee market; attackers pay per operation✅ By protocol
D-2Drain fee-payer G… account (HKDF-derived)Fee-payer accountFee-payer is only used for fee bump; wallet funds are held separately in the C… contract account✅ By design
D-3Remove the last signer, locking the walletremove_signerPhase 5 guardian recovery allows a pre-registered guardian to restore access; UI warns when removing the last signer✅ Guardian recovery
D-4DoS Lens price oracleLens servicex402 micropayment gate rate-limits free callers; Render horizontal scaling available⚠️ Basic rate limiting only
D-5Mainnet RPC open relay quota exhaustion (arbitrary RPC flooding)app/api/rpc/mainnet/route.tsSame-origin proxy enforces a strict 11-method JSON-RPC allow-list, rejecting unapproved calls with 403, preventing open relay exploitation against metered accounts✅ Implemented
D-6Device portability loss / passkey restore without contract address C…Passkey recovery & SDKPasskey sync restores WebAuthn credentials across devices but does not carry contract addresses. Re-discovery requires deterministic salt resolution, manual address input, or indexer lookup (#626)⚠️ Partial / Address entry required

Elevation of Privilege

#ThreatComponentMitigationStatus
E-1Rogue signer added without user consentadd_signeradd_signer requires a valid WebAuthn signature from an existing registered signer✅ Implemented
E-2Cross-contract re-entrancy (malicious contract calls Veil)__check_authSoroban’s require_auth model prevents re-entrancy; each auth entry is bound to a specific invocation✅ By Soroban design
E-3Replay of an old valid WebAuthn assertion__check_authsignature_payload is derived from the Soroban auth entry hash, which is unique per ledger sequence; Soroban ledger sequences prevent replay at the network level✅ Implemented
E-4Exposure of metered MAINNET_RPC_URL secretDeployment Env & CIMAINNET_RPC_URL is a metered, paid RPC credential kept strictly in server-side deployment environment variables (never NEXT_PUBLIC_*). In CI/CD, secret isolation and mock RPC providers prevent quota consumption during automated test runs✅ Server-only env isolation
E-5Guardians recover to a key they controlGuardian recovery3-day timelock on complete_recovery; user can cancel during the window; guardian’s own key must authorise⚠️ Timelock relies on user monitoring
E-6Silent fee-payer derivation mode downgrade (PRF → legacy)frontend/wallet/lib/feePayer.tsDerivation mode (prf-raw, prf-hkdf, or legacy) is pinned in veil_feepayer_mode on wallet setup; PRF seeds remain in session memory only, and FeePayerDiagnostics exposes candidate probes to prevent unexpected G… address shifts or silent security downgrades (#629)✅ Implemented

Trust Assumptions

AssumptionJustificationRisk if violated
Stellar consensus is BFT-honestStellar SCP tolerates up to ⅓ Byzantine validators in the quorum sliceDouble-spend or transaction censorship
WebAuthn implementation is correctBrowser/OS vendor (Apple, Google, Microsoft) implements the FIDO2 spec faithfullyPasskey bypass
Hardware secure enclave is tamper-resistantCertified by FIDO Alliance; assumed not to have hardware backdoorsPrivate key exfiltration
Cloud passkey sync provider (iCloud/Google) is honestOperator trusts Apple / Google with the encrypted passkey blobCredential recovery by a compromised cloud provider
p256 and sha2 Rust crates are correctWidely used, audited crates; Rust memory safety eliminates most implementation bugsSignature forgery or hash collision
Soroban VM is deterministic and isolatedSoroban design guarantee; each contract execution is sandboxedArbitrary code execution inside the VM
Vercel and Render supply-chain integrityBuild artefacts served from Vercel CDN; Render container imagesMalicious injection at deploy time
Mainnet RPC provider maintains availability and integrityUpstream provider executes read/write RPC requests honestlyStale state, delayed transaction submission, or false fee estimations

Residual Risks

RiskLikelihoodImpactAccepted Until
No formal security auditMediumHighPre-mainnet audit scheduled
localStorage credential ID (same-origin XSS readable)LowLowProduction SDK hardening (PRF seed is already session-only)
No RP ID / origin verification in contract (Phase 4 planned)LowHighPhase 4 — store rpIdHash at init()
No per-signer nonce (sequence replay across forks)Very LowMediumPhase 5 — on-chain nonce
Quantum adversary breaking P-256Very Low (10+ yr)CriticalPost-quantum: replace P-256 with ML-DSA when Soroban supports it
Passkey sync provider compromiseVery LowHighHardware key option (FIDO2 security key) as alternative authenticator
Malicious anchor issuing fraudulent assetsLowMediumUI shows asset issuer; user must approve trust lines explicitly
Cross-device contract address re-discoveryMediumLowIndexer-based contract address recovery (#626)
Metered RPC proxy quota exhaustionMediumMediumIngress IP rate limiting and quota monitoring (#631)

Scope

This threat model covers:

  • In scope: Soroban smart contract (invisible_wallet, factory), browser SDK (invisible-wallet-sdk), wallet PWA (frontend/wallet), AI agent (packages/agent), Lens price oracle, Wraith transfer indexer, docs site (frontend/docs), mainnet RPC proxy (app/api/rpc/mainnet), and CI/CD secret boundaries.
  • Out of scope: Stellar network consensus layer, browser/OS vendor implementations of WebAuthn, Render/Vercel hosting infrastructure internals.

References