Back to skill

Security audit

Agent Comm Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its agent-communication purpose, but it under-declares network and signing capabilities and has concrete weaknesses around private-key use, relay connections, and local key storage.

Install only if you are prepared to review and constrain it first. It should use validated identity IDs, explicit authorization for each private-key signing operation, wss:// or trusted local-only relays, complete manifest permissions/actions, and updated dependencies before handling real credentials or agent identities.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/vault.js:6
Finding
Keystore Path Traversal Through Attacker-Controlled Identity Names## Vulnerability Details **File Location**: `scripts/vault.js:6-33` **Vulnerability Type**: Path traversal and unauthorized filesystem access **Risk Level**: High ### Vulnerable Code ```js const VAULT_PATH = path.join(process.cwd(), 'data/keystore'); module.exports = { getOrGenerateIdentity: async (localAgentId) => { await sodium.ready; if (!fs.existsSync(VAULT_PATH)) fs.mkdirSync(VAULT_PATH, { recursive: true }); const keyPath = path.join(VAULT_PATH, `${localAgentId}.keys.json`); if (fs.existsSync(keyPath)) { const keys = JSON.parse(fs.readFileSync(keyPath)); return { publicKey: Buffer.from(keys.publicKey, 'hex'), privateKey: Buffer.from(keys.privateKey, 'hex') }; } const keypair = sodium.crypto_sign_keypair(); fs.writeFileSync(keyPath, JSON.stringify({ publicKey: sodium.to_hex(keypair.publicKey), privateKey: sodium.to_hex(keypair.privateKey) }), { mode: 0o600 }); return { publicKey: keypair.publicKey, privateKey: keypair.privateKey }; }, signPayload: async (localAgentId, payloadString) => { await sodium.ready; const keyPath = path.join(VAULT_PATH, `${localAgentId}.keys.json`); const keys = JSON.parse(fs.readFileSync(keyPath)); const signature = sodium.crypto_sign_detached( payloadString, Buffer.from(keys.privateKey, 'hex') ); return sodium.to_hex(signature); }, ``` The attacker-controlled values originate from `index.js:26-27` and `index.js:65-67`: ```js async function registerIdentity(params) { const localAgentId = params.alias || `agent-${uuidv4()}`; const keys = await vault.getOrGenerateIdentity(localAgentId); } async function signMessage(params) { const { localId, payload } = params; const contentStr = typeof payload === 'string' ? payload : JSON.stringify(payload); const signatureHex = await vault.signPayload(local ...[truncated 1972 chars]
Remediation
## Remediation Suggestions - Restrict identity names to a conservative allowlist, such as `/^[A-Za-z0-9_-]{1,64}$/`. - Reject path separators, `.` and `..` components, control characters, and platform-specific separator variants. - Resolve the final path and verify containment before every read or write: ```js const base = path.resolve(VAULT_PATH); const candidate = path.resolve(base, `${localAgentId}.keys.json`); if (!candidate.startsWith(base + path.sep)) { throw new Error('Invalid identity identifier'); } ``` - Use opaque, randomly generated identifiers as file names rather than user-provided aliases. - Store the human-readable alias as validated metadata instead of using it as a path component. - Protect against symlink-based redirection by checking path components and using safe file-open flags. - Consider exclusive creation (`wx`) when creating new identities to prevent unintended replacement. - Apply the same centralized path-validation routine to registration and signing.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.js:65
Finding
Private-Key Signing Operations Lack Caller Authorization## Vulnerability Details **File Location**: `index.js:8-17, 65-69` **Vulnerability Type**: Missing authorization for sensitive cryptographic operations **Risk Level**: High ### Vulnerable Code ```js export async function run(action, params, context) { try { switch (action) { case 'agent.register': return await registerIdentity(params); case 'message.sign': return await signMessage(params); case 'message.verify': return await verifyMessage(params); case 'network.connect': return await connectWithAuth(params); case 'network.broadcast': return await broadcastUpdate(params); case 'secret.genKey': return { success: true, data: { key: await vault.generateSessionKey() } }; case 'secret.encrypt': return await encryptAction(params); case 'secret.decrypt': return await decryptAction(params); default: return { success: false, error: `Action ${action} unsupported` }; } } ``` ```js async function signMessage(params) { const { localId, payload } = params; const contentStr = typeof payload === 'string' ? payload : JSON.stringify(payload); const signatureHex = await vault.signPayload(localId, contentStr); return { success: true, data: { signature: signatureHex } }; } ``` The underlying key use occurs at `scripts/vault.js:27-33`: ```js signPayload: async (localAgentId, payloadString) => { await sodium.ready; const keyPath = path.join(VAULT_PATH, `${localAgentId}.keys.json`); const keys = JSON.parse(fs.readFileSync(keyPath)); const signature = sodium.crypto_sign_detached( payloadString, Buffer.from(keys.privateKey, 'hex') ); return sodium.to_hex(signature); }, ``` ### Technical Analysis The exported `run()` function receives a `context` argument, but the implementation does not use it for authentication or authorization. `m ...[truncated 1679 chars]
Remediation
## Remediation Suggestions - Bind each created identity to an authenticated principal from the invocation context. - Enforce ownership or explicit delegated authorization before every signing and relay-connection operation. - Move authorization into the vault layer so callers cannot bypass it through another code path. - Replace guessable aliases used as key handles with opaque, unguessable identifiers. - Require narrowly scoped capability tokens for signing, including permitted identity, operation, audience, and expiration. - Record security audit events for identity creation, signing attempts, successful signing, and authorization failures. - Reject calls when no authenticated context is available rather than treating missing context as trusted. - Add tests proving that one agent cannot sign with or authenticate using another agent's identity.

