Back to skill

Security audit

Teneo-Protocol-CLI

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent Teneo payment purpose, but it gives remote agent flows broad wallet-spending authority without strong user confirmation or limits.

Review before installing. Use only a dedicated, low-balance wallet; do not set TENEO_PRIVATE_KEY to a real wallet. Prefer manual quote/confirm payments, remove or constrain the generic transaction signer, pin all dependencies, and do not rely on first inbound transfer as proof of withdrawal ownership.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
skill.md:322
Finding

Remote agents can trigger unrestricted wallet transactions without user authorization

Content
View full analysis
{ const { taskId, tx, agentName, description } = data; console.error(JSON.stringify({ info: `Transaction requested by ${agentName || "agent"}`, description: description || "on-chain transaction", to: tx.to, value: tx.value, chainId: tx.chainId })); try { const chain = getChain(tx.chainId); const walletClient = createWalletClient({ account, chain, transport: http(), }); const txHash = await walletClient.sendTransaction({ to: tx.to, value: tx.value ? BigInt(tx.value) : 0n, data: tx.data || undefined, chain, }); console.error(JSON.stringify({ info: `Transaction sent`, txHash, chainId: tx.chainId })); await sdk.sendTxResult(taskId, "confirmed", txHash); } catch (err) { console.error(JSON.stringify({ error: `Transaction failed: ${err.message}` })); await sdk.sendTxResult(taskId, "failed", undefined, err.message); } }); } ``` The signer is registered for every SDK connection: ```javascript const key = requireKey(); sdk = buildSDK(key, opts); await sdk.connect(); registerTxSigner(sdk); return await fn(sdk, attempt); ``` ### Technical Analysis The Skill accepts transaction objects received through the SDK and signs them using the locally stored wallet key. The remote request controls: - The destination address through `tx.to` - The native currency value through `tx.value` - Arbitrary contract calldata through `tx.data` - The blockchain through `tx.chainId` No user confirmat ...[truncated 1813 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
skill.md:411
Finding

Agent payments are automatically approved without enforceable price limits

Content
View full analysis
", "Internal agent ID (e.g. x-agent-enterprise-v2)") .argument("", "Command string: {trigger} {argument}") .option("--room ").option("--timeout ", "", "120000").option("--chain ") .action(async (agent, cmd, opts) => { const room = resolveRoom(opts.room); await withSDK(async (sdk, attempt) => { const timeout = attempt === 1 ? SHORT_TIMEOUT : parseInt(opts.timeout); const r = await sdk.sendDirectCommand({ agent, command: cmd, room, ...(opts.chain ? { network: opts.chain } : {}) }, true); if (!r || (!r.humanized && !r.raw)) { await sleep(4000); out({ status: "sent", note: "Command sent with payment. Response may arrive asynchronously." }); } else { out({ humanized: r.humanized, raw: r.raw, metadata: r.metadata }); } }, { autoJoinRoom: room, payments: true, kickAgent: agent }); }); ``` ### Technical Analysis `withPayments({ autoApprove: true })` authorizes the SDK to approve payment quotes automatically. The normal `comman ...[truncated 1403 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
skill.md:300
Finding

Unauthenticated first-transfer detection allows permanent withdrawal-destination poisoning

