Back to skill

Security audit

Wip Repos

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent repo-organization purpose, but it also contains under-documented commands and boundary weaknesses that can move or write files beyond what users would reasonably expect.

Install only if you trust the manifests and repositories it will read. Run check and sync --dry-run first, avoid compliance --fix and claude unless you have reviewed their diffs, and do not use this against third-party or mixed-ownership repo sets until path containment and CLAUDE.md metadata handling are fixed.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
core.mjs:137
Finding
Manifest path traversal allows filesystem moves and writes outside the repository root<![CDATA[ ## Vulnerability Details **File Location**: `core.mjs:137-196`, with additional arbitrary writes at `core.mjs:309-394` **Vulnerability Type**: Path traversal and insufficient filesystem boundary enforcement **Risk Level**: High ### Complete Code Snippet ```javascript export function planSync(manifestPath, reposRoot) { const manifest = loadManifest(manifestPath); const diskPaths = walkRepos(reposRoot); const moves = []; // Build a map of remote -> manifest path const remoteToManifest = new Map(); for (const [mPath, info] of Object.entries(manifest.repos)) { if (info.remote) { remoteToManifest.set(info.remote, mPath); } } // For each repo on disk, check if its remote matches a manifest entry at a different path for (const diskPath of diskPaths) { const fullPath = join(reposRoot, diskPath); const gitConfig = join(fullPath, '.git', 'config'); if (!existsSync(gitConfig)) continue; const configText = readFileSync(gitConfig, 'utf8'); const match = configText.match(/url\s*=\s*.*[:/]([^/]+\/[^/\s.]+?)(?:\.git)?\s*$/m); if (!match) continue; const remote = match[1]; const expectedPath = remoteToManifest.get(remote); if (expectedPath && expectedPath !== diskPath) { moves.push({ from: diskPath, to: expectedPath, remote, fromFull: fullPath, toFull: join(reposRoot, expectedPath), }); } } return moves; } export function executeSync(moves, reposRoot) { const results = []; for (const move of moves) { const parentDir = dirname(move.toFull); if (!existsSync(parentDir)) { mkdirSync(parentDir, { recursive: true }); } if (existsSync(move.toFull)) { results.push({ ...move, status: 'skipped', reason: 'target exists' }); continue; } try { renameSync(move.fromFull, move.toFull); results.push({ ...move, status: 'moved' }); } catch (err) { results.push({ ...move, status: 'error' ...[truncated 4448 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every manifest repository key before using it: - Require a nonempty relative path. - Reject absolute paths. - Reject `.` and `..` components. - Reject null bytes and platform-specific drive or UNC paths. - Define an explicit allowlist for path characters if practical. 2. Resolve and enforce containment: ```javascript import { resolve, relative, isAbsolute } from 'node:path'; function resolveWithinRoot(root, candidate) { if (typeof candidate !== 'string' || candidate.length === 0 || isAbsolute(candidate)) { throw new Error('Repository path must be a nonempty relative path'); } const resolvedRoot = resolve(root); const resolvedCandidate = resolve(resolvedRoot, candidate); const rel = relative(resolvedRoot, resolvedCandidate); if (rel === '' || rel === '..' || rel.startsWith(`..${path.sep}`) || isAbsolute(rel)) { throw new Error(`Repository path escapes root: ${candidate}`); } return resolvedCandidate; } ``` 3. Account for symbolic links: - Resolve the real path of the repository root. - Resolve the real path of existing source paths and nearest existing destination parents. - Confirm their real paths remain under the root. - Consider rejecting symlinks in organizational directory components. 4. Change `executeSync()` to accept only logical relative moves: ```javascript executeSync([{ from, to }], reposRoot) ``` It should derive validated full paths internally. Do not trust caller-provided `fromFull` or `toFull`. 5. Apply the same containment function in: - `planSync`; - `executeSync`; - `checkCompliance`; - `fixCompliance`; - any future file-writing operation. 6. Require explicit confirmation before non-dry-run synchronization, particularly when invoked by an agent. 7. Add tests covering: - `../` and nested traversal; - absolute Unix and Windows paths; - UNC paths; - symbolic-link escapes; - external compliance targets; - d ...[truncated 47 chars]

T02 · Agent Memory Poisoning

Error
Location
claude.mjs:104
Finding
Untrusted repository metadata is persisted into agent instruction files<![CDATA[ ## Vulnerability Details **File Location**: `claude.mjs:34-56`, `claude.mjs:104-140`, and `claude.mjs:226-240` **Vulnerability Type**: Persistent agent instruction poisoning through generated `CLAUDE.md` content **Risk Level**: High ### Complete Code Snippet Repository-controlled package metadata is collected without sanitization: ```javascript function extractRepoMeta(repoPath) { const pkg = readJSON(join(repoPath, 'package.json')); const name = pkg?.name || basename(repoPath); const description = pkg?.description || ''; const version = pkg?.version || ''; const exports = pkg?.exports ? Object.keys(pkg.exports) : []; const binCommands = pkg?.bin ? Object.keys(pkg.bin) : []; const scripts = pkg?.scripts || {}; // Detect interfaces const interfaces = []; if (binCommands.length > 0) interfaces.push('CLI'); if (pkg?.main || pkg?.exports) interfaces.push('Module'); if (existsSync(join(repoPath, 'mcp-server.mjs')) || existsSync(join(repoPath, 'dist', 'mcp-server.js'))) interfaces.push('MCP'); if (existsSync(join(repoPath, 'openclaw.plugin.json'))) interfaces.push('OpenClaw Plugin'); if (existsSync(join(repoPath, 'SKILL.md'))) interfaces.push('Skill'); return { name, description, version, interfaces, binCommands, dirs, scripts, path: repoPath, }; } ``` The metadata is directly rendered as Markdown: ```javascript function generateEcosystem(targetMeta, relevantMetas) { const lines = []; for (const m of relevantMetas) { const relPath = basename(m.path); lines.push(`### ${m.name}`); lines.push(`**Path:** \`${relPath}\``); if (m.description) lines.push(m.description); if (m.interfaces.length > 0) lines.push(`**Interfaces:** ${m.interfaces.join(', ')}`); if (m.binCommands.length > 0) lines.push(`**CLI:** ${m.binCommands.join(', ')}`); if (m.version) lines.push(`**Version:** ${m.version}`); lines.push(''); } return lines.join('\n').trim(); } function upda ...[truncated 4660 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not copy untrusted repository metadata into `CLAUDE.md` or any other file with agent-instruction semantics. 2. Prefer writing generated inventory to a non-instruction document such as: - `REPOS-ECOSYSTEM.md`; - a JSON inventory; - ordinary README content explicitly treated as untrusted reference data. 3. If integration with `CLAUDE.md` is unavoidable: - Include only locally configured, administrator-approved metadata. - Use a strict schema and conservative character and length limits. - Reject multiline values, HTML comments, Markdown headings, links, shell syntax, and generator marker strings. - Do not import free-form descriptions from other repositories. - Label generated values as untrusted data rather than instructions. 4. Validate marker integrity before replacement: - Require exactly one start marker and one end marker. - Require the start marker to precede the end marker. - Reject generated content containing either marker. - Fail closed rather than appending when malformed or duplicate markers exist. 5. Show a diff and require explicit user confirmation before modifying any `CLAUDE.md`. 6. Make dry-run the default for cross-repository instruction-file changes. 7. Document the `claude` command in `SKILL.md` and clearly disclose that it reads metadata from all manifest repositories and writes persistent agent-facing files. 8. Add security tests using descriptions containing: - instruction-like prose; - Markdown headings; - start and end markers; - multiline content; - links and command examples; - extremely long values. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (12)

Hidden Instructions

High
Category
Prompt Injection
Content
const __dirname = dirname(fileURLToPath(import.meta.url));
const TEMPLATE_PATH = join(__dirname, '..', '..', 'templates', 'repo-claude-md.template');
const START_MARKER = '<!-- wip-repos:start -->';
const END_MARKER = '<!-- wip-repos:end -->';

function readJSON(path) {
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This markdown file documents that the filesystem 'adapts' to the manifest and that folders 'snap back' or are moved on sync, which affects user data layout and system state. Although `--dry-run` is shown, there is no explicit warning that `wip-repos sync` will make real filesystem changes and should be used carefully.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill documents a `sync` command that will actively move repositories on disk to match a manifest, but it does not clearly warn that running it can modify the filesystem beyond a preview step. In an agent-driven context, terse operational guidance can cause a user or autonomous system to invoke the mutating command directly, leading to unintended repo moves, broken paths, or workflow disruption.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The CLI imports and exposes a `claude` command that is not documented in the header comments or usage output, creating a hidden capability outside the stated skill scope. Undocumented commands are dangerous because they bypass operator expectations and review boundaries, and they often become a path for unvetted agent behavior or unexpected downstream actions.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest describes this skill as a 'Repo manifest reconciler' that makes repos-manifest.json the single source of truth for repo organization. However, the CLI also provides `compliance` and `--fix` functionality to scan repositories for licensing/CLA/npmignore files and create missing files, which is a separate repo compliance management capability rather than manifest reconciliation.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The code hardcodes legal and licensing defaults for a specific organization and can create or update compliance artifacts such as CLA.md, .license-guard.json, and .npmignore. If run against third-party or mixed-ownership repositories, it may inject incorrect legal terms or packaging rules, causing policy corruption, legal misrepresentation, and accidental changes that are hard to detect at scale.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill’s stated purpose is manifest reconciliation, but this section adds compliance auditing and auto-remediation that writes organization-specific files into repositories. In an agent setting, this expands the write surface beyond expected scope and can silently modify unrelated repos, creating integrity and governance risk if the skill is invoked with broad filesystem access.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The `repos_add` and `repos_move` tool definitions describe modifying the manifest, and their handlers execute those changes, but this file provides no confirmation prompt or explicit warning that user data will be altered. Although the success messages appear after execution, they do not disclose the write beforehand, and there is no docstring-level warning here about the manifest being modified.

Intent-Code Divergence

Low
Confidence
97% confidence
Finding
The module docstring lists only `check`, `sync`, `add`, `move`, and `tree`, but the code also implements `compliance`, `watchdog`, and `claude`. This documentation does not merely lack detail; it presents an incomplete and narrower set of capabilities than the executable CLI actually exposes, which can mislead reviewers about the skill's intent and operational scope.

Intent-Code Divergence

Low
Confidence
99% confidence
Finding
The inline comment states that underscore-prefixed folders sort to the top. But when a starts with '_' and b does not, the comparator returns 1, which places a after b in ascending sort order, contradicting the documented intent.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"directory": "tools/wip-repos"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  }
}
Confidence
95% confidence
Finding
The dependency is specified with a caret range (^1.0.0), which permits automatic installation of newer minor and patch releases. This weakens supply-chain reproducibility and can unexpectedly introduce vulnerable or malicious upstream code without any manifest change in this package.

Unverifiable Dependency: @modelcontextprotocol/sdk has 3 known advisory(ies) (CVE-2026-25536 (@modelcontextprotocol/sdk has cross-client data leak via shared server/transport); CVE-2026-0621 (Anthropic's MCP TypeScript SDK has a ReDoS vulnerability); CVE-2025-66414 (Model Context Protocol (MCP) TypeScript SDK does not enable DNS rebinding protec)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
The package depends on @modelcontextprotocol/sdk without pinning to a known-safe version, while the dependency family has multiple published advisories. Because the manifest allows version drift, consumers may install an affected release, exposing them to issues such as cross-client data leakage, ReDoS, or DNS rebinding weaknesses depending on runtime usage.

Static analysis

No suspicious patterns detected.