T09 · Insecure Skill Coding Practices

Error
Location
index.js:31
Finding
Relay Authentication Uses Plaintext WebSockets and Signs Unscoped Challenges## Vulnerability Details **File Location**: `index.js:31-56` **Vulnerability Type**: Insecure transport and cryptographic protocol design **Risk Level**: High ### Vulnerable Code ```js async function connectWithAuth(params) { const { sessionId, localId, did, relayUrl = 'ws://localhost:3001', onUpdate } = params; if (onUpdate) updateCallbacks.set(sessionId, onUpdate); return new Promise((resolve, reject) => { const ws = new WebSocket(relayUrl); ws.on('message', async (data) => { const msg = JSON.parse(data.toString()); if (msg.action === 'challenge') { const signRes = await vault.signPayload(localId, msg.challenge); ws.send(JSON.stringify({ action: 'subscribe', sessionId, did, challenge: msg.challenge, signature: signRes })); } if (msg.action === 'authorized') { relayConnections.set(sessionId, ws); resolve({ success: true, data: { status: 'Authorized' } }); } if (msg.action === 'update') { const cb = updateCallbacks.get(sessionId); if (cb) cb(msg.payload); } }); ws.on('open', () => ws.send(JSON.stringify({ action: 'auth_request' }))); ws.on('error', (err) => reject({ success: false, error: err.message })); }); } ``` ### Technical Analysis The default relay URL uses plaintext `ws://`, which does not provide transport confidentiality, server authentication, or integrity. The function also permits a caller-selected relay URL without an origin allowlist. When a relay sends a challenge, the code signs `msg.challenge` directly. The signed data has no domain-separation prefix and does not cryptographically inclu ...[truncated 1943 chars]
Remediation
## Remediation Suggestions - Require `wss://` for relay connections. Permit plaintext loopback connections only through an explicit development-only setting. - Apply a strict allowlist of approved relay origins and reject credentials embedded in URLs. - Sign a canonical, domain-separated structure rather than arbitrary relay bytes. It should include: - Protocol and version - Operation name - Relay origin - Session ID - DID or public-key fingerprint - Cryptographically random nonce - Issuance and expiration timestamps - Validate the challenge schema, type, size, randomness, freshness, and single-use status before signing. - Derive the DID from the selected local public key or verify that the supplied DID exactly matches that key. - Bind the authorization response to the challenge and session rather than accepting a standalone `authorized` action. - Add connection, authentication, and message-processing timeouts. - Catch JSON parsing and asynchronous signing errors inside the message handler and close the connection on protocol violations.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
plugin.json:6
Finding
Executable Network Capabilities and Actions Are Missing From the Plugin Manifest## Vulnerability Details **File Location**: `plugin.json:6-33` **Vulnerability Type**: Permission and capability declaration mismatch **Risk Level**: Medium ### Vulnerable Code The manifest declares only local storage and cryptography permissions: ```json { "id": "agent-comm-skill", "name": "Agent Communication Skill", "version": "1.0.0", "description": "Enables agents to register and communicate securely using DID and Ed25519 signatures.", "permissions": ["local-storage", "cryptography"], "entry": "dist/index.js", "skills": [ { "action": "agent.register", "description": "Register a new agent identity or restore an existing one", "parameters": [ { "name": "alias", "type": "string", "required": false } ] }, { "action": "message.sign", "description": "Securely sign CRDT payload using Vault ed25519 (Private)", "parameters": [ { "name": "localId", "type": "string", "required": true }, { "name": "payload", "type": "object", "required": true } ] }, { "action": "message.verify", "description": "Verify external signatures to validate task assignments", "parameters": [ { "name": "publicKeyHex", "type": "string", "required": true }, { "name": "payload", "type": "object", "required": true }, { "name": "signatureHex", "type": "string", "required": true } ] } ] } ``` However, `index.js:8-18` exposes additional actions: ```js switch (action) { case 'agent.register': return await registerIdentity(params); case 'message.sign': return await signMessage(params); case 'message.verify': return await verifyMessage(params); case 'network.connect': return await connectWithAuth(params); case 'network.broadcast': return await broadcastUpdate(params); case 'secret.genKey': return { success: tru ...[truncated 2803 chars]
Remediation
## Remediation Suggestions - Declare the network permission required by the target host platform. - Register every callable action in `plugin.json`, including: - `network.connect` - `network.broadcast` - `secret.genKey` - `secret.encrypt` - `secret.decrypt` - Document all executable actions and their security implications in `SKILL.md`. - Remove actions that are not intended to be externally callable. - Restrict network destinations by scheme, hostname, port, and environment. - Add automated checks that compare action names in the implementation, manifest, and documentation. - Ensure host-side runtime enforcement blocks undeclared capabilities instead of relying only on documentation or user review. - Keep `index.ts`, generated JavaScript, `plugin.json`, and `SKILL.md` synchronized during release builds.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (19)