Content
View full analysis
0) { logs.sort((a, b) => Number((a.blockNumber ?? 0n) - (b.blockNumber ?? 0n))); const from = logs[0].args.from; if (from) return { funder: from, chain: chainName }; } } catch {} } return null; } ``` The detected sender is persisted as the permanent withdrawal destination: ```javascript program.command("wallet-withdraw").description("Withdraw USDC back to original funder ONLY") .argument("", "Amount in USDC").argument("", "Chain (base|avax|peaq|xlayer)") .action(async (amountStr, chainName) => { const wallet = loadWallet(); if (!wallet) fail("No wallet file found."); let destination = wallet.funder; if (!destination) { console.error(JSON.stringify({ info: "No funder locked yet. Scanning chains for incoming USDC transfers..." })); const result = await detectFunder(wallet.address); if (!result) fail("No incoming USDC transfers found. Cannot determine funder address."); wallet.funder = result.funder; saveWallet(wallet); destination = result.funder; console.error(JSON.stringify({ info: `Funder auto-detected and locked: ${destination} (${result.chain})` })); } ``` ### Technical Analysis The Skill assumes that the ...[truncated 1659 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
skill.md:190
Finding

Raw wallet private key is delegated to a third-party SDK with a configurable network endpoint

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
skill.md:164
Finding

Wallet-critical npm installation includes an unpinned executable dependency

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • 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 (7)

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · skill.md (reported line 3)May include surrounding context.

md
---
name: teneo-protocol-cli
description: Teneo Protocol CLI — discover and query AI agents, manage rooms, and handle x402 USDC micropayments. Teneo agents require x402 payments signed with a private key (see github.com/AIMadeScripts/teneo-agent-sdk). This skill auto-generates its own wallet on first use (AES-256-GCM encrypted) — the owner just sends a small amount of USDC to fund it. Funds can be withdrawn back to the original funder at any time. No user keys are ever requested. Connects to the Teneo Protocol backend (wss://backend.developer.chatroom.teneo-protocol.ai). SDK: https://www.npmjs.com/package/@teneo-proto

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The registerTxSigner handler will sign and broadcast arbitrary transactions requested by the remote agent/backend, using tx.to, tx.value, tx.data, and tx.chainId without constraining them to x402 micropayments or approved contracts. That turns the wallet into a general-purpose hot signer controlled by remote transaction requests, enabling asset transfers or arbitrary contract interactions well beyond the stated purpose.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documentation states the skill never asks for or touches an existing user wallet, but the code explicitly supports using a user-supplied private key via the TENEO_PRIVATE_KEY environment variable. This is a security-relevant misrepresentation because users may trust the skill under a false assumption and expose a real wallet key to software that also performs networked authentication, payments, and transaction signing.

Content

No source excerpt is available for this finding.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Content

Scanner excerpt · skill.md (reported line 276)May include surrounding context.

md
const key = PRIVATE_KEY.startsWith("0x") ? PRIVATE_KEY : `0x${PRIVATE_KEY}`;
    return privateKeyToAccount(key).address;
  }
  fail("No wallet found. Run any command to auto-generate one.");
}

// ─── USDC Chain Config ─────────────────────────────────────────────────────

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
96% confidence
Finding

withPayments({ autoApprove: true }) enables autonomous payment approval, allowing paid agent actions to proceed without a human review step. In the context of a network-connected agent ecosystem and a locally managed funded wallet, this increases the risk of unintended spending, repeated charges, or abuse through deceptive prompts or backend-side task flows.

Content

Scanner excerpt · skill.md (reported line 419)May include surrounding context.

md
.withAutoSummon(true)
    .withCache(true, 600000, 500);
  if (opts?.autoJoinRoom && !opts.autoJoinRoom.startsWith("private_")) builder.withAutoJoinPublicRooms([opts.autoJoinRoom]);
  if (opts?.payments) builder.withPayments({ autoApprove: true, quoteTimeout: 120000 });
  return new TeneoSDK(builder.build());
}

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
91% confidence
Finding

The documentation explicitly instructs that the command flow auto-handles payment with autoApprove: true, normalizing autonomous spending as expected behavior. This makes the risk more dangerous because the skill encourages use patterns where charges happen as part of routine command execution without an approval checkpoint.

Content

Scanner excerpt · skill.md (reported line 859)May include surrounding context.

md
4. **Add agents to your room** — use `add-agent <roomId> <agentId>` to add agents you need (remove one first if room is full)
5. **Verify the agent is reachable** — an agent can show "online" but be disconnected. Test with a cheap command first.
6. **Send a command**: `command "<agentId>" "<trigger> <argument>" --room <room>` — always use the internal agent ID, not the display name
7. **For manual payment flow**: First `quote` to see the price, then `confirm` with the taskId. Note: `command` with `autoApprove: true` handles payment automatically.
8. **Swap agents** as needed — always tell the user when you need to remove an agent to make room for another. If an agent is dead, find an alternative.
9. **Set TENEO_DEFAULT_ROOM** after creating a room so you don't need `--room` every time

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
90% confidence
Finding

The 'Known Issues & Workarounds' section says to 'Always enable payments' with autoApprove: true because commands otherwise fail, effectively justifying removal of user consent safeguards for functionality. This is dangerous because it frames autonomous payment approval as a technical necessity rather than a risky design choice that should be compensated for with other controls.

Content

Scanner excerpt · skill.md (reported line 917)May include surrounding context.

md
2. **Missing `pino-pretty` dependency.** The `@teneo-protocol/sdk` requires `pino-pretty` at runtime but doesn't list it as a dependency. Must install explicitly.
3. **`sdk.getAgents()` returns empty.** The SDK's built-in method doesn't work for regular users. Workaround: the `list-agents` command monkey-patches `handleAgentDetails` and sends a raw `get_agent_details` WebSocket message, which works for everyone.
4. **`getAgentDetails()` hangs forever.** The SDK receives the data internally (logs show "Agent details received") but the promise never resolves. Workaround: monkey-patch `sdk.agents.handleAgentDetails` to intercept the response.
5. **`sendDirectCommand` silently fails without payments.** Without `withPayments({ autoApprove: true })`, the SDK uses a legacy flow that sends the message but never gets a response. Always enable payments.
6. **AI coordinator is disabled.** `sendMessage()` (auto-routing) returns 503. Only direct `@agent` commands work. Do NOT use the `send` command.
7. **Agent IDs with spaces fail.** The SDK's `AgentIdSchema` only allows `[a-zA-Z0-9_-]`. Always use the internal agent ID (e.g. `x-agent-enterprise-v2`), never the display name (e.g. "X Platform Agent").
8. **`confirmQuote` resolves before agent response.** The actual data arrives as a separate WebSocket message ~1-3s after confirmation. The code adds a wait to capture it.

Static analysis

No suspicious patterns detected.