Skim includes built-in protections against denial-of-service (DoS) attacks and malicious input.
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)
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
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 nestingLimit: 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.
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.
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 deepError 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.
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 objectsError 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.
Limit: 500 levels
Purpose: Prevents stack overflow on deeply nested TOML tables
Example of protected input:
[level1.level2.level3]
# ... 500+ levels deepError 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.
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 tablesError 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.
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.
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 '/')Skim never makes network requests. All processing is local.
Skim only parses code, never executes it. Source code is treated as data, not instructions.
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)
If you discover a security vulnerability, please:
- Do not open a public GitHub issue
- Email security details to the maintainers (see SECURITY.md)
- Allow time for a fix before public disclosure
See SECURITY.md for the full disclosure process.
If processing code from untrusted sources:
- Validate file size - Check files aren't larger than expected
- Use
--no-cache- Avoid caching untrusted content - Run in container - Isolate Skim process
- Set resource limits - Use ulimit or container limits
# Process untrusted code safely
ulimit -m 1000000 # 1GB memory limit
skim untrusted.ts --no-cache- Pin Skim version - Don't use
latest - Use
--no-cache- Avoid cache poisoning - Validate inputs - Check file sizes before processing
- Set timeouts externally - Limit processing time at the CI step or shell level
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.txtFor 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.
When accepting glob patterns from users:
- Validate patterns - Reject patterns starting with
/or containing.. - Limit scope - Restrict to specific directories
- 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"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)
Skim is written in Rust, which provides:
- ✅ Memory safety without garbage collection
- ✅ No buffer overflows
- ✅ No null pointer dereferences
- ✅ Thread safety
Skim has minimal dependencies. All dependencies are:
- Vetted for security issues
- Regularly updated
- From trusted sources (crates.io, npm)
Skim protects against:
- ✅ Malicious code causing crashes (DoS)
- ✅ Malicious code causing memory exhaustion
- ✅ Path traversal attacks
- ✅ Malformed UTF-8
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)
Skim includes security tests for:
- Maximum input size enforcement
- Path traversal rejection
- Symlink rejection
- Glob pattern validation
Run security tests:
cargo test --test securitySee SECURITY.md for:
- Contact information
- Response timeline
- Disclosure policy
- Security policy
Security fixes are noted in CHANGELOG.md with [SECURITY] prefix.
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.