Known Vulnerable Dependency: path-to-regexp==0.1.12 — 1 advisory(ies): CVE-2024-45296 (path-to-regexp vulnerable to Regular Expression Denial of Service via multiple r)

High
Category
Supply Chain
Confidence
94% confidence
Finding
path-to-regexp 0.1.12 is an old routing parser version associated with Regular Expression Denial of Service. Because it is used by express for route matching, maliciously crafted request paths can potentially consume excessive CPU and degrade or block service availability.

Known Vulnerable Dependency: ws==8.19.0 — 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
92% confidence
Finding
ws 8.19.0 is flagged for both uninitialized memory disclosure and memory-exhaustion denial of service. In an agent communication skill, WebSocket handling is likely central and network-facing, which makes these flaws materially more dangerous because a remote peer may trigger data leakage or resource exhaustion over the communication channel.

Known Vulnerable Dependency: ws==8.19.0 — 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
97% confidence
Finding
The package depends on ws 8.19.0, which is identified with advisories for uninitialized memory disclosure and memory exhaustion via tiny fragments/data chunks. Because this skill is explicitly an agent communication component and likely exposes or consumes WebSocket traffic, the vulnerable dependency sits directly on a network-facing path, making remote information disclosure or denial-of-service materially more plausible.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill exposes network connectivity to a relay server and encryption/decryption of sensitive credentials, but the description provides no user-facing warnings about trust boundaries, relay authenticity, metadata exposure, or the risks of handling secrets. In an agent swarm context, this omission can lead operators or downstream agents to connect to untrusted infrastructure or process credentials without adequate safeguards, increasing the chance of credential leakage or interception through misconfiguration or unsafe use.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill establishes outbound WebSocket connections to an arbitrary relay URL and sends authentication material and broadcast payloads without any user-facing disclosure, consent flow, or trust validation. In an agent skill context, silent network egress is risky because callers may unknowingly send identifiers, signed challenges, and message payloads to attacker-controlled infrastructure, especially since the default transport is insecure ws:// rather than encrypted wss://.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This code persists a newly generated private key to a local file in `data/keystore`, which is a safety-relevant file write involving credential material. There is no confirmation prompt, user-facing log/print, or explanatory comment/docstring warning that secret keys will be stored on disk.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The function loads a private key from a local keystore file and uses it to sign payloads, which is access to sensitive credential material. The file contains no user-facing notice, confirmation, or explanatory documentation/comment describing this sensitive key usage.

