Safety & Security Guide

Quick Reference

Features Covered

FeatureModuleVersion
Generation Safetysrc/validation/generation-safety.tsv1.9
Path Safetysrc/validation/path-safety.tsv1.9
YAML Safetysrc/validation/yaml-safety.tsv1.9
JSONL Safetysrc/validation/jsonl-safety.tsv1.10
Audit Loggersrc/safety/audit-logger.tsv1.10
File Locksrc/safety/file-lock.tsv1.10
Operation Cooldownsrc/safety/operation-cooldown.tsv1.10

Prerequisites

  • gsd-skill-creator installed
  • Understanding of skill creation and storage (Core Skill Management Guide)
  • No additional dependencies required — all safety modules use Node.js built-in APIs

Configuration Reference

Safety File Paths

  • Audit log: .claude/.audit-log.jsonl
  • Lock file: .claude/.skill-creator.lock
  • Cooldown state: .claude/.cooldown-state.json

Default Cooldown Periods

  • discover: 5 minutes (300,000 ms)
  • corpus-scan: 5 minutes (300,000 ms)
  • Unconfigured operations: no cooldown

Audit Operations

  • create — New skill created
  • update — Skill metadata or body modified
  • delete — Skill removed
  • migrate — Format migration performed
  • refine — Learning refinement applied
  • rollback — Version rollback executed

Dangerous Command Categories (11 patterns)

  • Recursive delete (rm -rf /, rm -rf ~, rm -rf *)
  • Piped downloads (curl url | sh, wget -O- url | sh)
  • Elevated privileges (sudo)
  • Credential manipulation (chmod 777, .ssh/, /etc/passwd)
  • Disk destruction (dd if=, mkfs)
  • Fork bombs
  • Environment destruction (unset PATH, export PATH=)

Path Safety Checks

  • Empty names
  • Null bytes (\0)
  • Traversal sequences (.., ../, ..\)
  • Path separators (/, \)
  • Filesystem special entries (.)
  • Resolved path containment (must stay within base directory)

Related Guides

Overview

gsd-skill-creator is a self-modifying system: it generates executable instructions that Claude follows, it stores data in append-only files that influence future behavior, and it manages filesystem operations across concurrent agents. Every one of those capabilities is a potential attack surface. The seven safety modules described in this guide form a defense-in-depth strategy that prevents misuse without impeding legitimate workflows.

The approach is layered. Generation Safety scans skill content for dangerous bash commands before they reach disk. Path Safety prevents traversal attacks on skill names and file operations. YAML Safety blocks code execution through deserialization. JSONL Safety adds tamper-evident checksums to observation data. Audit Logger records every mutation for forensic review. File Lock prevents concurrent agents from corrupting shared state. Operation Cooldown rate-limits expensive or risky operations to prevent abuse.

None of these modules require configuration to be effective — they are active by default in every code path. The system is designed so that the safe path is the only path: there is no "unsafe mode" toggle, no way to skip validation on the hot path, and no shortcut that bypasses the security layers.

Key Concepts

For Students & Teachers

Think of gsd-skill-creator as a building with seven security systems, each protecting against a different threat:

The Generation Safety system is the building inspector. Before any new room (skill) is added to the building, the inspector reviews the blueprints. If a blueprint contains something dangerous — like wiring that could cause a fire (a dangerous command) — the inspector blocks it and puts up a warning sign explaining what was wrong and why.

The Path Safety system is the address verifier. Every package (skill) has a delivery address (file path), and the verifier makes sure the address points to a real room inside the building — not a trick address that would send the package outside the building to somewhere it should not go.

The YAML Safety system is the mail scanner. Configuration data (YAML frontmatter) comes in like mail, and the scanner makes sure it only contains data — not hidden programs (like !!js/function) that would execute when the mail is opened.

The JSONL Safety system is the tamper-evident seal on records. Each observation record gets a mathematical seal (SHA-256 checksum) when written. If someone alters the record later, the seal breaks and the tampering is detected.

The Audit Logger is the security camera. It records every action — who did it, what they did, when, and to which file. If something goes wrong, the logs provide a complete timeline for investigation.

The File Lock is the single-occupancy sign. When one worker (agent) is modifying skills, it puts up the sign so other workers wait their turn. This prevents two agents from making conflicting changes at the same time, which could corrupt the skill files.

The Operation Cooldown is the speed limit. Some operations (like scanning all skills) are expensive, so there is a mandatory waiting period between runs to prevent overload or abuse.

How It Works

1. Generation Safety

The generation-safety.ts module implements three security controls: SEC-05 (dangerous command deny list), SEC-06 (script extraction), and SEC-07 (allowed-tools restrictions). These work together during skill generation to prevent unsafe content from reaching the skill store.

Dangerous Command Detection

The scanForDangerousCommands() function tests every line of skill content against a deny list of 11 dangerous command patterns. The patterns are designed to be precise — they flag genuinely dangerous commands while avoiding false positives on safe, common operations.

