Back to skill

Security audit

Fleet Communication System

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real fleet message bus, but it exposes unauthenticated network messaging and a writable dashboard in ways users should review carefully before installing.

Only use this skill on a tightly trusted network, preferably localhost or behind a VPN/firewall, and do not send secrets or sensitive task results through it. Treat incoming fleet messages as untrusted until authentication, authorization, escaping, size limits, and retention controls are added.

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 (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
fleet_bus.js:133
Finding
Unauthenticated Fleet Access, Message Disclosure, and Node Identity Spoofing<![CDATA[ ## Vulnerability Details **File Location**: `fleet_bus.js:133-174` **Vulnerability Type**: Missing authentication and authorization **Risk Level**: High ### Vulnerable Code ```js if (m === 'GET' && p === '/all') { return send(res, 200, allMsgs(100)); } if (m === 'GET' && p === '/status') { const total = fs.existsSync(MSG_FILE) ? fs.readFileSync(MSG_FILE,'utf8').trim().split('\n').filter(Boolean).length : 0; return send(res, 200, { status:'ok', port:PORT, total, nodes:Object.keys(knownNodes), uptime: process.uptime()|0 }); } if (m === 'GET' && p === '/nodes') return send(res, 200, knownNodes); if (m === 'GET' && p === '/messages') { const node = parsed.searchParams.get('node') || '00'; const since = parseInt(parsed.searchParams.get('since') || '0'); return send(res, 200, readMsgs(node, since)); } if (m === 'POST' && p === '/register') { return getBody(req, (e, data) => { if (e) return send(res, 400, { error: e.message }); regNode(data.nodeId, { ip: req.socket.remoteAddress, ...data }); send(res, 200, { ok:true, nodes: Object.keys(knownNodes) }); }); } if (m === 'POST' && p === '/send') { return getBody(req, (e, msg) => { if (e) return send(res, 400, { error: e.message }); if (!msg.from || !msg.to || !msg.msg) return send(res, 400, { error:'need from,to,msg' }); regNode(msg.from, { lastAction:'send' }); send(res, 200, { ok:true, entry: appendMsg(msg) }); }); } if (m === 'POST' && p === '/broadcast') { return getBody(req, (e, msg) => { if (e) return send(res, 400, { error: e.message }); msg.to = 'all'; if (!msg.from || !msg.msg) return send(res, 400, { error:'need from,msg' }); regNode(msg.from, { lastAction:'broadcast' }); send(res, 200, { ok:true, entry: appendMsg(msg) }); }); } send(res, 404, { error:'not found' }); }); server.listen(PORT, '0.0.0.0', () => console.log('🚌 Fleet Bus v1.1 on :' + PORT)); ``` ### Technical Analysis The message bus listens on every available net ...[truncated 2810 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind to `127.0.0.1` by default and require an explicit secure configuration to expose the service remotely. 2. Require authenticated transport, preferably mutual TLS for machine-to-machine communication. 3. Alternatively, require short-lived signed tokens with explicit node identity and permitted operations. 4. Derive the sender identity from the authenticated connection or token. Never trust `from` or `nodeId` supplied by the caller. 5. Apply endpoint-level authorization: - Only authorized coordinators should broadcast. - Nodes should only read messages addressed to their authenticated identity. - Remove or strictly restrict `/all`. - Prevent callers from replacing another node's registration. 6. Cryptographically sign messages and verify signatures before presenting them as trusted instructions. 7. Add replay protection through nonces, timestamps, and bounded validity periods. 8. Restrict port 18800 using host firewall rules and network access-control lists. 9. Log failed authentication, identity conflicts, and unusual message activity without logging sensitive message bodies unnecessarily. 10. Document that received message text must be treated as untrusted input and must not directly authorize tool or shell execution. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
fleet_bus.js:94
Finding
Stored Cross-Site Scripting in the Fleet Dashboard<![CDATA[ ## Vulnerability Details **File Location**: `fleet_bus.js:94-113` **Vulnerability Type**: Stored cross-site scripting through unsafe HTML rendering **Risk Level**: High ### Vulnerable Code ```js async function refresh(){ const s=await(await fetch(BASE+'/status')).json(); document.getElementById('status').innerHTML='消息: '+s.total+' | 运行: '+s.uptime+'秒'; const n=await(await fetch(BASE+'/nodes')).json(); document.getElementById('nodes').innerHTML=Object.entries(n).map(([k,v])=>'<span class="node online">'+k+' ('+( v.role||'agent')+')</span>').join(' '); const msgs=await(await fetch(BASE+'/messages?node=all&since=0')).json(); const all=await(await fetch(BASE+'/status')).json(); // get all messages via a trick - fetch for each node const r=await fetch(BASE+'/all'); let allMsgs=[]; try{allMsgs=await r.json();}catch{} const el=document.getElementById('msgs'); el.innerHTML=allMsgs.map(m=>{ const t=new Date(m.ts).toLocaleTimeString(); const cls=m.to==='all'?'msg broadcast':'msg'; return '<div class="'+cls+'"><span class="time">'+t+'</span> <span class="from">'+m.from+'</span> → <span class="to">'+m.to+'</span>: '+m.msg+'</div>'; }).join(''); el.scrollTop=el.scrollHeight; } ``` ### Technical Analysis The dashboard constructs HTML strings containing untrusted values and assigns those strings to `innerHTML`. The affected data includes: - Registered node IDs. - Registered node roles. - Message sender identities. - Message recipients. - Message bodies. These fields are controllable through the unauthenticated `/register`, `/send`, and `/broadcast` endpoints. Messages and node registrations are persisted before being displayed, making the vulnerability stored rather than merely reflected. No HTML escaping, sanitization, field validation, or restrictive Content Security Policy is present. An attacker can therefore store markup containing an event handler or another browser-executable HTML construct. The payload executes ...[truncated 1750 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace `innerHTML` with safe DOM construction: - Create elements with `document.createElement`. - Assign all untrusted values through `textContent`. - Append elements through `appendChild` or equivalent safe APIs. 2. If HTML rendering is genuinely required, process content with a well-maintained allowlist-based HTML sanitizer before insertion. 3. Validate all API fields server-side: - Require strings where expected. - Set conservative maximum lengths. - Restrict node IDs and message types to documented formats. - Reject control characters and malformed structures. 4. Add a restrictive Content Security Policy, for example disallowing inline scripts and event handlers and limiting outbound connections. 5. Add authentication and authorization to all data-writing endpoints so arbitrary network clients cannot persist dashboard content. 6. Consider separating the read-only dashboard from the write API and giving each a distinct origin and permission model. 7. Add automated tests using hostile HTML in every displayed field to verify that it is rendered exclusively as text. 8. Remove unused dashboard requests and minimize the amount of sensitive information exposed to browser clients. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
fleet_bus.js:23
Finding
Unbounded Request Buffering and Persistent Message Storage Enable Denial of Service<![CDATA[ ## Vulnerability Details **File Location**: `fleet_bus.js:23-41` **Vulnerability Type**: Unbounded resource consumption and synchronous file processing **Risk Level**: Medium ### Vulnerable Code ```js function appendMsg(msg) { const entry = { ...msg, ts: Date.now(), id: Math.random().toString(36).slice(2, 10) }; fs.appendFileSync(MSG_FILE, JSON.stringify(entry) + '\n'); return entry; } function readMsgs(node, since) { if (!fs.existsSync(MSG_FILE)) return []; return fs.readFileSync(MSG_FILE, 'utf8').trim().split('\n').filter(Boolean) .map(l => { try { return JSON.parse(l); } catch { return null; } }) .filter(m => m && (m.to === node || m.to === 'all') && m.ts > since); } function getBody(req, cb) { let d = ''; req.on('data', c => d += c); req.on('end', () => { try { cb(null, JSON.parse(d)); } catch (e) { cb(e); } }); } ``` Related full-log processing also occurs in the status handler: ```js if (m === 'GET' && p === '/status') { const total = fs.existsSync(MSG_FILE) ? fs.readFileSync(MSG_FILE,'utf8').trim().split('\n').filter(Boolean).length : 0; return send(res, 200, { status:'ok', port:PORT, total, nodes:Object.keys(knownNodes), uptime: process.uptime()|0 }); } ``` ### Technical Analysis `getBody` concatenates incoming request chunks into a string without enforcing a maximum body size. A single client can therefore cause the process to allocate memory proportional to the submitted request. Accepted messages are synchronously appended to an indefinitely growing JSON Lines file. Message count, message length, sender length, and recipient length are not limited. There is no rate limiting, storage quota, retention policy, or log rotation. Message retrieval and status handling use synchronous filesystem calls and load the complete message file into memory. They then split and parse the full contents while blocking the Node.js event loop. As the log grows, ordinary reads become increasingly expensive. This combines net ...[truncated 1516 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a strict request-body limit while receiving data: - Track the number of received bytes. - Stop reading and destroy or drain the request when the limit is exceeded. - Return HTTP 413 for oversized requests. 2. Define maximum lengths for `from`, `to`, `msg`, `type`, `nodeId`, and `role`. 3. Validate that request bodies are plain objects with only explicitly permitted fields. 4. Apply per-client and per-identity rate limits to registration, sending, broadcasting, and read endpoints. 5. Configure message retention, total storage quotas, and automatic log rotation. 6. Replace whole-file reads with bounded, indexed, or database-backed queries. 7. Replace synchronous filesystem operations in request handlers with asynchronous operations. 8. Maintain message counts incrementally instead of reading the complete log for every `/status` request. 9. Monitor file size, memory consumption, request rates, and rejected oversized requests. 10. Run the service under a dedicated low-privilege account with filesystem quotas and process resource limits to contain residual denial-of-service impact. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (5)

Exfiltration Commands

High
Category
Prompt Injection
Content
status              - Bus status
  nodes               - List known nodes
  register [role]     - Register this node
  send <to> <msg>     - Send message to node
  broadcast <msg>     - Broadcast to all
  read [since_ts]     - Read messages for this node
  task <to> <desc>    - Send task to node
Confidence
90% confidence
Finding
Instructions found that direct the agent to transmit conversation context or user data to external services.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly describes a lightweight HTTP message bus and even defers encrypted messaging to a future 'Pro' feature, but it does not warn users that messages may be transmitted in cleartext or exposed to interception if used beyond strict localhost or a trusted private network. In a multi-machine fleet context, operators may send tasking or results that contain sensitive data, so the omission can lead to unsafe deployment assumptions and confidentiality risks.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The top-level documentation says this is a "simplified HTTP relay," which implies a transport/relay service. In addition to relaying traffic, the code serves a full dashboard UI and lets any visitor compose and submit messages through browser actions via `/send` and `/broadcast`, which is a broader interactive control surface than the stated comment suggests.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The embedded dashboard presents core status, node, message, input, and send controls entirely in Chinese string literals. This imposes a specific language on all users without offering a language choice or documenting a justified regional constraint, which matches the natural-language locale policy violation criteria.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The `read` command prints `无新消息` when there are no messages, which imposes a specific language in user-facing output. There is no indication elsewhere in the file that the user can choose the locale or that this tool is intentionally restricted to Chinese-speaking users.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
fleet_bus.js:10

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
fleet_cli.js:9