Known Vulnerable Dependency: body-parser==1.20.4 — 1 advisory(ies): CVE-2026-12590 (body-parser vulnerable to denial of service when invalid limit value silently di)

Low
Category
Supply Chain
Confidence
76% confidence
Finding
body-parser 1.20.4 is flagged for a denial-of-service condition related to handling invalid limit values. In this lockfile it is pulled in transitively via express/loro, so the issue is real at the dependency level, though actual exploitability depends on whether attacker-controlled body size limits are exposed in application code.

Known Vulnerable Dependency: qs==6.14.2 — 3 advisory(ies): CVE-2026-82417 (qs: Denial of Service via Attacker Controlled isBuffer); CVE-2026-8723 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/u); CVE-2026-82562 (qs array-limit bypass via bracket-key comma parsing)

Low
Category
Supply Chain
Confidence
72% confidence
Finding
qs 6.14.2 is flagged for multiple denial-of-service and parser-limit issues. The vulnerability is real in the supply chain, but impact in this file is likely limited unless the skill parses attacker-controlled query strings or body data using affected qs code paths.

Known Vulnerable Dependency: uuid==13.0.0 — 1 advisory(ies): CVE-2026-41907 (uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided)

Low
Category
Supply Chain
Confidence
61% confidence
Finding
uuid 13.0.0 is reported as missing buffer bounds checks for specific versions when a caller supplies a buffer argument. This is a real package-level weakness, but exploitability depends on the application actually invoking v3/v5/v6 APIs with attacker-influenced buffer parameters, which is not evident from the lockfile alone.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"start": "node dist/index.js"
  },
  "dependencies": {
    "libsodium-wrappers": "^0.7.16",
    "loro": "^1.0.0",
    "uuid": "^13.0.0",
    "ws": "^8.19.0"
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "libsodium-wrappers": "^0.7.16",
    "loro": "^1.0.0",
    "uuid": "^13.0.0",
    "ws": "^8.19.0"
  },
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "libsodium-wrappers": "^0.7.16",
    "loro": "^1.0.0",
    "uuid": "^13.0.0",
    "ws": "^8.19.0"
  },
  "devDependencies": {
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Known Vulnerable Dependency: uuid==13.0.0 — 1 advisory(ies): CVE-2026-41907 (uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided)

Low
Category
Supply Chain
Confidence
80% confidence
Finding
The manifest includes uuid 13.0.0, which is flagged with a bounds-checking advisory affecting certain v3/v5/v6 buffer-using code paths. If the skill or a transitive consumer invokes those APIs with attacker-influenced buffer arguments, this could cause memory corruption-like behavior, crashes, or unexpected data handling in contexts that process untrusted input.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"libsodium-wrappers": "^0.7.16",
    "loro": "^1.0.0",
    "uuid": "^13.0.0",
    "ws": "^8.19.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"ws": "^8.19.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/uuid": "^10.0.0",
    "@types/ws": "^8.18.1",
    "typescript": "^5.0.0"
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/uuid": "^10.0.0",
    "@types/ws": "^8.18.1",
    "typescript": "^5.0.0"
  }
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"devDependencies": {
    "@types/node": "^20.0.0",
    "@types/uuid": "^10.0.0",
    "@types/ws": "^8.18.1",
    "typescript": "^5.0.0"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@types/node": "^20.0.0",
    "@types/uuid": "^10.0.0",
    "@types/ws": "^8.18.1",
    "typescript": "^5.0.0"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

No suspicious patterns detected.