The deny list covers: recursive filesystem deletion, piped remote code execution, privilege escalation, credential file manipulation, low-level disk destruction, resource exhaustion (fork bombs), and environment variable destruction.

Important precision note: Common safe commands like rm -rf node_modules are NOT flagged because the patterns only match when the target is root, home, wildcard, or $HOME.

Content Sanitization

The sanitizeGeneratedContent() function wraps the scanner and replaces dangerous lines with HTML comment warnings. It is designed to be run on skill body content before it is written to disk. The function processes the entire document, including content inside fenced code blocks, and tracks which code blocks were modified.

import { sanitizeGeneratedContent } from './validation/generation-safety.js';

const result = sanitizeGeneratedContent(dangerousContent);
console.log(result.sanitized);
// Dangerous lines replaced with:
// <!-- BLOCKED: piped-download - Download piped directly to shell execution -->

Allowed-Tools Inference

The inferAllowedTools() function analyzes a skill candidate's type and description to suggest a minimal set of Claude Code tools, following the principle of least privilege.

  • command: Gets Bash and Read
  • file: Gets Write, Edit, Glob, and Read
  • tool: Gets the specific tool if it matches a known Claude Code tool name
  • workflow: Keywords in the description determine tools

The result is always capped at 7 tools maximum and includes Read as a baseline tool.

Script Wrapping

The wrapAsScript() function converts inline bash commands into standalone scripts with proper shebangs and strict error handling (set -euo pipefail).

2. Path Safety

The path-safety.ts module provides defense-in-depth against path traversal attacks. It is used by every storage layer (SkillStore, AgentGenerator, TeamStore) to validate names before they become filesystem paths.

Name Validation

The validateSafeName() function checks for: empty names, null bytes, traversal sequences (..), path separators (/ and \), and filesystem special entries (.).

import { validateSafeName } from './validation/path-safety.js';

validateSafeName('my-skill');          // { valid: true }
validateSafeName('');                  // { valid: false, error: 'Name is empty' }
validateSafeName('foo/bar');           // { valid: false, error: 'Name contains path separator: /' }

Path Containment

The assertSafePath() function verifies that the resolved absolute path stays within the expected base directory. The trailing separator check prevents partial prefix collisions.

3. YAML Safety

The yaml-safety.ts module provides safe YAML frontmatter parsing. YAML supports custom tags like !!js/function that can execute arbitrary JavaScript during deserialization. This module ensures this is impossible.

import { safeParseFrontmatter } from './validation/yaml-safety.js';

const good = safeParseFrontmatter(`---
name: my-skill
description: A safe skill
---
# Content here
`);
// { success: true, data: {...}, body: '# Content here' }

const dangerous = safeParseFrontmatter(`---
name: !!js/function "function() { ... }"
---
`);
// { success: false, error: 'Dangerous YAML tag not allowed: !!js/function' }

4. JSONL Safety

The jsonl-safety.ts module provides SHA-256 checksum computation/verification and schema validation for JSONL observation entries.

import { createChecksummedEntry, verifyChecksum } from './validation/jsonl-safety.js';

const entry = createChecksummedEntry({
  timestamp: Date.now(),
  category: 'tool-sequence',
  data: { tools: ['Read', 'Edit', 'Bash'], count: 3 },
});

const check = verifyChecksum(entry);  // { valid: true }

entry.data.count = 999;
const tampered = verifyChecksum(entry);
// { valid: false, error: 'Checksum mismatch: entry may have been tampered with' }

5. Audit Logger

The AuditLogger records every skill mutation. It implements ACL-02: log all modifications with timestamps and source identification. The log is append-only JSONL.

const logger = new AuditLogger('.claude/.audit-log.jsonl');

await logger.log({
  operation: 'create',
  filePath: '.claude/skills/lint-on-save/SKILL.md',
  source: 'cli:create',
  details: { version: 1, charCount: 423, scope: 'project' },
});

const deletions = await logger.getEntries({
  since: '2025-07-01T00:00:00.000Z',
  operation: 'delete',
});

6. File Lock

The FileLock provides mutual exclusion using lockfiles with PID tracking and stale lock detection. It uses the O_EXCL flag for atomic check-and-create, preventing TOCTOU race conditions.

const lock = new FileLock('.claude/.skill-creator.lock');

const result = await lock.acquire('create-skill');
if (result.acquired) {
  try {
    await performSkillOperation();
  } finally {
    await result.release();
  }
}

7. Operation Cooldown

The OperationCooldown enforces minimum intervals between expensive operations. State is persisted to disk using atomic writes (write-to-temp-then-rename pattern).

const cooldown = new OperationCooldown(DEFAULT_COOLDOWNS);

const result = await cooldown.check('discover');
if (result.allowed) {
  await performDiscovery();
  await cooldown.record('discover');
} else {
  console.log(result.message);
  // "Operation 'discover' is in cooldown. Try again in 3 minutes."
}

Usage Examples

Example 1: Scanning Generated Content for Safety

import { sanitizeGeneratedContent, inferAllowedTools, wrapAsScript } from 'gsd-skill-creator/validation/generation-safety';

