Skip to main content
JSSG follows a deny-by-default security model for the most sensitive operations (network requests and spawning processes) and a sandboxed-by-default model for file system access. By default, codemods can work only with files under the target directory; anything else that could touch the outside world is either scoped down or gated behind an explicit capability.

Why defense in depth?

Codemods run arbitrary code transformations on your codebase. By restricting or scoping capabilities, JSSG ensures:
  • Safe execution: Untrusted codemods can’t exfiltrate secrets, read files outside the project, reach the network, or run shell commands without explicit permission.
  • Audit trail: The capabilities field in codemod.yaml provides a clear record of what extra permissions a codemod requires.
  • Principle of least privilege: The default surface is the minimum a typical transformation needs — just the files under your target directory.

File system — sandboxed by default

Unlike fetch and child_process, the fs module is always available but is constrained to the workflow’s target directory (the folder you’re running the codemod against). Reads and writes inside that directory succeed transparently; paths that normalize outside it are rejected with a Node-style EACCES error.

Inside target_dir

Allowed by defaultreadFileSync, writeFileSync, readdirSync (including { withFileTypes: true } Dirents), mkdirSync, statSync, existsSync, accessSync, unlinkSync, and their fs/promises equivalents all work.

Outside target_dir

Rejected with EACCESAbsolute paths outside the target or .. traversals that escape it throw a Node-style error with err.code === "EACCES".
scripts/codemod.ts
Path comparisons first normalize ./.. segments and collapse redundant slashes before the prefix check. When the curated fs is backed by real disk (the CLI), the resolver additionally walks each path component with symlink_metadata and rejects any path that traverses a symlink — a pre-existing target_dir/link-to-elsewhere/secret won’t slip past the guard even though its lexical form stays under target_dir.

Error codes

The curated fs surfaces standard Node err.code strings so existing catch logic keeps working:

Want unrestricted disk access?

If a codemod legitimately needs to read or write outside the target directory (caches, home-directory config, scratch paths in /tmp), opt in explicitly — see Unsafe capabilities below. That swaps the curated fs for the unrestricted LLRT fs module and the path guard is no longer applied.

Unsafe capabilities — require explicit permission

These still require opt-in via capabilities:

fs (unrestricted)

Full real-disk accessUpgrades from the sandboxed curated fs to LLRT’s real-disk fs module. No target_dir prefix check. Use only when the codemod genuinely needs paths outside the target.

fetch

Network requestsMake HTTP/HTTPS requests. Fully deny-by-default.

child_process

Process spawningExecute external commands. Fully deny-by-default.

Default (safe) modules

These modules are always available and require no special permissions:
  • assert - Assertion testing
  • buffer - Binary data manipulation
  • console - Logging and debugging
  • crypto - Cryptographic operations
  • events - Event emitter patterns
  • fs (sandboxed to target_dir)
  • os - Operating system information (read-only)
  • path - File path utilities
  • perf_hooks - Performance measurement
  • process - Process information (read-only, no spawning)
  • string_decoder - String encoding/decoding
  • timers - Timer functions
  • tty - TTY operations
  • util - Utility functions
  • stream_web - Web Streams API
  • url - URL parsing and manipulation
  • zlib - Compression/decompression
Default modules don’t perform network requests or spawn processes. fs is in this list because its default form is sandboxed to target_dir; adding fs to capabilities upgrades it to the unrestricted variant that can read and write anywhere on disk.

Enabling capabilities

To enable unsafe capabilities (or to upgrade fs from sandboxed to unrestricted), add a capabilities field to your codemod.yaml file:
codemod.yaml
Only request capabilities your codemod actually needs. Adding fs here removes the target_dir guard — the codemod can now read and write anywhere on disk. If you only need files in the repo, don’t list it.

Capability names

Use these exact strings in the capabilities array:

Usage examples

Default (sandboxed) file system

No capabilities entry needed — this just works:
scripts/codemod.ts

Unrestricted file system

1

Enable the fs capability (real-disk)

codemod.yaml
2

Use fs anywhere on disk

scripts/codemod.ts
Listing fs gives the codemod read/write access to every path the host user has permission for. Review untrusted codemods carefully before enabling it.

Network requests

1

Enable fetch capability

codemod.yaml
2

Use fetch in your transform

scripts/codemod.ts
Note the async transform function when using fetch or other asynchronous operations.

Running external commands

1

Enable child_process capability

codemod.yaml
2

Spawn processes in your transform

scripts/codemod.ts
Always validate and sanitize any user input before passing it to execSync or spawn to prevent command injection vulnerabilities.

CLI override flags

You can also enable capabilities via CLI flags when running a codemod:
CLI flags take precedence over codemod.yaml. This is useful for testing or one-off runs where you trust the codemod source. --allow-fs swaps the sandboxed fs for the unrestricted variant — if your codemod only needs files inside the target directory, you don’t need to pass it.

Best practices

If your codemod only reads/writes files inside the repo, don’t add fs to capabilities. The default sandboxed fs already works and keeps the scope of your codemod tight for reviewers.
Only request capabilities your codemod truly needs. For example, if you only read config from the repo, you don’t need any capabilities at all.
In your README, explain why each capability is required and what operations use it.
README.md
Never pass unsanitized user input to shell commands:
If possible, provide a version of your codemod that works without unsafe capabilities for maximum trust.

Security considerations

Untrusted codemods: Never run codemods from untrusted sources with fs (unrestricted), fetch, or child_process enabled without reviewing the code first. The default sandboxed fs is designed so that even a malicious codemod can’t exfiltrate data outside the target directory.

Next steps

Intro

Build your first JSSG codemod

Advanced patterns

Learn advanced transformation techniques