Back to skill

Security audit

Arbiter

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent human-review workflow, but its local queue handling can write or expose decision files more broadly than users are told.

Review before installing. Do not send secrets, credentials, personal data, regulated data, or sensitive internal plans through this skill. Use it only on a trusted single-user machine or after fixing path validation, YAML/Markdown serialization, exact plan matching, ownership checks, and restrictive file permissions.

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)

T09 · Insecure Skill Coding Practices

Error
Location
src/push.ts:110
Finding

Path Traversal Through Attacker-Controlled Agent Identifier

Content
View full analysis
.md ``` 3. `join()` normalizes the path and resolves it outside the pending queue. 4. `writeFileSync()` creates the attacker-con ...[truncated 921 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
src/push.ts:39
Finding

YAML Frontmatter and Markdown Structure Injection

Content
View full analysis
{ const optionsMarkdown = d.options .map(o => `- \`${o.key}\` — ${o.label}${o.note ? ` (${o.note})` : ''}`) .join('\n'); return ` --- ## Decision ${i + 1}: ${d.title} id: ${d.id} status: pending answer: null answered_at: null ${d.allowCustom ? 'allow_custom: true' : ''} **Context:** ${d.context} **Options:** ${optionsMarkdown} `; }).join('\n'); ``` ### Technical Analysis Untrusted fields are interpolated directly into YAML frontmatter and into the Markdown control structure without escaping or schema validation. Affected values include: - `agent` - `session` - `tag` - `title` - `priority` - `notify` - Decision IDs and titles - Decision context - Option keys, labels, and notes Newline characters and YAML delimiters can introduce new metadata fields, terminate frontmatter early, or alter how downstream components parse the document. Markdown separators and lines such as `id:`, `status:`, and `answer:` can also create forged decision blocks because `parseDecisions()` later relies on regular expressions over text sections rather than a strict structured format. TypeScript interfaces provide no runtime protection because input is accepted through `JSON.parse()` and cast directly to `PushArgs`. ### Attack Path 1. An atta ...[truncated 1426 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
src/utils.ts:52
Finding

Partial Plan-ID Matching Can Return the Wrong Plan and Disclose Answers

Content
View full analysis
f.endsWith('.md')); for (const file of files) { const filepath = join(dir, file); // Quick check by filename for planId if (planId && file.includes(planId)) { return filepath; } // Need to read file for tag match if (tag) { try { const content = readFileSync(filepath, 'utf-8'); const { data } = matter(content); if (data.tag === tag || data.id === planId) { return filepath; } } catch { continue; } } } } return null; } ``` ### Technical Analysis Plan identifiers are matched with `file.includes(planId)` rather than by exact metadata equality. An attacker does not need to know the full eight-character plan ID; any substring appearing in a filename can select a plan. The first matching file returned by `readdirSync()` wins. This produces ambiguous, order-dependent lookup behavior. The issue affects both `arbiter-status` and `arbiter-get`, and the latter returns completed decision answers. No ownership check compares the selected plan's `agent` or `session` metadata with the requesting agent or session. In a shared queue used by multiple agents under the same operating-system account, one agent can query another agent's plan. ### Attack Path 1. Multiple agents create plans in the shared `~/.arbiter/queue` hierarchy. 2. A caller invokes `arbiter-get` or `arbiter-status` using a short identifier, filename fragment, agent name, or title fragment. ...[truncated 843 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
src/utils.ts:28
Finding

Queue Directories and Decision Files Are Created Without Explicit Restrictive Permissions

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
package.json:17
Finding

Dependency Resolution Is Not Reproducibly Locked

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The description says this skill submits decisions to Arbiter Zebu for async human review. However, the provided code chunk is for a separate 'get' operation and does not submit anything. In fact, it does not perform retrieval either; it is only a placeholder that prints a not-implemented message and exits. This is a material mismatch in primary purpose and capability.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The description claims the skill pushes decisions to Arbiter Zebu for async human review. However, the actual code does not perform any submission, file creation, queue interaction, or review-routing behavior. It only prints a 'not yet implemented' message and exits with an error. This is a material mismatch in primary behavior, even though the comments describe an intended future implementation.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The description says the skill submits or pushes decisions to Arbiter Zebu for async human review. The actual code chunk does not do that. It is a stub for a different function: checking the status of a plan/decision file, and even that functionality is not implemented. Its primary purpose and behavior therefore differ materially from the declared description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description says this skill is for pushing decisions to Arbiter Zebu for async human review. However, the provided code implements a get command that looks up an existing plan file, checks whether its status is completed, parses decisions, and returns collected answers as JSON. Its primary purpose is result retrieval, not submission for human review. This is a material purpose mismatch, even though both relate to the same broader Arbiter workflow.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

