Back to skill

Security audit

skills-pager

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a disclosed navigation-index helper, but its companion script can write outside the intended index directory if given a crafted skill ID.

Install only if you are comfortable with a skill creating persistent .skill-index files in each workspace. Until the script validates --skill-id, use only simple skill names made of letters, numbers, dots, underscores, or hyphens, and do not pass user-supplied or path-like values such as ../name.

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

Warning
Location
scripts/create-skills-pager-map.js:73
Finding

Path Traversal Enables Filesystem Writes Outside the Intended Skill Index Directory

Content
View full analysis

Vulnerability Details

File Location: scripts/create-skills-pager-map.js, lines 17–18, 32–34, 63, and 73–75
Vulnerability Type: Unvalidated path component / directory traversal
Risk Level: Medium

Vulnerable Code

js
if (value === "--skill-id") {
  args.skillId = argv[++i];
}
js
if (!args.skillId) {
  fail("Missing required argument: --skill-id");
}
js
function writeText(filePath, content) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, content, "utf8");
}
js
const indexRoot = path.join(workspaceRoot, ".skill-index");
const skillRoot = path.join(indexRoot, "skills", args.skillId);
const registryPath = path.join(indexRoot, "registry.json");
const indexPath = path.join(skillRoot, "index.md");

Technical Analysis

The --skill-id argument is used directly as a filesystem path component without validation. path.join() normalizes path traversal sequences rather than rejecting them. Consequently, a value containing ../ segments can cause skillRoot and indexPath to resolve outside the intended .skill-index/skills/ directory.

The resulting path is passed to writeText(), which recursively creates missing directories and overwrites the destination with fs.writeFileSync(). No canonical-path containment check, safe identifier allowlist, symbolic-link check, or overwrite confirmation protects the operation.

Although the destination filename is fixed as index.md, an attacker can select an arbitrary writable directory relative to the current workspace and cause that directory's index.md to be created or replaced.

Attack Path

  1. An attacker influences the script invocation or supplies an untrusted skill identifier.

  2. The attacker provides a traversal value, for example:

    bash
    node scripts/create-skills-pager-map.js \
      --skill-id ../../../target-directory \
      --source target/SKILL.md \
      --page setup
    
  3. `path.join(indexRoot, " ...[truncated 1221 chars]

Remediation
View remediation

Remediation Suggestions

  1. Apply a strict identifier allowlist

    Accept only a single safe path component:

    js
    function validateSkillId(skillId) {
      if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(skillId)) {
        fail("Invalid --skill-id: use letters, numbers, dots, underscores, or hyphens only");
      }
    
      if (skillId === "." || skillId === "..") {
        fail("Invalid --skill-id");
      }
    
      return skillId;
    }
    
  2. Enforce canonical path containment

    Resolve both the permitted root and destination, then verify that the destination remains beneath the permitted root:

    js
    const skillsRoot = path.resolve(indexRoot, "skills");
    const skillId = validateSkillId(args.skillId);
    const skillRoot = path.resolve(skillsRoot, skillId);
    
    if (
      skillRoot === skillsRoot ||
      !skillRoot.startsWith(`${skillsRoot}${path.sep}`)
    ) {
      fail("Resolved skill path escapes .skill-index/skills");
    }
    
    const indexPath = path.join(skillRoot, "index.md");
    
  3. Reject path syntax explicitly

    Reject absolute paths, /, \, null bytes, and traversal components even if an allowlist is also used:

    js
    if (
      path.isAbsolute(args.skillId) ||
      args.skillId.includes("/") ||
      args.skillId.includes("\\") ||
      args.skillId.includes("\0")
    ) {
      fail("Invalid --skill-id");
    }
    
  4. Protect against unintended overwrites

    Use exclusive creation when appropriate or require an explicit overwrite option:

    js
    fs.writeFileSync(indexPath, content, {
      encoding: "utf8",
      flag: args.force ? "w" : "wx",
    });
    
  5. Use atomic writes

    Write to a temporary file in the validated destination directory and rename it atomically after successful completion. Ensure the temporary filename is generated by the program rather than derived from user input.

  6. Add regression tests

    Tests should verify rejection of:

    • ../target
    • ../../../../tmp/target

...[truncated 181 chars]

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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 (7)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 46)May include surrounding context.

md
- important detail is split across `SKILL.md`, `references/`, or pre-split section files

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 94)May include surrounding context.

md
This skill includes a companion scaffold script at `scripts/create-skills-pager-map.js`.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 127)May include surrounding context.

md
This skill includes a companion scaffold script at `scripts/create-skills-pager-map.js`.

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · README.md (reported line 150)May include surrounding context.

md
## Design Principles


| Principle               | What it means                                                                                                          |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Answer first**        | Never let index creation block the user's question. Build the index after answering, while understanding is fresh.     |
| **Source is authority** | The index is navigation, not a replacement for source verification on consequential details.                           |

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The trigger condition 'When a skill you need is roughly 100 lines or more' is ambiguous and relies on a rough size heuristic rather than a clearly bounded activation scope. Nearby guidance such as 'a narrow request can still be the right trigger' further broadens activation, increasing the chance of unintended or inconsistent invocation for ordinary reading tasks.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The 'Use this skill when' bullets include generic situations like 'you notice yourself rereading the same source regions' and requests that 'name specific parts across multiple large skills.' These are common workflow situations but the document does not provide a specific invocation phrase set or clear non-trigger boundaries, making activation scope overly broad for a markdown skill description.

Content

No source excerpt is available for this finding.

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · references/map-layout.md (reported line 114)May include surrounding context.

md
- `incident-recovery`

## Important sources
- `skills/deployment-pipeline/SKILL.md`
- `skills/deployment-pipeline/references/rollback.md`

## Route notes

Static analysis

No suspicious patterns detected.