const sanitized = sanitizeGeneratedContent(generatedBody);
console.log(`Blocked ${sanitized.findings.length} dangerous commands`);

const tools = inferAllowedTools({ type: 'command', pattern: 'deploy', suggestedDescription: 'Run deployment build and push' });
// ['Bash', 'Read']

const script = wrapAsScript('npm run build --production', 'build-prod');
// { filename: 'build-prod.sh', content: '...', executable: true }

Example 2: Full Lock-Protected Operation with Audit Trail

async function safeCreateSkill(name, metadata, body) {
  const cooldownCheck = await cooldown.check('create');
  if (!cooldownCheck.allowed) throw new Error(cooldownCheck.message);

  const lockResult = await lock.acquire('create-skill');
  if (!lockResult.acquired) throw new Error(lockResult.message);

  try {
    const skill = await store.create(name, metadata, body);
    await logger.log({ operation: 'create', filePath: skill.path, source: 'api:safeCreateSkill', details: { name, version: 1 } });
    await cooldown.record('create');
    return skill;
  } finally {
    await lockResult.release();
  }
}

Example 3: Verifying JSONL Data Integrity

import { validateJsonlEntry, verifyChecksum } from 'gsd-skill-creator/validation/jsonl-safety';

const lines = content.split('\n').filter(l => l.trim());
let valid = 0, tampered = 0, malformed = 0;

for (const line of lines) {
  const schemaResult = validateJsonlEntry(line);
  if (!schemaResult.valid) { malformed++; continue; }
  const parsed = JSON.parse(line);
  if (parsed._checksum) {
    const checksumResult = verifyChecksum(parsed);
    checksumResult.valid ? valid++ : tampered++;
  } else { valid++; }
}
console.log(`Integrity: ${valid} valid, ${tampered} tampered, ${malformed} malformed`);
For Professionals & Developers

Architecture Decisions

Defense-in-depth layering: Path safety is enforced at two levels: name validation and path containment. An attacker would need to bypass both.

Discriminated unions over exceptions: YAML safety, JSONL safety, and path validation return discriminated union results rather than throwing.

Precise deny list patterns: The dangerous command patterns are intentionally narrow to minimize false positives.

O_EXCL for atomic locking: The file lock uses O_CREAT | O_EXCL for atomic check-and-create, eliminating TOCTOU race conditions.

Write-to-temp-then-rename: The cooldown state file uses atomic write patterns.

Checksum isolation to data field: JSONL checksums cover only the data field for forward compatibility.

Extension Points

  • Custom deny lists: Import DANGEROUS_COMMANDS and concatenate with project-specific patterns.
  • Custom cooldowns: Pass any CooldownConfig object to add project-specific operation limits.
  • Custom audit/lock paths: Both constructors accept custom file paths.
  • Audit details: The details field accepts arbitrary data for domain-specific logging.
For SysAdmins & Operators

Storage and Log Management

# Rotate audit log monthly
mv .claude/.audit-log.jsonl ".claude/.audit-log-$(date +%Y-%m).jsonl"

# Count operations by type
cat .claude/.audit-log.jsonl | jq -r '.operation' | sort | uniq -c | sort -rn

Monitoring Recommendations

  • Lock health: If .skill-creator.lock exists for more than 10 minutes, the holding process likely crashed.
  • Stale lock cleanup: FileLock handles stale locks automatically on next acquisition.
  • Cooldown state: Small file, regenerates if deleted.
  • Audit log integrity: Consider append-only filesystem attributes for the audit log.

Security Hardening

  • Append-only audit log: Set the append-only filesystem attribute to prevent deletion or truncation.
  • Strict file permissions: Set the skills directory to owner-only access.
  • Git hooks for review: Add a pre-commit hook that runs scanForDangerousCommands() on changed SKILL.md files.

Incident Response

  1. Check audit log for unexpected operations
  2. Check Git history for unauthorized commits to .claude/skills/
  3. Verify JSONL integrity using checksum verification
  4. Review lock history for concurrent access from unknown PIDs
  5. Check for path traversal artifacts outside .claude/skills/

Troubleshooting

Generation safety blocks a legitimate command

Cause: The dangerous command patterns are intentionally broad for certain categories.

Solution: Wrap the command in a standalone script file in the skill's scripts/ subdirectory.

Lock acquisition fails

Cause: Another process holds the lockfile, or a crashed process left a stale lock.

Solution: Check if the reported PID is running. If dead, the next lock attempt will automatically clean it.

Operation cooldown blocks a needed action

Solution: Wait for the remaining time or use the CLI's --force flag.

YAML safety rejects a valid skill file

Solution: Ensure frontmatter starts with field-value pairs. Remove any !! tag syntax.

JSONL checksum verification fails

Solution: Investigate tampered entries. Recompute the checksum using createChecksummedEntry() if the modification was intentional.

PathTraversalError in normal operation

Solution: Verify the skills directory path. Check for symlinks that might cause path resolution to escape the base directory.

Related Capabilities