This is a material description-behavior mismatch. The declared purpose suggests an action-oriented submission skill that sends decisions to Arbiter Zebu for async human review. The supplied code does something different: it is a read-only status inspection utility for previously created plans. It locates a plan file, parses stored metadata and decisions, and prints a JSON status report. There is no evidence of pushing requests, initiating human review, or requesting approval. The code’s primary purpose, resources accessed (local plan files), and invocation triggers differ from the declaration.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README shows users passing potentially sensitive decision context into arbiter-push and explains that a human reviews answers in Telegram, but it does not clearly warn that submitted content leaves the agent boundary and is exposed to a human-operated external review channel. In an agent skill context, this omission can cause operators or upstream agents to send secrets, internal architecture details, credentials, or regulated data for review without informed consent, resulting in confidentiality and privacy leakage.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill explicitly relies on environment variables (CLAWDBOT_AGENT, CLAWDBOT_SESSION) and local CLI execution, but it does not declare tool scope or permissions. That can cause the agent runtime to grant broader access than users expect and obscures that the skill reads execution context from the environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill sends plan context, decision details, and notifications into local queue files for later human review, but the description does not clearly warn users that potentially sensitive content will be persisted on disk and exposed to a human operator. In agent workflows, this can lead to inadvertent disclosure of secrets, private data, or internal plans.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/push.sh (reported line 3)May include surrounding context.

sh
#!/usr/bin/env bash
#
# arbiter push - Create a decision file in the queue
#
# Usage: push.sh <tag> <title> [options]
#

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The heartbeat guidance tells the agent to delete notification files after processing without warning that deletion is destructive and may remove evidence needed for audit, troubleshooting, or recovery. In multi-agent or asynchronous systems, premature deletion can also cause missed notifications or state inconsistencies.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
92% confidence
Finding

The dependency gray-matter is specified with a caret range, which permits automatic installation of newer minor/patch releases than the one reviewed by the author. This weakens build reproducibility and increases supply-chain risk because a later compromised or breaking release could be pulled in without an explicit manifest change.

Content

Scanner excerpt · package.json (reported line 18)May include surrounding context.

json
"get": "node dist/get.js"
  },
  "dependencies": {
    "gray-matter": "^4.0.3",
    "nanoid": "^5.0.4"
  },
  "devDependencies": {

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The nanoid dependency is unpinned via a caret range, so consumers may resolve different releases over time. In a security-relevant package, this creates avoidable supply-chain and reproducibility risk, especially because advisory exposure cannot be confidently ruled out when the exact installed version is unspecified.

Content

Scanner excerpt · package.json (reported line 19)May include surrounding context.

json
},
  "dependencies": {
    "gray-matter": "^4.0.3",
    "nanoid": "^5.0.4"
  },
  "devDependencies": {
    "typescript": "^5.3.3",

Unverifiable Dependency: nanoid has 5 known advisory(ies) (CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2024-55565 (Predictable results in nanoid generation when given non-integer values) +2 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding

The manifest references nanoid without an exact pinned version, while the package has known advisories in some releases. Because the actual installed version cannot be verified from this manifest alone, the project may resolve to an affected release, creating uncertainty around ID-generation safety and potential denial-of-service or predictability issues depending on usage.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
83% confidence
Finding

TypeScript is a development dependency, but leaving it unpinned still permits non-deterministic toolchain changes during builds. That can introduce build instability or, in a supply-chain compromise scenario, malicious code execution during development or CI installation.

Content

Scanner excerpt · package.json (reported line 22)May include surrounding context.

json
"nanoid": "^5.0.4"
  },
  "devDependencies": {
    "typescript": "^5.3.3",
    "@types/node": "^20.11.0"
  },
  "engines": {

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
80% confidence
Finding

The @types/node package is unpinned, allowing build-time type definitions to drift across installations. While lower risk than runtime packages, it still reduces reproducibility and can contribute to supply-chain exposure in developer and CI environments.

Content

Scanner excerpt · package.json (reported line 23)May include surrounding context.

json
},
  "devDependencies": {
    "typescript": "^5.3.3",
    "@types/node": "^20.11.0"
  },
  "engines": {
    "node": ">=20.0.0"

Static analysis

No suspicious patterns detected.