Back to skill

Security audit

markdown-lint

Security checks for vulnerabilities and agentic risk

Overview

This Markdown lint skill is coherent and low risk, with one local symlink-handling caveat for untrusted directories.

Installers should treat this as a normal local Markdown linter. Avoid running its recursive directory mode on untrusted folders or repositories containing unknown symbolic links until symlink containment or cycle handling is added.

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/lint.mjs:91
Finding

Recursive Symbolic-Link Traversal Can Escape the Target Directory or Cause Denial of Service

Content
View full analysis

Vulnerability Details

File Location: scripts/lint.mjs, lines 91–99 and 133–140
Vulnerability Type: Unrestricted symbolic-link traversal during recursive directory scanning
Risk Level: Medium

Vulnerable Code

js
function collectMarkdownFiles(dir) {
  const results = [];
  for (const entry of readdirSync(dir)) {
    const full = join(dir, entry);
    const stat = statSync(full);
    if (stat.isDirectory()) {
      if (entry.startsWith(".") || entry === "node_modules") continue;
      results.push(...collectMarkdownFiles(full));
    } else if (extname(full) === ".md") {
      results.push(full);
    }
  }
  return results;
}

The recursively collected files are subsequently read by lintFile:

js
const stat = statSync(resolved);
if (stat.isDirectory()) {
  files.push(...collectMarkdownFiles(resolved));
} else {
  files.push(resolved);
}

Technical Analysis

statSync() follows symbolic links. The recursive collector does not use lstatSync() or directory entries configured to identify symbolic links, does not verify that each resolved path remains beneath the original scan root, and does not track previously visited directories.

Consequently, a symbolic link located inside a scanned directory is treated as the linked target:

  • A link to a directory outside the requested root causes traversal and reading of Markdown files beyond the intended scope.
  • A link that points to an ancestor or otherwise creates a directory cycle causes repeated recursive calls without a depth or visited-directory limit.
  • The checks for dot-prefixed directories and node_modules only inspect the link's entry name, not its resolved destination, so they do not prevent this behavior.

Attack Path

  1. An attacker gains the ability to place files or symbolic links in a directory that a user or CI process will lint.
  2. The attacker creates a symbolic link with an innocuous name:
    • It may point to a directory outside the intended target contai ...[truncated 1377 chars]
Remediation
View remediation

Remediation Suggestions

  1. Reject symbolic links during recursive traversal by inspecting entries with lstatSync() or readdirSync(dir, { withFileTypes: true }):

    js
    const stat = lstatSync(full);
    if (stat.isSymbolicLink()) {
      continue;
    }
    
  2. Resolve and enforce containment for every traversed path. Compare each canonical path obtained with realpathSync() against the canonical scan root using path.relative(); reject paths that resolve outside the root.

  3. If following symbolic links is an intentional feature, track visited directories using canonical paths or device/inode pairs to prevent cycles.

  4. Add recursion-depth and file-count limits to reduce denial-of-service exposure from unusually deep or large directory trees.

  5. Handle per-entry filesystem errors without terminating the entire scan where appropriate, including broken links and permission failures.

  6. Add regression tests covering:

    • A symlink to a parent directory.
    • A symlink to an external directory.
    • A self-referential or mutually referential link cycle.
    • Broken symbolic links.
    • Valid nested directories that remain within the selected root.
Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep

Static analysis

No suspicious patterns detected.