Skip to content

Latest commit

 

History

History
401 lines (286 loc) · 10.4 KB

File metadata and controls

401 lines (286 loc) · 10.4 KB

Security

Skim includes built-in protections against denial-of-service (DoS) attacks and malicious input.

Built-in Protections

Max Recursion Depth

Limit: 500 levels

Purpose: Prevents stack overflow on deeply nested code

Example of protected input:

// Extremely nested code (>500 levels)
function a() {
  function b() {
    function c() {
      // ... 500+ levels deep
    }
  }
}

Error message:

Error: Recursion depth exceeded (max: 500 levels)

Max Input Size

Limit: 50MB per file

Purpose: Prevents memory exhaustion

Error message:

Error: File too large: 52,428,800 bytes exceeds maximum of 52,428,800 bytes (50MB)

Rationale: Files larger than 50MB are unusual and may indicate:

  • Minified/bundled code (should be processed before skimming)
  • Generated code (consider processing source instead)
  • Malicious input attempting memory exhaustion

Max AST Nodes

Limit: 100,000 nodes

Purpose: Prevents memory exhaustion from pathological code

Example of protected input:

// Code with 100,000+ AST nodes
const x = [[[[[[...]]]]]]  // Extremely deep nesting

Max JSON Nesting Depth

Limit: 500 levels (serde_json enforces 128 by default)

Purpose: Prevents stack overflow on deeply nested JSON objects

Example of protected input:

{
  "level1": {
    "level2": {
      "level3": {
        // ... 500+ levels deep
      }
    }
  }
}

Error message:

Error: JSON nesting depth exceeded: 501 (max: 500). Possible malicious input.

Note: serde_json has a default recursion limit of 128, which provides primary protection. Our 500-level limit provides a secondary validation layer for consistency with other transformers.

Max JSON Keys

Limit: 10,000 keys per file

Purpose: Prevents memory exhaustion from JSON with millions of keys

Example of protected input:

{
  "key0": "value",
  "key1": "value",
  // ... 10,000+ keys total across all nested objects
}

Error message:

Error: JSON key count exceeded: 10001 (max: 10000). Possible malicious input.

Rationale: Processing JSON with millions of keys could exhaust memory. The 10,000 key limit matches the MAX_SIGNATURES limit used in other transformers, ensuring consistent protection.

Max YAML Nesting Depth

Limit: 500 levels (serde_yaml_ng enforces 128 by default)

Purpose: Prevents stack overflow on deeply nested YAML objects

Example of protected input:

level1:
  level2:
    level3:
      # ... 500+ levels deep

Error message:

Error: YAML nesting depth exceeded: 501 (max: 500). Possible malicious input.

Note: serde_yaml_ng has a default recursion limit of 128, which provides primary protection. Our 500-level limit provides a secondary validation layer for consistency with JSON.

Max YAML Keys

Limit: 10,000 keys per file

Purpose: Prevents memory exhaustion from YAML with millions of keys

Example of protected input:

key0: value
key1: value
# ... 10,000+ keys total across all nested objects

Error message:

Error: YAML key count exceeded: 10001 (max: 10000). Possible malicious input.

Rationale: Processing YAML with millions of keys could exhaust memory. The 10,000 key limit matches the JSON limit, ensuring consistent protection across data formats.

Max TOML Nesting Depth

Limit: 500 levels

Purpose: Prevents stack overflow on deeply nested TOML tables

Example of protected input:

[level1.level2.level3]
# ... 500+ levels deep

Error message:

Error: TOML nesting depth exceeded: 501 (max: 500). Possible malicious input.

Note: The toml crate has a default recursion limit which provides primary protection. Our 500-level limit provides a secondary validation layer for consistency with JSON and YAML.

Max TOML Keys

Limit: 10,000 keys per file

Purpose: Prevents memory exhaustion from TOML with millions of keys

Example of protected input:

key0 = "value"
key1 = "value"
# ... 10,000+ keys total across all tables

Error message:

Error: TOML key count exceeded: 10001 (max: 10000). Possible malicious input.

Rationale: Processing TOML with millions of keys could exhaust memory. The 10,000 key limit matches the JSON and YAML limits, ensuring consistent protection across data formats.

UTF-8 Validation

Protection: Safe handling of multi-byte Unicode characters

Prevents:

  • Invalid UTF-8 sequences
  • Buffer overruns
  • Character encoding attacks

All input is validated as UTF-8 before processing.

Path Traversal Protection

Protection: Rejects malicious file paths

Blocked patterns:

  • ../../../etc/passwd - Parent directory traversal
  • Absolute paths in glob patterns (when security matters)
  • Symlinks in directory processing

Example:

# Blocked for security
$ skim "../../../etc/passwd"
Error: Path traversal detected

# Blocked in glob mode
$ skim "/etc/*.conf"
Error: Glob pattern must be relative (cannot start with '/')

Sandboxing

No Network Access

Skim never makes network requests. All processing is local.

No Code Execution

Skim only parses code, never executes it. Source code is treated as data, not instructions.

