Back to skill

Security audit

Use Modular Wallets

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent Circle modular-wallet development guide, but users should avoid copying its localStorage credential examples into production wallet apps.

Use this skill for prototyping Circle modular wallets, but require explicit user confirmation for transfers and recovery actions, default to testnets, and replace all localStorage credential persistence with a production-safe session or storage design before handling real funds.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
references/circle-smart-account.md:55
Finding
Passkey Credential Objects Persisted in Browser localStorage## Vulnerability Details **File Location**: `references/circle-smart-account.md:55` and `references/circle-smart-account.md:67` **Vulnerability Type**: Sensitive authentication data stored in script-accessible browser storage **Risk Level**: Medium ### Vulnerable Code ```typescript const credential = await toWebAuthnCredential({ transport: passkeyTransport, mode: WebAuthnMode.Register, username: 'alice', }) // Persist credential so the user stays logged in across reloads localStorage.setItem('credential', JSON.stringify(credential)) ``` The login flow repeats the same insecure persistence pattern: ```typescript const credential = await toWebAuthnCredential({ transport: passkeyTransport, mode: WebAuthnMode.Login, }) localStorage.setItem('credential', JSON.stringify(credential)) ``` ### Technical Analysis The examples serialize a `P256Credential` object and place it in `localStorage`. Data in `localStorage` is available to every JavaScript context running under the same origin. It does not receive the confidentiality protections provided by an `HttpOnly` session cookie. Consequently, an attacker who obtains same-origin script execution through cross-site scripting, a compromised frontend dependency, malicious analytics code, or another injected script can read and export the serialized credential object. The exact ability to authenticate using the extracted object depends on the SDK representation and authenticator requirements; WebAuthn private key material should ordinarily remain inside the authenticator. Nevertheless, exposing credential identifiers and related authentication state increases the impact of frontend compromise and may facilitate session abuse, correlation, credential-targeting attacks, or replay attempts where the surrounding implementation improperly treats the serialized object as sufficient authentication state. The parent Skill recognizes this risk in `SKILL.md` by stating that `localStorage` is for a quick example onl ...[truncated 1686 chars]
Remediation
## Remediation Suggestions - Remove all examples that serialize sensitive credential objects into `localStorage`. - Keep WebAuthn private key operations within the platform authenticator and persist only the minimum non-secret credential identifier or account metadata required by the application. - Use a server-managed session represented by a cookie configured with `HttpOnly`, `Secure`, and an appropriate `SameSite` policy. - Require a fresh WebAuthn assertion before sensitive wallet operations instead of treating restored browser data as proof of authentication. - Apply a restrictive Content Security Policy and Trusted Types where supported to reduce XSS exposure. - Minimize third-party scripts and audit frontend dependencies that execute in the wallet origin. - Clear obsolete authentication state on logout, account recovery, credential revocation, and session expiry. - Make the secure implementation the primary reference example rather than relying solely on a warning in `SKILL.md`.

T09 · Insecure Skill Coding Practices

Warning
Location
references/passkey-recovery.md:111
Finding
Recovered Passkey Credential Persisted in Browser localStorage## Vulnerability Details **File Location**: `references/passkey-recovery.md:111` **Vulnerability Type**: Recovered authentication data stored in script-accessible browser storage **Risk Level**: Medium ### Vulnerable Code ```typescript const localAccount = mnemonicToAccount(userEnteredMnemonic.trim()) // Create a temporary smart account using the recovery EOA as owner const tempAccount = await toCircleSmartAccount({ client, owner: localAccount, }) // Execute recovery -- replaces the lost passkey owner with the new credential await bundlerClient.executeRecovery({ account: tempAccount, credential: newCredential, paymaster: true, }) // Persist the new credential for future sessions localStorage.setItem('credential', JSON.stringify(newCredential)) ``` ### Technical Analysis Following account recovery, the example serializes the newly registered passkey credential into `localStorage`. This storage is readable by all JavaScript executing under the application origin and therefore does not provide isolation from XSS, compromised frontend dependencies, or injected third-party scripts. The timing makes this pattern particularly sensitive: the newly created credential has just replaced the lost passkey as the smart account owner. Exposure of its serialized representation can disclose credential metadata and recovery-related authentication state at the moment ownership changes. WebAuthn private keys should remain inside the authenticator, so disclosure of this serialized object does not by itself prove that an attacker can extract the private key or sign arbitrary transactions. The risk becomes more severe if surrounding application code restores the object as trusted login state without requiring a fresh WebAuthn assertion. ### Attack Path 1. The user starts wallet recovery and enters a valid mnemonic. 2. The application registers a new WebAuthn credential and executes the ownership recovery operation. 3. The reference flow serializes `newCredentia ...[truncated 1128 chars]
Remediation
## Remediation Suggestions - Do not store `newCredential` or other sensitive recovery state in `localStorage`. - Persist only non-sensitive identifiers required to locate the credential, while keeping cryptographic secrets and signing operations inside the authenticator. - Establish a new server-side session after successful recovery and issue a `Secure`, `HttpOnly`, appropriately `SameSite` cookie. - Require a fresh WebAuthn assertion before permitting sensitive account or transaction operations. - Invalidate all pre-recovery sessions and references to the old passkey after ownership replacement. - Remove the recovery mnemonic and derived temporary account from application memory as soon as recovery completes; do not log, persist, or transmit the phrase. - Harden the recovery interface with a restrictive Content Security Policy, dependency review, input isolation, and removal of unnecessary third-party scripts. - Document the lifecycle of recovery credentials and provide explicit logout, revocation, and session-expiration behavior.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- ALWAYS store mnemonic recovery backups outside the repository root. NEVER commit recovery phrases to version control.
- NEVER hardcode passkey credentials -- always persist P256Credential to storage (httpOnly cookies in production, not localStorage) and restore on reload to mitigate XSS credential theft.
- NEVER reuse a recovery mnemonic phrase across multiple accounts.
- ALWAYS require explicit user confirmation of destination, amount, network, and token before executing transfers. NEVER auto-execute fund movements on mainnet.
- ALWAYS warn when targeting mainnet or exceeding safety thresholds (e.g., >100 USDC).
- ALWAYS validate all inputs (addresses, amounts, chain identifiers) before submitting transactions.
- ALWAYS warn before interacting with unaudited or unknown contracts.
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The example persists `newCredential` to `localStorage`, which is readable by any JavaScript executing in the origin, including injected script from XSS or compromised third-party dependencies. In a wallet recovery context this is more sensitive than ordinary app state because the stored credential metadata can aid account takeover, replay of registration state, or unauthorized reuse depending on SDK behavior and surrounding implementation.

Scope Creep

Low
Category
Excessive Agency
Content
## Overview

Modular Wallets are flexible smart contract accounts (MSCAs) that extend functionality through installable modules. Built on ERC-4337 (account abstraction) and ERC-6900 (modular smart contract framework), they support passkey authentication, gasless transactions, batch operations, and custom logic modules (multisig, subscriptions, session keys). MSCAs are lazily deployed -- gas fees for account creation are deferred until the first outbound transaction.

## Prerequisites / Setup
Confidence
75% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Static analysis

No suspicious patterns detected.