Back to skill

Security audit

AOI Squad Orchestrator (Lite)

Security checks for vulnerabilities and agentic risk

Overview

This skill is a small local CLI for preset team reports that stores nickname preferences locally, with no evidence of network access, credential access, or hidden execution.

Before installing, understand that this CLI keeps team nickname mappings in ~/.openclaw/aoi/squad_names.json until you remove or change them. Treat report_markdown as untrusted Markdown if displaying it elsewhere, especially in renderers that load remote images or links automatically.

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

T09 · Insecure Skill Coding Practices

Note
Location
skill.js:153
Finding
Markdown Injection in Generated Report<![CDATA[ ## Vulnerability Details **File Location**: `skill.js`, lines 153-178 **Vulnerability Type**: Improper neutralization of Markdown syntax in user-controlled report fields **Risk Level**: Low ### Vulnerable Code ```js function mdEscape(s) { return String(s ?? "").replace(/</g, "&lt;").replace(/>/g, "&gt;"); } function cmdRun(args) { const preset = args.preset; const task = args.task || ""; if (!preset) return fail("Missing --preset"); if (!task) return fail("Missing --task"); if (!PRESETS[preset]) return fail(`Unknown preset: ${preset}`); const db = loadNames(); const teamNames = getOrInitTeam(db, preset); saveNames(db); const started = nowIso(); const ended = nowIso(); const runId = `run_${Date.now()}`; const team = PRESETS[preset].roles.map(r => { const nickname = teamNames[r.key]; return { nickname, role: r.archetype, objective: r.objective, output: "(MVP placeholder) — integrate actual multi-agent reasoning in Pro/Max.", artifacts: [] }; }); const oneLine = `Completed preset '${preset}' on task: ${task.slice(0, 80)}${task.length > 80 ? "…" : ""}`; const reportMarkdown = `# AOI Squad Report (v0.1)\n- Preset: ${mdEscape(preset)}\n- Run: ${mdEscape(runId)}\n- Time: ${mdEscape(started)} → ${mdEscape(ended)}\n\n## Task\n${mdEscape(task)}\n\n## Team outputs\n${team.map(m => `### ${mdEscape(m.nickname)} (${mdEscape(m.role)})\n- Objective: ${mdEscape(m.objective)}\n- Output: ${mdEscape(m.output)}\n`).join("\n")}\n## Synthesis\n- One-line: ${mdEscape(oneLine)}\n- Decision:\n - (placeholder)\n- Risks:\n - (placeholder)\n- Next actions:\n - [P1] Review and refine outputs (Owner: ${mdEscape(team[0].nickname)})\n- VCP Proof:\n - (none)\n`; ``` ### Technical Analysis The `mdEscape()` function escapes only the `<` and `>` characters. It does not neutralize Markdown control syntax such as image declarations, links, headings, blockquotes, or formatting delimiters. The unrestric ...[truncated 2161 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a well-maintained, context-aware Markdown escaping or sanitization library rather than replacing only angle brackets. 2. Treat untrusted task text and nicknames as plain text. Escape all Markdown metacharacters where inline insertion is required. 3. Consider placing the task in a fenced code block while safely handling embedded fence delimiters, or omit the Markdown representation and rely on the structured JSON fields. 4. Configure downstream renderers to: - Disable remote image loading. - Reject unsafe or unapproved URL schemes. - Sanitize generated HTML. - Add `rel="noopener noreferrer"` to external links where applicable. 5. Add regression tests covering image syntax, links, headings, blockquotes, nested formatting, multiline input, and attempts to break out of any chosen containment context. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Credential Access

High
Category
Privilege Escalation
Content
function validateName(name) {
  if (!name || typeof name !== "string") return "Name must be a string";
  if (name.length < 3 || name.length > 40) return "Name length must be 3..40";
  const bad = ["http://","https://","/","\\","://",".env","$", "~"];
  if (bad.some(b => name.includes(b))) return "Name contains disallowed characters";
  return null;
}
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The preset documentation for the builder role says it will draft build steps and command sequences with 'no external side effects', but invoking `cmdRun` loads and saves team names in `~/.openclaw/aoi/squad_names.json`, and `cmdTeamShow` also initializes and writes names. This is an active contradiction between the documented intent and the implemented behavior, not merely missing detail.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The skill explicitly documents that renamed team-member mappings are stored in a local file under the user's home directory, but it does not warn users that these names persist across runs. While this is not an exploit primitive by itself, undisclosed persistence can create privacy and expectation-of-ephemerality issues, especially on shared systems or in regulated environments.

Static analysis

No suspicious patterns detected.