Read-Only by Default

By default, Skim only reads files. Cache writes are the only file modifications, and they're:

  • In user's cache directory only
  • JSON format (not executable)
  • Atomic (prevents partial writes)

Vulnerability Disclosure

If you discover a security vulnerability, please:

  1. Do not open a public GitHub issue
  2. Email security details to the maintainers (see SECURITY.md)
  3. Allow time for a fix before public disclosure

See SECURITY.md for the full disclosure process.

Security Best Practices

When Processing Untrusted Code

If processing code from untrusted sources:

  1. Validate file size - Check files aren't larger than expected
  2. Use --no-cache - Avoid caching untrusted content
  3. Run in container - Isolate Skim process
  4. Set resource limits - Use ulimit or container limits
# Process untrusted code safely
ulimit -m 1000000  # 1GB memory limit
skim untrusted.ts --no-cache

In CI/CD Pipelines

  1. Pin Skim version - Don't use latest
  2. Use --no-cache - Avoid cache poisoning
  3. Validate inputs - Check file sizes before processing
  4. Set timeouts externally - Limit processing time at the CI step or shell level

No internal time cap — use external timeouts

Important: skim does NOT impose an internal wall-clock timeout on wrapped commands (removed in ADR-008). It imposes a 64 MiB memory cap on captured output (MAX_OUTPUT_BYTES) — that protection remains. The cap is applied per pipe: stdout and stderr are each capped independently at 64 MiB, so worst-case combined retained output is ~128 MiB. If you need a hard time bound, add it externally:

  • CI step timeout: timeout-minutes: in GitHub Actions (or equivalent in other CI systems)
  • Shell timeout(1): timeout 300 skim ...
  • Agent tool timeout: configure at the agent level
  • Interactive: Ctrl-C
# GitHub Actions example — bound command lifetime at the step level
- name: Process code
  timeout-minutes: 5
  run: skim src/ --no-cache > docs/api.txt

# Or use the shell timeout(1) wrapper
- name: Process code
  run: |
    # External timeout — skim itself imposes no time cap
    timeout 300 skim src/ --no-cache > docs/api.txt

Dev servers and watch-mode commands

For interactive dev servers and watch-mode commands (vite dev, npm run dev, jest --watch, etc.), skim detects them automatically and passes them through with inherited stdio — no buffering, no compression. This means Ctrl-C works and live output streams directly to the terminal.

Piped-stdin and vitest: the indefinite-command guard fires regardless of whether stdin is a terminal. As an accepted tradeoff, cat output | skim vitest runs vitest live rather than compressing the piped input. To compress piped vitest output, use skim vitest run (the run subcommand marks the invocation as finite) or set SKIM_PASSTHROUGH=1 to let skim forward piped content without spawning a new process.

Glob Pattern Security

When accepting glob patterns from users:

  1. Validate patterns - Reject patterns starting with / or containing ..
  2. Limit scope - Restrict to specific directories
  3. Use allowlists - Only allow known-good patterns
# Good: Relative pattern
skim "src/**/*.ts"

# Bad: Absolute pattern (rejected)
skim "/etc/**/*.conf"

# Bad: Parent traversal (rejected)
skim "../../../*.ts"

Security Considerations

Tree-sitter Security

Skim uses tree-sitter for parsing. Tree-sitter:

  • ✅ Memory-safe (written in C with safety checks)
  • ✅ Does not execute code
  • ✅ Handles malformed input gracefully
  • ✅ Well-tested on billions of lines of code (GitHub uses it)

Rust Security

Skim is written in Rust, which provides:

  • ✅ Memory safety without garbage collection
  • ✅ No buffer overflows
  • ✅ No null pointer dereferences
  • ✅ Thread safety

Dependencies

Skim has minimal dependencies. All dependencies are:

  • Vetted for security issues
  • Regularly updated
  • From trusted sources (crates.io, npm)

Threat Model

In Scope

Skim protects against:

  • ✅ Malicious code causing crashes (DoS)
  • ✅ Malicious code causing memory exhaustion
  • ✅ Path traversal attacks
  • ✅ Malformed UTF-8

Out of Scope

Skim does NOT protect against:

  • ❌ Viewing sensitive code (Skim outputs what you give it)
  • ❌ Information disclosure (you control the output)
  • ❌ Supply chain attacks on dependencies (use cargo-audit)

Security Testing

Skim includes security tests for:

  • Maximum input size enforcement
  • Path traversal rejection
  • Symlink rejection
  • Glob pattern validation

Run security tests:

cargo test --test security

Reporting Security Issues

See SECURITY.md for:

  • Contact information
  • Response timeline
  • Disclosure policy
  • Security policy

Security Changelog

Security fixes are noted in CHANGELOG.md with [SECURITY] prefix.

Future Security Enhancements

Potential improvements (not yet implemented):

  • Process isolation (sandbox)
  • Resource limits (CPU time, memory)
  • Content security policy for cache
  • Audit logging for security events

See GitHub issues for security enhancement requests.