Back to skill

Security audit

Wip Readme Format

Security checks for vulnerabilities and agentic risk

Overview

This README formatter is mostly purpose-aligned, but its deploy path contains a real command-injection risk when run on an untrusted repository.

Install only if you trust the source and use it on repositories you control. Run --dry-run first, review generated README-init-* files, and avoid --deploy on untrusted checkouts until the shell command construction is fixed to use an argument-array API such as execFileSync.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (2)

T01 · Skill Instruction Hijacking

Warning
Location
format.mjs:93
Finding
Generated README Injects Persistent Instructions into Downstream AI Sessions<![CDATA[ ## Vulnerability Details **File Location**: `format.mjs:93-124` **Vulnerability Type**: Persistent downstream instruction injection **Risk Level**: Medium ### Vulnerable Code ```js function generateTeachBlock(toolName, repoUrl, skillPath) { const skillUrl = repoUrl ? `${repoUrl}/blob/main/${skillPath || 'SKILL.md'}` : 'SKILL.md'; const installCmd = repoUrl ? `wip-install ${repoUrl.replace('https://github.com/', '')} --dry-run` : `wip-install /path/to/repo --dry-run`; return `## Teach Your AI to Use ${toolName} Open your AI and say: \`\`\` Read the SKILL.md at ${skillUrl.replace('https://', '')}. Then explain to me: 1. What are these tools? 2. What do they do? 3. What would they change about how we work together? Then ask me: - Do you have more questions? - Do you want to install them? If I say yes, run: ${installCmd} Show me exactly what will change on my system. When I'm ready, I'll tell you to install for real. \`\`\` Your agent will read the repo, explain everything, and do a dry-run install first so you can see exactly what changes before anything is written to your system.`; } ``` The generated document also receives fixed third-party branding and attribution: ```js const badgeLines = ['###### WIP Computer', '']; ``` ```js sections.license = generateLicenseBlock(repoPath) + '\n\nBuilt by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code (Claude Opus 4.6).'; ``` ### Technical Analysis The formatter does not merely reorganize existing documentation. It creates a persistent instruction block that tells a downstream AI agent to: 1. Retrieve and interpret a `SKILL.md` document. 2. Adopt a new interaction flow. 3. Ask the user whether installation should proceed. 4. Execute an installation command after receiving confirmation. The repository URL is derived from the target repository's `package.json`. Consequently, the target repository controls the location from which a downstream agent is instruc ...[truncated 1955 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove mandatory AI execution instructions from the default README template. 2. Make the “Teach Your AI” block an explicit opt-in feature, disabled by default. 3. Do not instruct an agent to execute an installer from generated documentation. 4. If external skill documentation must be referenced, pin it to an immutable commit hash rather than the mutable `main` branch. 5. Validate repository URLs against an explicit allowlist and reject noncanonical or unexpected URL formats. 6. Render third-party content as informational documentation, not as imperative instructions addressed to an AI agent. 7. Require explicit user-provided values before adding organization branding or author attribution. 8. Clearly display all generated external URLs and commands during review, and require separate confirmation for each. 9. Add tests ensuring untrusted package metadata cannot produce executable AI instructions or mutable remote references. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
format.mjs:350
Finding
Repository-Controlled Filename Enables Shell Command Injection During Deployment<![CDATA[ ## Vulnerability Details **File Location**: `format.mjs:350-355` **Vulnerability Type**: OS command injection through shell-interpolated filename **Risk Level**: High ### Vulnerable Code ```js try { const { execSync } = await import('node:child_process'); const initFiles = readdirSync(repoPath).filter(f => f.startsWith('README-init-')); const allUntracked = initFiles.every(f => { try { const status = execSync(`git status --porcelain "${f}"`, { cwd: repoPath, encoding: 'utf8' }).trim(); return status.startsWith('??'); } catch { return false; } }); if (allUntracked && initFiles.length > 0) { fail('Init files have not been reviewed. They are all untracked (just generated).'); console.log(' Review the README-init-*.md files, edit as needed, then git add them before deploying.'); console.log(' Or commit them first so there is a review trail.'); process.exit(1); } } catch {} ``` ### Technical Analysis `readdirSync(repoPath)` obtains filenames directly from the target repository. Every filename beginning with `README-init-` is interpolated into a command string passed to `execSync`. Although the filename is placed inside double quotes, double-quoted shell strings still process constructs such as command substitution using `$(...)` or backticks. Repository filenames can contain these shell metacharacters on supported filesystems. The application therefore treats untrusted filename data as shell syntax. For example, a filename shaped like the following satisfies the prefix filter: ```text README-init-$(touch injected-marker).md ``` When interpolated, the resulting shell command is equivalent to: ```bash git status --porcelain "README-init-$(touch injected-marker).md" ``` The shell executes `touch injected-marker` before invoking Git. More damaging commands could be substituted. The surrounding `try`/`catch` blocks do not mitigate the vulnerability because injected commands execute before an exception i ...[truncated 1747 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not construct a shell command by interpolating a repository-controlled filename. Invoke Git directly with a fixed executable and an argument array: ```js import { execFileSync } from 'node:child_process'; const status = execFileSync( 'git', ['status', '--porcelain', '--', f], { cwd: repoPath, encoding: 'utf8', shell: false, } ).trim(); ``` Additional hardening should include: 1. Use `--` before the filename so filenames beginning with a hyphen cannot be interpreted as Git options. 2. Explicitly set `shell: false`. 3. Reject filenames containing control characters if they are not required by the workflow. 4. Avoid silently swallowing command failures; report validation failures without exposing sensitive command output. 5. Apply resource limits such as a timeout and output-size limit to child processes. 6. Run repository-processing operations with the least-privileged account available. 7. Add regression tests using filenames containing: - `$(command)` - Backticks - Double quotes - Semicolons - Newlines - Leading hyphens 8. Audit all other child-process calls for string interpolation and replace them with argument-array APIs. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Vague Triggers

Medium
Confidence
92% confidence
Finding
The manifest description says the skill can "Reformat any repo's README," which is a broad natural-language activation description without constraints on when it should or should not be invoked. Because this is a markdown/manifest file, the lack of narrower trigger conditions or exclusion examples increases the chance of unintended invocation for general README-related requests.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill explicitly states that it rewrites README.md and moves technical content to TECHNICAL.md, but it lacks a prominent warning about these destructive file modifications. In agent-driven workflows, unclear write-side effects can cause unintended documentation changes, content relocation, and CI/review disruption, especially if invoked automatically or on the wrong repository.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The manifest description says the skill can reformat "any repo's README," but it does not define activation constraints, trigger phrases, or exclusion conditions. In a manifest file, this broad natural-language scope can make invocation boundaries unclear and increases the chance of unintended activation for general README-related requests.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
format.mjs:354