Back to skill

Security audit

Bitcoin Wallet for Agents using Arkade

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real cryptocurrency wallet skill, but it needs review because some docs can expose wallet keys and the install examples execute mutable packages.

Review carefully before installing. Do not pass any private key or seed phrase to the CLI, pin and verify package versions instead of using unpinned npx/dlx commands, keep only small/test funds unless you trust the package and host, and require explicit confirmation for every send, offboard, Lightning payment, swap, claim, or refund.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
README.md:49
Finding
Private Key Exposure Through Incorrect Command-Line Documentation<![CDATA[ ## Vulnerability Details **File Location**: `README.md:49-52`, `README.md:102-105` **Vulnerability Type**: Private key exposure through process arguments and shell history **Risk Level**: High ### Vulnerable Code ```markdown ### CLI Usage ```bash # Initialize wallet arkade init <private-key-hex> ``` ``` The command table repeats the unsafe interface: ```markdown | `init <key> [url]` | Initialize wallet | ``` The actual implementation interprets the first argument as a server URL rather than a private key: ```javascript async function cmdInit(serverUrl) { const existing = loadConfig(); if (existing) { const { sdk } = await getSDK(); const { Wallet, SingleKey } = sdk; const wallet = await Wallet.create({ identity: SingleKey.fromHex(existing.privateKey), arkServerUrl: existing.serverUrl || DEFAULT_SERVER, }); const address = await wallet.getAddress(); console.log("Wallet already initialized."); console.log(`Server: ${existing.serverUrl || DEFAULT_SERVER}`); console.log(`Address: ${address}`); return; } try { const config = await autoInit(serverUrl); ``` ### Technical Analysis The README explicitly directs users to place a Bitcoin private key in a command-line argument. Command-line secrets can be exposed through: - Shell history files - Process listings and process-monitoring utilities - Terminal session recording - CI/CD logs - Agent transcripts and tool-call logs - Audit and telemetry systems The implementation makes the issue more dangerous because `cmdInit` does not import the supplied private key. It treats the argument as `serverUrl` and independently generates a new wallet through `autoInit`. A user may consequently disclose an existing wallet key while unknowingly initializing a different wallet. This violates secure secret-entry principles: cryptographic credentials must not be accepted or requested through process arguments. ### Attack Path 1. A user follows the README ...[truncated 1210 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace every documented invocation with the actual interface: ```bash arkade init [server-url] ``` 2. Change the command table to: ```markdown | `init [url]` | Generate and initialize a new wallet | ``` 3. Add an explicit warning that private keys and mnemonics must never be supplied through command-line arguments. 4. If wallet import functionality is required, implement a separate command such as `arkade import` that reads the key from: - An interactive, non-echoing prompt - Protected standard input - A user-owned file verified to have restrictive permissions 5. Never print an imported key or include it in exception messages, telemetry, or debug logs. 6. Add automated documentation tests that compare documented CLI syntax with the actual command parser. 7. Consider detecting a 64-character hexadecimal first argument to `init` and aborting with a warning, without echoing the supplied value. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
cli/arkade.mjs:54
Finding
Non-Atomic Creation of Wallet Credential File<![CDATA[ ## Vulnerability Details **File Location**: `cli/arkade.mjs:54-62` **Related Sensitive Data Locations**: `cli/arkade.mjs:97-106`, `cli/arkade.mjs:655-660` **Vulnerability Type**: Insecure secret-file creation and symlink handling **Risk Level**: Medium ### Vulnerable Code ```javascript /** * Save configuration to disk. */ function saveConfig(config) { if (!existsSync(CONFIG_DIR)) { mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 }); } writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); chmodSync(CONFIG_FILE, 0o600); } ``` The saved object contains the Bitcoin private key: ```javascript const privateKey = Buffer.from(identity.key).toString("hex"); const url = serverUrl || DEFAULT_SERVER; const config = { privateKey, serverUrl: url, createdAt: new Date().toISOString(), }; saveConfig(config); ``` It can later contain the LendaSwap mnemonic as well: ```javascript if (!config.lendaswapMnemonic) { try { const mnemonic = await lendaswap.getMnemonic(); config.lendaswapMnemonic = mnemonic; saveConfig(config); } catch { // Non-fatal: mnemonic save failed, will generate a new one next time } } ``` ### Technical Analysis `writeFileSync` creates or truncates the credential file before `chmodSync` restricts it to mode `0600`. On initial creation, the effective file mode is determined by the default creation mode and process umask. If the process has a permissive umask, the private key and mnemonic may be readable by other local users during the interval between the write and permission change. The operation is also non-atomic. An existing `config.json` symbolic link is followed by `writeFileSync`, allowing a local attacker with sufficient access to the wallet directory or a pre-created malicious directory structure to redirect the write. Updates can also leave a partially written configuration if the process terminates during the operation. Although the directory is requested with mode `0700` whe ...[truncated 1710 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create new credential files with restrictive permissions in the write operation itself: ```javascript writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600, flag: "wx", }); ``` 2. For updates, use an atomic replacement process: - Create a random temporary file in the same directory. - Open it with exclusive creation and mode `0600`. - Write and flush the complete content. - Atomically rename it over the destination. - Ensure temporary files are removed on failure. 3. Use `lstat` to reject symbolic links for both the configuration directory and file. 4. Verify that the directory: - Is owned by the current user - Is an actual directory - Has no group or world permissions 5. Verify the owner, regular-file type, and mode before loading an existing configuration. 6. Avoid separate `existsSync` and write operations where possible because they introduce time-of-check/time-of-use races. 7. Consider encrypting wallet credentials at rest using an operating-system key store or a user-supplied secret, while recognizing that encryption does not replace correct file permissions and atomic writes. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:48
Finding
Unpinned Remote Package Execution in Installation Instructions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:48-57` **Additional Location**: `README.md:17-34` **Vulnerability Type**: Mutable dependency execution and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```markdown ### Quick Start (no install required) ```bash # Using pnpm (recommended) pnpm dlx @arkade-os/skill init pnpm dlx @arkade-os/skill address # Using npx npx -y -p @arkade-os/skill arkade init npx -y -p @arkade-os/skill arkade address ``` ``` The agent installation instructions also execute an unpinned package: ```markdown ```bash npx skills add arkade-os/skill ``` This discovers the `arkade` skill and installs it into supported agents (Claude Code, Cursor, etc.). You can also target a specific agent or install globally: ```bash # Install to a specific agent npx skills add arkade-os/skill --agent claude-code # Install globally (user-level) npx skills add arkade-os/skill -g ``` ``` ### Technical Analysis The documented `pnpm dlx` and `npx` commands resolve mutable package versions and execute downloaded code immediately. No exact package version or integrity digest is specified. The `npx -y` option further suppresses the interactive installation prompt. Although the repository contains a lockfile, that lockfile does not constrain packages resolved by an independent `npx` or `pnpm dlx` invocation. Consequently, the code executed by a future user may differ from the audited source. This is especially sensitive for a wallet application because executed package code operates with the invoking user’s privileges and can access `~/.arkade-wallet/config.json`, which contains the Bitcoin private key and LendaSwap mnemonic. ### Attack Path 1. An attacker compromises the npm publisher account, package registry path, installation utility, or a mutable transitive dependency. 2. The attacker publishes a malicious package version under a package name used by the unpinned command. 3. A user or agent follows the documenta ...[truncated 1211 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin exact reviewed versions in all execution instructions: ```bash pnpm dlx @arkade-os/skill@0.1.5 init npx -p @arkade-os/skill@0.1.5 arkade init ``` 2. Pin the version of the Skills CLI as well rather than invoking mutable `npx skills`. 3. Avoid `-y` for security-sensitive wallet software so users retain an explicit review point before package execution. 4. Publish cryptographic checksums, package provenance attestations, and signed release artifacts. 5. Recommend downloading or installing the package first, verifying its version and integrity, and only then executing wallet commands. 6. Use npm provenance and protected publisher accounts with strong multi-factor authentication. 7. Keep production dependencies exact or tightly constrained, review lockfile changes, and run automated dependency and malware scanning before release. 8. Clearly explain that the repository lockfile does not protect standalone `npx` or `pnpm dlx` execution. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (41)

YARA rule 'ransomware_behavior': Ransomware-like patterns (mass encryption, ransom notes) [malware]

Critical
Category
YARA Match
Content
ds

| Command | Description |
|---------|-------------|
| `init <key> [url]` | Initialize wallet |
| `address` | Show Ark address |
| `boarding-address` | Show boarding address |
| `balance` | Show balance breakdown |
| `send <addr> <amt>` | Send sats |
| `history` | Transaction history |
| `onboard` | Get paid onchain: convert received onchain BTC to offchain |
| `offboard <addr>` | Pay onchain: send offchain BTC to an onchain address |
| `ln-invoice <amt>` | Create Lightning invoice |
| `ln-pay <bolt11>` | Pay Lightning invoice |
| `ln-fees` | Show swap fees |
| `ln-limits` | Show swap limits |
| `swap-quote <amt> <from> <to>` | Get stablecoin quote |
| `swap-to-stable <amt> <token> <chain> <addr>` | Swap BTC to stablecoin |
| `swap-to-btc <amt> <token> <chain> <addr>` | Swap stablecoin to BTC |
| `swap-status <id>` | Check swap status |
| `swap-pending` | Show pending swaps |
| `swap-pairs` | Show trading pairs |

## Configuration

- **Data:** `~/.arkade-wallet/config.json`

## Docu
Confidence
80% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

YARA rule 'ransomware_behavior': Ransomware-like patterns (mass encryption, ransom notes) [malware]

Critical
Category
YARA Match
Content
```bash
# Send sats to an Ark address
arkade send <ark-address> <amount-sats>

# Example: Send 50,000 sats
arkade send ark1qxyz... 50000

# View transaction history
arkade history
```

### Onchain Payments (Onboard/Offboard)

```bash
# Get paid onchain: Receive BTC to your boarding address, then onboard to Arkade
# Step 1: Get your boarding address
arkade boarding-address

# Step 2: Have someone send BTC to your boarding address

# Step 3: Onboard the received BTC to make it available offchain
arkade onboard

# Pay onchain: Send offchain BTC to any onchain Bitcoin address
arkade offboard <btc-address>

# Example: Pay someone at bc1 address
arkade offboard bc1qxyz...
```

### Lightning Network

```bash
# Create a Lightning invoice to receive payment
arkade ln-invoice <amount-sats> [description]

# Example: Create invoice for 25,000 sats
arkade ln-invoice 25000 "Coffee payment"

# Pay a Lightning invoice
arkade ln-pay <bolt11-invoice>

# Show swap fees
arkade ln-fees

# Show swap limits
Confidence
80% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

YARA rule 'ransomware_behavior': Ransomware-like patterns (mass encryption, ransom notes) [malware]

Critical
Category
YARA Match
Content
{ ArkadeBitcoinSkill } = skill;

  const config = loadConfig();
  const arkProvider = new RestArkProvider(config.serverUrl || DEFAULT_SERVER);
  const arkInfo = await arkProvider.getInfo();

  const bitcoin = new ArkadeBitcoinSkill(wallet);
  const balance = await bitcoin.getBalance();

  if (balance.onchain.total === 0) {
    console.log("No boarding UTXOs to onboard.");
    console.log(
      `Send BTC to your boarding address: ${await wallet.getBoardingAddress()}`,
    );
    return;
  }

  console.log(`Onboarding ${formatSats(balance.onchain.total)} sats...`);

  try {
    const result = await bitcoin.onboard({
      feeInfo: arkInfo.feeInfo,
      eventCallback: (event) => {
        console.log(`  Event: ${event.type}`);
      },
    });

    console.log(`Onboarded successfully!`);
    console.log(`Commitment TX: ${result.commitmentTxid}`);
  } catch (e) {
    console.error(`Error: ${e.message}`);
    process.exit(1);
  }
}

/**
 * Offboard command.
 */
async function cmdOffboard
Confidence
80% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

YARA rule 'ransomware_behavior': Ransomware-like patterns (mass encryption, ransom notes) [malware]

Critical
Category
YARA Match
Content
dResult,
  BalanceInfo,
  IncomingFundsEvent,
  OnboardParams,
  OffboardParams,
  RampResult,
} from "./types";

/**
 * ArkadeBitcoinSkill provides a unified interface for sending and receiving
 * Bitcoin over the Arkade protocol.
 *
 * This skill wraps the core wallet functionality and provides:
 * - Offchain Bitcoin transactions via Ark
 * - Get paid onchain via boarding address + onboard
 * - Pay onchain via offboard to any Bitcoin address
 * - Balance management
 * - Transaction history
 *
 * @example
 * ```typescript
 * import { Wallet, SingleKey } from "@arkade-os/sdk";
 * import { ArkadeBitcoinSkill } from "@arkade-os/skill";
 *
 * // Create a wallet
 * const wallet = await Wallet.create({
 *   identity: SingleKey.fromHex(privateKeyHex),
 *   arkServerUrl: "https://arkade.computer",
 * });
 *
 * // Create the skill
 * const bitcoinSkill = new ArkadeBitcoinSkill(wallet);
 *
 * // Get addresses for receiving
 * const addresses = await bitcoinSkill.getReceiveAddresses();
 * consol
Confidence
80% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The implemented code aligns well with the Arkade/offchain/onchain Bitcoin portions of the description: it supports Ark receive/send, boarding address retrieval, balance/history, incoming-funds notifications, onboard, and offboard. However, the declared description materially overstates functionality by claiming Lightning send/receive and USDC/USDT swaps, neither of which appears anywhere in the code. There are no unrelated dangerous capabilities beyond the Bitcoin/Arkade wallet operations, but the description does not accurately represent the full implemented scope because two advertised primary capabilities are absent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The code chunk is narrowly focused on Lightning payments through Boltz swaps for Arkade wallets. It creates and pays Lightning invoices, gets Lightning swap fees and limits, monitors pending/history of swaps, and manages swap-processing resources. The declared description is substantially broader, claiming Bitcoin send/receive over Arkade offchain and onchain plus stablecoin swaps. Those capabilities are not represented in this code chunk. While the declared description includes Lightning, it inaccurately describes this specific skill chunk because key claimed capabilities are absent.

Missing User Warnings

High
Confidence
97% confidence
Finding
The CLI example shows passing a private key as a command-line argument, which is especially dangerous because command-line arguments are commonly exposed through shell history, process listings, audit logs, crash reports, and agent telemetry. In a cryptocurrency wallet skill, disclosure of the private key enables full compromise of funds, making this more dangerous than a generic secret-handling issue.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README instructs users to initialize the wallet with a raw private key in a CLI argument, which is highly sensitive because command-line arguments can be exposed through shell history, process listings, logs, screenshots, or agent transcripts. In a skill intended for agent integration, this is more dangerous because agents often capture, replay, or store command invocations automatically, increasing the chance of credential compromise and irreversible theft of funds.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README documents live fund-transfer, invoice-payment, and swap operations without any warning that they move real assets, may be irreversible, and can incur fees or route funds to attacker-controlled destinations if misused. In an agent-skill context this is especially risky because users may delegate execution to automation, making accidental or prompt-induced transfers more plausible.

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill invokes package-managed commands and describes wallet initialization and transaction operations, but it declares no explicit tool scope or permissions. In an agent setting, missing scope boundaries can let the runtime expose broader capabilities than intended, increasing the chance of unintended environment access or unsafe execution paths.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- `swap-to-stable` / `swap-to-btc` — executes a stablecoin swap
- `swap-claim` / `swap-refund` — claims or refunds a swap

Read-only commands (`address`, `balance`, `history`, `ln-invoice`, `ln-fees`, `ln-limits`, `ln-pending`, `swap-quote`, `swap-pairs`, `swap-status`, `swap-pending`, `boarding-address`) are safe to run without confirmation.

**Wallet initialization:** `init` creates a new private key stored at `~/.arkade-wallet/config.json` (permissions `0600`). All other commands require `init` to have been run first. The agent MUST inform the user and get confirmation before running `init` for the first time.
Confidence
75% 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.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The quick-start flow uses npx/dlx to fetch and execute a package at runtime without a pinned version. This creates a supply-chain risk: a compromised latest release or dependency could be executed immediately in the user's environment, potentially alongside wallet operations.

Rp1

Medium
Category
MCP Rug Pull
Confidence
98% confidence
Finding
The command `npx -y -p @arkade-os/skill arkade ...` installs and executes the package dynamically without version pinning. Because this skill handles real funds and key material indirectly, executing an unpinned remote package materially increases the impact of package compromise.

Rp1

Medium
Category
MCP Rug Pull
Confidence
98% confidence
Finding
This is another instance of unpinned dynamic package execution in the command examples. Repetition of this pattern normalizes insecure installation for users, making supply-chain compromise more likely to affect real fund transfers.

Session Persistence

Medium
Category
Rogue Agent
Content
### Lightning Network

```bash
# Create a Lightning invoice to receive payment
arkade ln-invoice <amount-sats> [description]

# Example: Create invoice for 25,000 sats
Confidence
71% confidence
Finding
The skill stores wallet state and private keys in a persistent local config file and supports pending payment/swap operations, which creates session persistence around financial credentials and transaction state. Persistence itself is expected for a wallet, but in an agent environment it raises the risk of later unauthorized use if the host, account, or file permissions are compromised.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest says the skill can 'Swap USDC/USDT stablecoins,' but this CLI exposes a much broader stablecoin feature set including initiating swaps in both directions, claiming completed swaps, refunding failed swaps, checking status, and tracking pending swaps. Those are materially broader wallet/settlement operations than the top-level description communicates, especially because they reach into external chain settlement flows and lifecycle management.

Session Persistence

Medium
Category
Rogue Agent
Content
onboard                      Move funds from onchain to offchain (Arkade)
  offboard <btc-address>       Move funds from offchain to onchain

  ln-invoice <amount> [desc]   Create a Lightning invoice
  ln-pay <bolt11>              Pay a Lightning invoice
  ln-fees                      Show Lightning swap fees
  ln-limits                    Show Lightning swap limits
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
}

/**
 * Create a LendaSwapSkill with SQLite persistence for swaps and wallet data.
 * Stores data in ~/.arkade-wallet/lendaswap.db
 */
async function createLendaSwap() {
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The BTC-to-stablecoin swap command initiates an irreversible fund-moving operation immediately after parsing arguments, with no interactive confirmation, dry-run, or explicit risk acknowledgment. In an agent-integration context, this raises the chance of accidental or prompt-induced asset transfers to attacker-controlled addresses or wrong chains.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The stablecoin-to-BTC swap command also executes a value-bearing swap without any explicit warning or confirmation about consequences, fees, or destination semantics. Because this CLI is intended for agent use, a malicious or mistaken instruction could trigger a real transfer using the caller's configured wallet and EVM address context.

Vague Triggers

Medium
Confidence
85% confidence
Finding
The package description advertises broad, high-risk financial capabilities such as sending/receiving Bitcoin, Lightning transfers, and stablecoin swaps without indicating any user-consent gating, confirmation requirements, transaction limits, or scope restrictions. In an agent skill context, vague authorization boundaries around money movement materially increase the risk of unintended or over-broad invocation leading to irreversible asset transfer.

Session Persistence

Medium
Category
Rogue Agent
Content
"build:esm": "tsc -p tsconfig.esm.json --outDir dist/esm",
        "build:cjs": "tsc -p tsconfig.cjs.json --outDir dist/cjs",
        "build:types": "tsc -p tsconfig.json --outDir dist/types --emitDeclarationOnly",
        "format": "prettier --write src cli",
        "lint": "prettier --check src cli",
        "prepublishOnly": "pnpm run build"
    },
Confidence
81% confidence
Finding
The skill metadata declares persistent local config/database paths under the user's home directory, indicating session or wallet-related state survives across runs. For a financial skill, persistent wallet/config state can amplify damage from misuse, stale authorization, credential leakage, or cross-session confusion if the agent accesses prior sensitive state without fresh user approval.

Static analysis

No suspicious patterns detected.