Agent Shield beta · wrap Claude Code, Cursor and your MCP servers in a runtime contract →

[ Product · Runtime firewall ]

Android-style permissions for node_modules.

Your phone asks before an app reads your contacts. Node never asks before a package reads your secrets. Capwall gives every dependency an explicit grant and stops anything outside it, while your own code runs untouched.

capabilities.lockillustrative
# generated by capwall init, reviewed in git
packages:
  stripe:
    allow: [env.read, http.request]
  pg:
    allow: [net.connect, dns.resolve]
  chalk:
    allow: []
  some-package:
    deny:  [child_process]

[ Before / after ]

Same package. Different powers.

A package deep in node_modules wants to…Plain NodeWith Capwall
Read STRIPE_SECRET_KEY✕ Allowed, silently✓ Only if granted env.read
Spawn a shell✕ Allowed, silently✓ Only if granted child_process
Phone home to an unknown server✕ Allowed, silently✓ Only if granted net.connect
Rewrite files on disk✕ Allowed, silently✓ Only if granted fs.write
Run code from a string✕ Allowed, silently✓ Only if granted dynamic-code

[ 11 capabilities ]

Every dangerous power, named.

Grant them per package with capwall allow, revoke with capwall deny.

env.readRead values from process.env.
import.guardBlock undeclared lateral require/import across node_modules (NodeShield).
env.writeSet or delete environment variables.
child_processSpawn, exec or fork other programs.
net.connectOpen outbound TCP connections.
net.listenOpen a server or listening socket.
http.requestMake HTTP(S) requests, including fetch.
dns.resolveLook up hostnames.
fs.readRead files from disk.
fs.writeCreate, change or delete files.
dynamic-codeRun code built from strings.
worker_threadsStart worker threads.
your codeNever restricted. Only dependencies are gated.

[ Under the hood ]

Three layers. One gate.

01 · LOADER HOOKS

ESM imports, gated on resolve

A node --import hook sees every import before it resolves and knows which package asked.

02 · MODULE PATCHES

CommonJS goes through the same check

Module._load is patched so require() of child_process, net or fs is attributed and checked too.

03 · GUARDED GLOBALS

process.env and fetch, wrapped

Globals a package can reach without importing anything are guarded, so a stray env read is still caught.

FAIL-CLOSEDA recognized package stepping outside its grant throws ERR_CAPWALL_CAPABILITY_DENIED and the process dies. No silent leak.
HONEST BOUNDARIESThis is a capability gate, not a cryptographic sandbox. It raises the bar enormously. It isn't unbreakable, and we say so.

[ Attestation · in-toto + DSSE ]

Prove the lock you scanned is the lock you ran.

Every pipeline step signs an Ed25519 envelope recording its materials and products.

STEP 1 · developer keygit_checkout
STEP 2 · CI keyast_capability_scanproduct: capabilities.lock
STEP 3 · CI keybuild_package
STEP 4 · runtime node keyruntime_enforcementmaterial: capabilities.lock
STEP 2 PRODUCT ══ SHA-256 MATCH ══ STEP 4 MATERIAL
$ capwall attest verify

Fails unless the lock the runtime enforced is bit-identical to the one the scanner produced. Runtime telemetry is recorded as a runtime bill of materials.

[ Try it ]

Watch it block a malicious package live.

$ cd examples/vulnerable-app $ capwall run -- node app.js ERR_CAPWALL_CAPABILITY_DENIED
[ Read the quickstart ]