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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
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.

Content

Scanner excerpt · SKILL.md (reported line 91)May include surrounding context.

md
- 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.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Scope Creep

Low
Category
Excessive Agency
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.

Content

Scanner excerpt · SKILL.md (reported line 8)May include surrounding context.

md
## 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

Static analysis

No suspicious patterns detected.