Skip to main content

Path Security API

The Path Security module validates and sanitizes paths for safe filesystem operations. It prevents path traversal attacks, null byte injection, symlink escapes, and other filesystem exploits when generating skill names and writing skill files.

Security Measures

  • Path traversal prevention - Blocks ../ sequences and absolute path injection
  • Null byte protection - Strips null bytes (\0) from paths and filenames
  • Symlink resolution - Resolves symlinks before validation to prevent escapes
  • Spec compliance - Enforces agentskills.io naming spec
  • Bounded writes - Ensures all writes stay within allowed directories

Functions

sanitizeName

Sanitize a skill name for safe filesystem and spec-compliant usage. Applies the following transformations:
  1. Unicode normalization (NFKD) and ASCII folding
  2. Lowercase conversion
  3. Replace non-alphanumeric chars (except hyphens) with hyphens
  4. Collapse consecutive hyphens
  5. Strip leading/trailing hyphens
  6. Truncate to MAX_NAME_LENGTH (64 chars)
  7. Ensure result matches agentskills.io spec regex
function sanitizeName(rawName: string): string
rawName
string
required
The unsanitized name string
Returns: string - A sanitized, spec-compliant kebab-case name Throws: Error if the name cannot be sanitized to a valid result

Example

import { sanitizeName } from "auto-skill/core/path-security";

// Basic sanitization
const name1 = sanitizeName("Debug Test Failure");
console.log(name1); // "debug-test-failure"

// Remove unsafe characters
const name2 = sanitizeName("../../../etc/passwd");
console.log(name2); // "etc-passwd"

// Unicode normalization
const name3 = sanitizeName("Café-Résumé");
console.log(name3); // "cafe-resume"

// Truncation at word boundary
const name4 = sanitizeName("a".repeat(100));
console.log(name4.length); // <= 64

// Error: empty result
try {
  sanitizeName("!@#$%^&*()");
} catch (err) {
  console.log(err.message); // "Skill name '!@#$%^&*()' cannot be sanitized..."
}

isPathSafe

Check whether a target path is safely contained within an allowed root. Guards against:
  • Path traversal (../)
  • Null bytes in path components
  • Symlink escapes (resolves symlinks before comparison)
  • Absolute path injection
function isPathSafe(target: string, allowedRoot: string): boolean
target
string
required
The path to validate
allowedRoot
string
required
The directory that must contain target
Returns: boolean - True if target is safely within allowedRoot, false otherwise

Example

import { isPathSafe } from "auto-skill/core/path-security";
import path from "node:path";

const skillsDir = "/home/user/.claude/skills";

// Safe: within allowed root
const safe1 = isPathSafe(
  path.join(skillsDir, "auto/my-skill.md"),
  skillsDir
);
console.log(safe1); // true

// Unsafe: path traversal
const safe2 = isPathSafe(
  path.join(skillsDir, "../../../etc/passwd"),
  skillsDir
);
console.log(safe2); // false

// Unsafe: null byte
const safe3 = isPathSafe(
  skillsDir + "/skill\0.md",
  skillsDir
);
console.log(safe3); // false
Check whether a symlink is safe to create. Ensures both the link location and its target are within allowed boundaries.
function isSafeSymlink(
  linkPath: string,
  targetPath: string,
  allowedRoot: string
): boolean
Where the symlink will be created
targetPath
string
required
What the symlink will point to
allowedRoot
string
required
The root directory that must contain both paths
Returns: boolean - True if the symlink is safe to create, false otherwise

Example

import { isSafeSymlink } from "auto-skill/core/path-security";
import path from "node:path";

const skillsDir = "/home/user/.claude/skills";
const autoDir = path.join(skillsDir, "auto");
const cursorDir = path.join(skillsDir, "cursor");

// Safe: both within allowed root
const safe = isSafeSymlink(
  path.join(cursorDir, "my-skill.md"),
  path.join(autoDir, "my-skill.md"),
  skillsDir
);
console.log(safe); // true

// Unsafe: target escapes allowed root
const unsafe = isSafeSymlink(
  path.join(cursorDir, "my-skill.md"),
  "/etc/passwd",
  skillsDir
);
console.log(unsafe); // false

safeWrite

Write content to a file only if the path is safe. Creates parent directories as needed. Uses atomic-write for safety (writes to temp file, then renames).
function safeWrite(
  content: string,
  target: string,
  allowedRoot: string
): string
content
string
required
The file content to write
target
string
required
The file path to write to
allowedRoot
string
required
The directory that must contain target
Returns: string - The resolved path of the written file Throws: Error if the target path is not safe

Example

import { safeWrite } from "auto-skill/core/path-security";
import path from "node:path";

const skillsDir = "/home/user/.claude/skills/auto";
const skillContent = `---
name: my-skill
---
# My Skill`;

try {
  const written = safeWrite(
    skillContent,
    path.join(skillsDir, "my-skill.md"),
    skillsDir
  );
  console.log(`Wrote skill to ${written}`);
} catch (err) {
  console.error(`Failed to write skill: ${err.message}`);
}

Constants

SPEC_NAME_REGEX

Regular expression for validating skill names per agentskills.io spec.
const SPEC_NAME_REGEX: RegExp = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/
Valid names:
  • Must start and end with alphanumeric character
  • May contain hyphens in the middle
  • Lowercase only
  • Examples: debug-test, git-workflow, api-client
Invalid names:
  • -leading-hyphen
  • trailing-hyphen-
  • UPPERCASE
  • has_underscore
  • has.dot

MAX_NAME_LENGTH

Maximum allowed name length per agentskills.io spec.
const MAX_NAME_LENGTH: number = 64

Sanitization Process

The sanitizeName function applies these transformations in order:
// Input: "Debug Test Failure!"

// 1. Strip null bytes
"Debug Test Failure!"

// 2. Unicode normalize (NFKD) + ASCII fold
"Debug Test Failure!"

// 3. Lowercase
"debug test failure!"

// 4. Replace path separators with hyphens
"debug test failure!"

// 5. Replace unsafe chars with hyphens
"debug-test-failure-"

// 6. Collapse consecutive hyphens
"debug-test-failure-"

// 7. Strip leading/trailing hyphens
"debug-test-failure"

// 8. Truncate to MAX_NAME_LENGTH (at word boundary)
"debug-test-failure"

// 9. Validate against SPEC_NAME_REGEX
// ✓ Matches ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$

// Output: "debug-test-failure"

Security Best Practices

When working with file paths in Auto-Skill:
  1. Always sanitize user input - Use sanitizeName() for any user-provided or derived names
  2. Validate before writing - Use isPathSafe() before any filesystem operations
  3. Prefer safeWrite - Use safeWrite() instead of raw fs.writeFileSync()
  4. Check symlinks - Use isSafeSymlink() before creating cross-agent symlinks
  5. Trust no input - Even internally-generated names should be validated

Build docs developers (and LLMs) love