Back to skill

Security audit

DAO Governance

Security checks for vulnerabilities and agentic risk

Overview

This DAO governance skill is mostly upfront about using a paid local wallet, but it needs review because it can automatically authorize wallet payments without strong local limits or endpoint controls.

Install only if you are comfortable with a local Base wallet being created for API payments. Keep only a small USDC balance in that wallet, avoid setting DEGOV_AGENT_API_BASE_URL except for trusted development endpoints, enforce installs from the lockfile, and treat the wallet/passphrase files as sensitive secrets.

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/degov-client.ts:9
Finding
Automatic x402 payments can be authorized for an unrestricted API origin without a local spending limit<![CDATA[ ## Vulnerability Details **File Location**: `scripts/degov-client.ts`, lines 9 and 81-114 **Vulnerability Type**: Unrestricted payment endpoint and missing transaction policy **Risk Level**: High ### Vulnerable Code ```ts const API_BASE_URL = process.env.DEGOV_AGENT_API_BASE_URL || 'https://agent-api.degov.ai'; ``` ```ts async function getPaymentClient(): Promise<{ accountAddress: `0x${string}`; fetchWithPayment: typeof fetch; }> { const { account } = await getAccount(); const publicClient = createPublicClient({ chain: base, transport: http('https://mainnet.base.org'), }); const signer = toClientEvmSigner(account, publicClient); return { accountAddress: account.address, fetchWithPayment: wrapFetchWithPaymentFromConfig(fetch, { schemes: [ { network: 'eip155:8453', client: new ExactEvmScheme(signer), }, ], }), }; } async function apiCall(endpoint: string): Promise<unknown> { const { accountAddress, fetchWithPayment } = await getPaymentClient(); const url = `${API_BASE_URL}${endpoint}`; console.error(`Using wallet: ${accountAddress}`); console.error(`Calling: ${url}`); const response = await fetchWithPayment(url); const paymentResponse = response.headers.get('PAYMENT-RESPONSE'); const text = await response.text(); ``` ### Technical Analysis The paid API client decrypts the locally stored wallet key, constructs an EVM signer, and gives that signer to the x402-enabled fetch wrapper. The destination is derived from `DEGOV_AGENT_API_BASE_URL`, which can contain an arbitrary origin. The implementation does not locally enforce: - An allowlist of trusted API hosts. - HTTPS for non-local endpoints. - An expected x402 payment recipient. - An expected USDC contract or payment asset. - A maximum amount per request. - A cumulative session or daily budget. - Interactive confirmation of the exact payment terms. Network transmission of signed x402 payment autho ...[truncated 2071 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict the default paid client to an explicit origin allowlist containing `https://agent-api.degov.ai`. 2. Reject non-HTTPS alternate origins, except an explicitly enabled loopback development mode. 3. Require a separate, clearly named unsafe-development flag before honoring `DEGOV_AGENT_API_BASE_URL`. 4. Validate every x402 challenge before signing: - Require Base Mainnet, chain ID 8453. - Require the intended USDC contract. - Require an approved payment recipient. - Reject unsupported schemes, assets, networks, and facilitators. 5. Add an enforceable maximum payment amount per request. 6. Add cumulative session and daily spending limits stored independently from server-provided pricing. 7. Show the exact recipient, token, network, and amount before signing. Require user confirmation unless the payment falls within a previously approved capped budget. 8. Fail closed when pricing metadata is unavailable for a paid operation rather than relying on informational fallback prices as a security boundary. 9. Add tests confirming that unapproved origins, recipients, assets, and excessive payment amounts are rejected before any authorization is generated. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wallet-store.ts:11
Finding
Wallet ciphertext and its automatically generated decryption secret are stored in the same state directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wallet-store.ts`, lines 11-13, 129-173, and 287-295 **Vulnerability Type**: Ineffective separation of encrypted wallet material and decryption secret **Risk Level**: Medium ### Vulnerable Code ```ts const DEFAULT_STATE_DIR = path.join(os.homedir(), '.agents', 'state', 'dao-governance'); export const DEFAULT_WALLET_PATH = path.join(DEFAULT_STATE_DIR, 'wallet.json'); export const DEFAULT_PASSPHRASE_PATH = path.join(DEFAULT_STATE_DIR, 'wallet-passphrase'); ``` ```ts function getStoredPassphrase(): string | null { const passphrasePath = getPassphrasePath(); if (!fs.existsSync(passphrasePath)) { return null; } normalizeWalletPermissions(passphrasePath); const passphrase = fs.readFileSync(passphrasePath, 'utf8').trim(); if (!passphrase) { throw new Error(`Wallet passphrase file is empty: ${passphrasePath}`); } return passphrase; } function generatePassphrase(): string { return crypto.randomBytes(32).toString('base64url'); } function getOrCreateStoredPassphrase(): string { const existing = getStoredPassphrase(); if (existing) { return existing; } const passphrase = generatePassphrase(); writeSecretFile(getPassphrasePath(), passphrase); return passphrase; } async function resolvePassphrase(options: { confirm?: boolean } = {}): Promise<string> { const fromEnv = process.env.DEGOV_AGENT_WALLET_PASSPHRASE; if (fromEnv) { return fromEnv; } if (!options.confirm) { const stored = getStoredPassphrase(); if (stored) { return stored; } } if (options.confirm) { return getOrCreateStoredPassphrase(); } const passphrase = await promptPassphrase('Wallet passphrase: '); if (!options.confirm) { return passphrase; } const confirmation = await promptPassphrase('Confirm wallet passphrase: '); if (passphrase !== confirmation) { throw new Error('Wallet passphrase confirmation does not match.'); } return passphras ...[truncated 3049 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the automatically generated secret in an operating-system credential facility, such as macOS Keychain, Windows Credential Manager, or the Linux Secret Service, rather than beside the ciphertext. 2. For the strongest local mode, require a user-entered passphrase and do not persist it to disk. 3. If unattended signing is required, use a restricted signer or wallet architecture that enforces recipient, asset, per-call, and cumulative spending limits. 4. Separate the wallet and decryption secret across independently protected storage boundaries. 5. Clearly document that same-directory automatic passphrase storage protects only against isolated wallet-file disclosure, not same-user or full-directory compromise. 6. Preserve mode `0600`, and also ensure the parent directory has restrictive permissions such as `0700`. 7. Reject symbolic links and verify file ownership before reading or writing wallet and passphrase files. 8. Consider memory-hard KDF parameters explicitly configured and versioned in the encrypted payload so their security properties remain predictable across runtime versions. 9. Provide a secure migration path that moves existing passphrases into the operating-system credential store and removes the old passphrase file only after successful verification. 10. Continue advising users to maintain a minimal wallet balance, but treat this only as defense in depth rather than as the primary protection. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The documented behavior includes creating, encrypting, storing, migrating, and deleting wallet-related files and handling passphrases, yet the skill framing emphasizes governance Q&A rather than secret management and blockchain account operations. That discrepancy increases the risk that sensitive local file and key-management actions occur under a misleading trust model, which could expose funds or credentials if the implementation is flawed or abused.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The documented behavior includes creating, encrypting, storing, migrating, and deleting wallet-related files and handling passphrases, yet the skill framing emphasizes governance Q&A rather than secret management and blockchain account operations. That discrepancy increases the risk that sensitive local file and key-management actions occur under a misleading trust model, which could expose funds or credentials if the implementation is flawed or abused.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill invokes networked API access and local wallet/state management behavior, but it declares no explicit tool scope or permissions. That makes the skill's effective capabilities broader and less reviewable than its metadata suggests, increasing the chance of unintended network access or sensitive local-state interaction without clear operator awareness.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Guardrails

- Do not ask users to paste private keys.
- Use the local managed wallet for API payments.
- Use an internally managed local passphrase by default for encrypted storage, unless an explicit override is provided.
- Use `budget` when you need the current API pricing table.
Confidence
80% 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.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The code performs paid onchain transaction handling through fetchWithPayment, causing real blockchain-backed payment activity despite the manifest framing the skill as an information-retrieval tool. This mismatch is dangerous because users or integrators may invoke seemingly read-only commands without realizing they can trigger spending, and the code trusts a configurable API base URL that could direct paid requests to an unintended service.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The script goes beyond passive DAO-governance information retrieval by creating, storing, displaying, and funding a local blockchain payment wallet. Even if intended only for x402 API fee settlement, this expands the skill’s privilege and asset-handling scope, creating financial-risk surface area if the wallet is misused, compromised, or if users misunderstand and fund it with real assets.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The implementation exceeds the stated DAO-governance lookup purpose by creating/managing wallets and querying on-chain balances. Scope mismatch matters in agent skills because users may grant trust based on the manifest description, while hidden financial-account functionality increases attack surface and can be abused for surveillance, custody, or later transaction features.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
This file implements local wallet generation, encryption, migration, and secret persistence even though the skill is described as a DAO-governance information skill. That capability expansion is risky because it enables custody of private keys and handling of secret material that users would not reasonably expect from a read-oriented governance assistant, increasing the chance of unauthorized asset access if other parts of the skill invoke it.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The migration routine deletes the source wallet file after writing the target, with no explicit warning, confirmation, backup, or recovery flow shown here. For secret-bearing files, silent deletion can cause irreversible loss of access if the new file path is misconfigured, corrupted, unreadable, or tied to a different passphrase source.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"wallet:balance": "pnpm exec tsx degov-client.ts wallet balance"
  },
  "dependencies": {
    "@x402/evm": "^2.6.0",
    "@x402/fetch": "^2.6.0",
    "viem": "^2.37.5"
  },
Confidence
88% confidence
Finding
The dependency uses a caret range, which permits automatic installation of newer compatible releases. This increases supply-chain risk because a compromised or breaking upstream release could be pulled in during future installs without an explicit review, especially for a Web3-related skill that includes wallet functionality.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "@x402/evm": "^2.6.0",
    "@x402/fetch": "^2.6.0",
    "viem": "^2.37.5"
  },
  "devDependencies": {
Confidence
88% confidence
Finding
This package is referenced with a caret semver range, allowing dependency drift over time. If the upstream package or one of its transitive dependencies is compromised, future installs may consume the malicious version, which is relevant here because the skill relies on external Web3/network tooling.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "@x402/evm": "^2.6.0",
    "@x402/fetch": "^2.6.0",
    "viem": "^2.37.5"
  },
  "devDependencies": {
    "@types/node": "^24.3.0",
Confidence
88% confidence
Finding
Using a non-exact version for a core Web3 library means builds are not fully reproducible and can silently change behavior or pull in a compromised release. In DAO governance and wallet-adjacent code, even small upstream changes can affect transaction handling or chain interaction safety.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"viem": "^2.37.5"
  },
  "devDependencies": {
    "@types/node": "^24.3.0",
    "prettier": "^3.6.2",
    "tsx": "^4.20.4",
    "typescript": "^5.9.2"
Confidence
80% confidence
Finding
Although this is a development dependency, the caret range still permits unreviewed version changes in developer and CI environments. A compromised dev tool can affect builds, generated artifacts, or local execution, creating a supply-chain entry point even if it is not shipped at runtime.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "@types/node": "^24.3.0",
    "prettier": "^3.6.2",
    "tsx": "^4.20.4",
    "typescript": "^5.9.2"
  }
Confidence
80% confidence
Finding
Prettier is a development tool, but leaving it unpinned allows future installs to resolve to different code than originally reviewed. If an upstream release is compromised, local development or CI jobs invoking formatting commands could execute attacker-controlled package code.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"devDependencies": {
    "@types/node": "^24.3.0",
    "prettier": "^3.6.2",
    "tsx": "^4.20.4",
    "typescript": "^5.9.2"
  }
}
Confidence
84% confidence
Finding
tsx is an executable dev tool used to run TypeScript directly, so an unpinned version presents meaningful supply-chain risk in local and CI execution contexts. A malicious or compromised update could lead to arbitrary code execution when scripts are run.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@types/node": "^24.3.0",
    "prettier": "^3.6.2",
    "tsx": "^4.20.4",
    "typescript": "^5.9.2"
  }
}
Confidence
80% confidence
Finding
TypeScript is a build-time dependency, and a caret range permits toolchain drift that may introduce malicious or unsafe code into development workflows. While impact is lower than an exposed runtime dependency, compromised build tools can still affect CI integrity and generated outputs.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The code accesses a sensitive credential via DEGOV_AGENT_WALLET_PASSPHRASE, but there is no comment, log, or other disclosure in this file indicating that secret material may be sourced from process environment. Access to credentials is safety-relevant because environment variables can be exposed through shell history, process inspection, or misconfiguration.

Missing User Warnings

Low
Confidence
85% confidence
Finding
getUsdcBalance sends the supplied address to a public RPC endpoint over HTTP transport to query on-chain balance. There is no visible log, comment, or inline disclosure here that the address will be sent to an external service, which is relevant to privacy expectations.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/degov-client.ts:9

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/wallet-store.ts:254