Back to skill

Security audit

Safe Multisig Skill

Security checks for vulnerabilities and agentic risk

Overview

This Safe wallet skill is mostly purpose-aligned, but it can sign or execute high-impact blockchain actions with weak review and endpoint safeguards.

Review this skill carefully before installing. Use it only with low-value test Safes or low-privilege signers until approval signing verifies transaction details, endpoint validation is added, dependencies are updated, and signing/execution requires an explicit human review of chain, Safe, nonce, recipient, value, calldata, and service host.

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

Error
Location
scripts/approve-tx.ts:27
Finding
Blind Approval of an Unverified Safe Transaction Hash<![CDATA[ ## Vulnerability Details **File Location**: `scripts/approve-tx.ts:27-57` **Vulnerability Type**: Blind signing of unverified transaction data **Risk Level**: High ### Vulnerable Code ```ts try { if (opts.safe) validateAddress(opts.safe, 'safe'); if (opts.safeTxHash) validateTxHash(opts.safeTxHash, 'safe-tx-hash'); validateApiKey(opts); const txServiceUrl = resolveTxServiceUrl(opts); const chainId = resolveChainId(opts); const pk = requirePrivateKey(); // FIX SM-005 / SH-05: Use resolveRpcUrl instead of hardcoded Base fallback const provider = resolveRpcUrl(opts); const safeSdk = await Safe.init({ provider, signer: pk, safeAddress: opts.safe! }); const senderAddress = await safeSdk.getSafeProvider().getSignerAddress(); // Sign the tx hash const sig = await safeSdk.signHash(opts.safeTxHash!); const apiKitConfig: { chainId: bigint; txServiceUrl: string; apiKey?: string } = { chainId, txServiceUrl }; if (opts.apiKey) apiKitConfig.apiKey = opts.apiKey; const apiKit = new SafeApiKit(apiKitConfig); await apiKit.confirmTransaction(opts.safeTxHash!, sig.data); ``` ### Technical Analysis The approval command validates only that `--safe-tx-hash` is a syntactically valid 32-byte hexadecimal value. It does not retrieve the associated transaction from the Safe Transaction Service before signing it. Consequently, the script does not verify: - That the hash corresponds to an existing Safe transaction. - That the transaction belongs to the Safe supplied through `--safe`. - The transaction destination, value, calldata, operation, nonce, or gas-refund fields. - That a fetched transaction recomputes to the supplied Safe transaction hash. - That the signer has reviewed or explicitly authorized the underlying action. The command then signs the opaque caller-controlled hash and submits the resulting owner confirmation. This is a blind-signing pattern. It is particularly dangerous for Safe wallets because ...[truncated 1938 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Before producing an approval signature: 1. Retrieve the transaction associated with `--safe-tx-hash` from the configured transaction service. 2. Verify that the returned transaction’s Safe address exactly matches `--safe`. 3. Reconstruct the complete Safe transaction, including: - Destination address - Value - Calldata - Operation type - Nonce - Safe transaction gas - Base gas - Gas price - Gas token - Refund receiver 4. Recompute the Safe transaction hash locally using the SDK and reject the operation if it differs from the supplied hash. 5. Confirm that the connected RPC network’s chain ID matches the requested chain. 6. Present the complete transaction summary before signing, including decoded calldata where possible. 7. Require explicit interactive authorization for high-impact actions, unless a deliberate non-interactive flag is supplied. 8. Consider adding policy controls that reject delegate calls, owner changes, module activation, unlimited token approvals, or transfers above configured limits. 9. Add tests proving that approval is rejected when: - The transaction belongs to another Safe. - Any returned field is modified. - The recomputed hash differs. - The chain ID differs. - The transaction cannot be retrieved. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/safe_lib.ts:76
Finding
Sensitive API Credentials and Authorization Signatures Can Be Sent to Unvalidated Custom Endpoints<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/safe_lib.ts:76-92` - `scripts/safe_lib.ts:113-116` - `scripts/propose-tx.ts:121-134` - `scripts/approve-tx.ts:49-57` - `scripts/execute-tx.ts:38-53` **Vulnerability Type**: Insufficient validation of sensitive network destinations **Risk Level**: Medium ### Vulnerable Code The shared configuration accepts arbitrary transaction-service and RPC URLs: ```ts export function addCommonOptions(cmd: Command): Command { return cmd .option('--chain <slug>', 'Safe tx-service chain slug (e.g. base, base-sepolia, mainnet)') .option('--tx-service-url <url>', 'Override tx-service base URL') .option('--rpc-url <url>', 'RPC URL (required for signing/executing)', process.env.RPC_URL) .option('--api-key <key>', 'Safe Transaction Service API key', process.env.SAFE_TX_SERVICE_API_KEY) .option('--debug', 'Verbose logging'); } /** * FIX SM-001 + SM-002: Resolve the transaction service URL. * Appends /api suffix and uses correct EIP-3770 short names. */ export function resolveTxServiceUrl(opts: CommonOptions): string { if (opts.txServiceUrl) return opts.txServiceUrl.replace(/\/$/, ''); if (!opts.chain) throw new Error('Missing --chain or --tx-service-url'); ``` RPC overrides are similarly returned without validation: ```ts export function resolveRpcUrl(opts: CommonOptions): string { if (opts.rpcUrl) return opts.rpcUrl; if (process.env.RPC_URL) return process.env.RPC_URL; if (opts.chain) { const url = DEFAULT_RPCS[opts.chain.toLowerCase()]; if (url) return url; throw new Error(`No default RPC URL for chain "${opts.chain}". Pass --rpc-url explicitly.`); } throw new Error('Missing --rpc-url or --chain (needed to resolve RPC URL)'); } ``` The proposal flow forwards the API key and transaction authorization material to the selected transaction service: ```ts const apiKitConfig: { chainId: bigint; txServiceUrl: string; apiKey?: string } = { chainId, txServiceUrl } ...[truncated 4765 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Implement a centralized endpoint-validation and credential-routing policy: 1. Parse every service and RPC endpoint with `new URL()`. 2. Permit only `https:` for remote endpoints. 3. Permit `http:` only for explicitly authorized loopback development endpoints such as `127.0.0.1`, `localhost`, or `[::1]`. 4. Reject unsupported schemes, malformed URLs, URL fragments, and embedded usernames or passwords. 5. Maintain an allowlist of official Safe Transaction Service hosts. 6. Do not forward `SAFE_TX_SERVICE_API_KEY` to a custom host by default. 7. Introduce a separate credential option for self-hosted services, such as `SAFE_CUSTOM_TX_SERVICE_API_KEY`. 8. Require an explicit flag such as `--allow-custom-signing-endpoint` before sending proposal or approval signatures to a non-official host. 9. Print the normalized destination hostname and chain before signing, and require explicit confirmation in interactive operation. 10. Verify the RPC-reported chain ID against `resolveChainId(opts)` before any signing or on-chain submission. 11. Document that RPC and transaction-service URLs are security-sensitive trust boundaries. 12. Add automated tests covering: - Rejection of plaintext remote HTTP. - Rejection of embedded URL credentials. - Prevention of official API-key forwarding to custom hosts. - Explicit localhost development exceptions. - Chain-ID mismatches. - Warning or confirmation behavior for custom signing endpoints. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (78)

Known Vulnerable Dependency: vitest==4.0.18 — 2 advisory(ies): CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Critical
Category
Supply Chain
Confidence
90% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: vitest==4.0.18 — 2 advisory(ies): CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Critical
Category
Supply Chain
Confidence
98% confidence
Finding
This skill declares vitest v4, and the provided analysis indicates resolution to vitest 4.0.18, which is affected by critical issues including arbitrary file read and possible code execution when the Vitest UI server is exposed. Although vitest is a devDependency, exploitation can still affect developer workstations, CI runners, or test environments; in a multisig-wallet skill, compromise of those environments is especially dangerous because they may hold RPC credentials, private keys, API tokens, or transaction data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description promises a broad Safe multisig operational skill implemented with the Safe{Core} SDK in strict TypeScript. The supplied code is instead a small shell utility whose sole purpose is to derive a Safe Transaction Service API base URL for a given chain and fetch its /v1/about/ metadata endpoint. While this is loosely related to Safe infrastructure and could support troubleshooting or service discovery, it does not implement any of the primary declared capabilities such as Safe creation, transaction proposal/confirmation/execution, nonce inspection, or pending transaction listing. The implementation language and mechanism also differ materially from the stated TypeScript SDK-based skill.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description promises a full Safe multisig transaction skill centered on Safe{Core} SDK capabilities. However, the provided code chunk is only a test suite for utility helpers. The tested behaviors are mostly configuration and validation helpers: mapping chain slugs to Safe transaction service URLs and chain IDs, resolving RPC URLs, requiring a signer private key from env vars, validating addresses and transaction hashes, warning about missing API keys, performing generic fetch-with-timeout JSON requests, and constructing CLI commands/options. These are supporting utilities, not the declared primary functionality. Since the actual code shown does not implement the core Safe operations named in the description, the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
There is a clear mismatch between the declared purpose and the provided code. The description claims operational Safe multisig capabilities via Safe{Core} SDK, but the code chunk is merely a Vitest configuration file for running tests. This is unrelated support tooling and does not demonstrate any of the claimed Safe-related behavior. Therefore the supplied code does not accurately represent the declared skill functionality.

Ae1

High
Category
analysis-evasion
Content
| `safe-info.ts` | Fetch Safe info (owners/threshold/nonce) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `safe-info.ts` | Fetch Safe info (owners/threshold/nonce) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `list-pending.ts` | List pending (queued) multisig transactions |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `list-pending.ts` | List pending (queued) multisig transactions |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `safe_txs_list.ts` | List all multisig transactions (queued + executed) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `propose-tx.ts` | Create + propose a multisig tx |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `propose-tx.ts` | Create + propose a multisig tx |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Known Vulnerable Dependency: nanoid==3.3.11 — 3 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2026-73086 (nanoid: Integer Overflow or Wraparound)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: picomatch==4.0.3 — 2 advisory(ies): CVE-2026-33672 (Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Mat); CVE-2026-33671 (Picomatch has a ReDoS vulnerability via extglob quantifiers)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: postcss==8.5.6 — 4 advisory(ies): CVE-2026-45623 (PostCSS: Arbitrary file read and information disclosure via attacker-controlled ); CVE-2026-69153 (PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappi); CVE-2026-41305 (PostCSS has XSS via Unescaped </style> in its CSS Stringify Output) +1 more

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: rollup==4.57.1 — 1 advisory(ies): CVE-2026-27606 (Rollup 4 has Arbitrary File Write via Path Traversal)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: ws==8.18.3 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: vite==7.3.1 — 5 advisory(ies): CVE-2026-39365 (Vite Vulnerable to Path Traversal in Optimized Deps `.map` Handling); CVE-2026-53571 (vite: `server.fs.deny` bypass on Windows alternate paths); CVE-2026-39363 (Vite Vulnerable to Arbitrary File Read via Vite Dev Server WebSocket) +2 more

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: ws==8.17.1 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill documents commands that use shell execution, environment variables, and network access, but it does not declare any explicit tool scope or permissions boundaries. In an agent setting, missing scope makes it easier for the runtime to grant broader capabilities than necessary, increasing the chance of unintended command execution, secret exposure, or live blockchain interaction.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
Using `npx tsx` without pinning an exact package version introduces supply-chain risk because dependency resolution may pull a newer or compromised package at runtime. In a skill that handles transaction signing and environment-based secrets, executing an unexpected package version could expose private keys or alter transaction behavior.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
Using `npx tsx` without pinning an exact package version introduces supply-chain risk because dependency resolution may pull a newer or compromised package at runtime. In a skill that handles transaction signing and environment-based secrets, executing an unexpected package version could expose private keys or alter transaction behavior.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
Using `npx tsx` without pinning an exact package version introduces supply-chain risk because dependency resolution may pull a newer or compromised package at runtime. In a skill that handles transaction signing and environment-based secrets, executing an unexpected package version could expose private keys or alter transaction behavior.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
Using `npx tsx` without pinning an exact package version introduces supply-chain risk because dependency resolution may pull a newer or compromised package at runtime. In a skill that handles transaction signing and environment-based secrets, executing an unexpected package version could expose private keys or alter transaction behavior.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
Using `npx tsx` without pinning an exact package version introduces supply-chain risk because dependency resolution may pull a newer or compromised package at runtime. In a skill that handles transaction signing and environment-based secrets, executing an unexpected package version could expose private keys or alter transaction behavior.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/safe_lib.ts:77