Chisel
Reduce context usage on file use. Send only unified diffs instead of full files (up to 20-100ร fewer tokens), and read large files with targeted grep/sed instead of full reads (up to 500ร). Kernel-enforced path confinement hard-locks the agent to a configured root: no accidental reads or writes outside scope. Standalone for your file access or embed in any MCP server (Rust, Node.js, Python via
README
Chisel
<img src="chisel.jpeg" alt="Chisel" width="300" />
๐ช Rust powered precision file operations for agents. Unix-native tools, minimal context footprint, strict path confinement: use directly with Chisel MCP or bring your own MCP, embeddable in any MCP server in Rust, Python, Nodejs.
https://github.com/user-attachments/assets/af84f1af-db47-4e42-808b-00861504cd34
Install โ download a pre-built .mcpb bundle (one-click, no build step) or a raw binary from the Releases page โ see Standalone usage below.
Agent skill included โ
skills/chisel/SKILL.mdteaches agents how to use Chisel at maximum efficiency. Install with:npx skills add ckanthony/Chisel
Security hardened โ Verified properties across two layers: the MCP server (
chisel) and the portable core library (chisel-core). See the Security model section for the full breakdown.
Contents
- Motivation
- Tools
- Standalone usage
- Behind a reverse proxy
- Bring your own MCP
- Extending chisel
- Workspace layout
- Security model
- Platform support
- Development
- Contributing
- Future considerations
Motivation
Most MCP file tools hand an LLM a blank canvas: read anything, write anything, make any mistake. Chisel takes the opposite approach:
- Reduce context overhead โ every tool call is compact. File edits go through
patch_apply: the model sends only a unified diff instead of rewriting an entire file, so large-file edits cost a fraction of the tokens - Familiar command patterns โ
shell_execexposes the same Unix tools (grep,sed,awk,find,cat, โฆ) LLMs already know well from training data, so prompts stay short and outputs are predictable - Precision over flexibility โ a fixed whitelist and strict path confinement mean the model cannot accidentally escape scope or run arbitrary commands
- Safety first โ bearer-token auth,
127.0.0.1-only binding, symlink-aware root confinement, atomic writes, and a read-only mode are all on by default - Reusable core โ
chisel-coreis a plain synchronous Rust library; any MCP server (Rust, Node.js via WASM, Python via WASM) can embed it without running a second process
Real-world demo
Six tasks on the same markdown file. Left: typical MCP file server. Right: chisel.
File: docs/api.md โ 300 lines, 6 headers, one section of ~20 lines.
Task 1 โ find all headers
# Typical # Chisel
tool: read_file tool: shell_exec
path: /data/docs/api.md command: grep
args: ["-n", "^#", "/data/docs/api.md"]
โ 300 lines returned (~3 000 tokens) โ 6 lines returned (~30 tokens)
model must scan the whole file 1:# API Reference
45:## Authentication
89:## Endpoints
134:## Request Format
178:## Response Format
234:## Errors
Task 2 โ read the content under ## Endpoints
# Typical # Chisel
tool: read_file (again, or re-use above) tool: shell_exec
path: /data/docs/api.md command: sed
args: ["-n", "/^## Endpoints/,/^## /p",
โ 300 lines returned again (~3 000 tokens) "/data/docs/api.md"]
model must locate the section in context
โ 44 lines returned (~440 tokens)
only the Endpoints section
Task 3 โ edit one line in that section
# Typical # Chisel
tool: write_file tool: patch_apply
path: /data/docs/api.md path: /data/docs/api.md
content: <entire 300-line file patch: |
with one line changed> --- a/docs/api.md
+++ b/docs/api.md
โ 300 lines uploaded (~3 000 tokens) @@ -91,1 +91,1 @@
any hallucination corrupts the file -GET /v1/items
+GET /v2/items
โ 7 lines uploaded (~50 tokens)
hunk mismatch โ PatchFailed, file untouched
Task 4 โ replace all - with : in that section
# Typical # Chisel
tool: read_file tool: shell_exec
path: /data/docs/api.md command: sed
args: ["-i", "s/-/:/g",
โ 300 lines returned (~3 000 tokens) "/data/docs/api.md"]
tool: write_file
content: <300 lines with replacement> โ 1 line call, 0 file content transmitted
โ 300 lines uploaded (~3 000 tokens) sed runs the replacement in-place
Total: ~12 000 tokens Total: ~520 tokens (23ร less)
Task 5 โ model tries to run rm -rf ~
# Typical # Chisel
(no shell tool exposed) tool: shell_exec
command: rm
args: ["-rf", "~"]
not applicable โ typical MCP file
servers have no shell tool, so the โ CommandNotAllowed
model would need a separate shell "rm" is not in the whitelist.
MCP or use write_file to script it Permitted: grep sed awk find cat
head tail wc sort uniq cut tr
diff file stat ls du rg
process is never spawned
rm,bash,sh,curl,chmod,sudoโ none of these are in the whitelist. The list is fixed at compile time; it cannot be extended at runtime by the model.
Task 6 โ model tries to edit /Users/home/.ssh/config directly
# Typical # Chisel
tool: write_file tool: patch_apply
path: /Users/home/jor/.ssh/config path: /Users/home/jor/.ssh/config
content: <malicious key appended> patch: <adds authorized_keys entry>
โ succeeds if the server process โ OutsideRoot
has filesystem access โ no path resolved path /Users/home/.ssh/config
confinement in a naive file server does not start with root /data
I/O is never performed
Every path โ including those passed to
shell_execโ is validated against the configured root before any I/O or process spawn. An absolute path outside root is always rejected, regardless of which tool is called.
Context cost comparison
Estimates use Claude Sonnet 4.6 tokenisation: typical source code averages ~10 tokens/line (identifiers, punctuation, and whitespace each count as tokens under BPE).
Single edit โ 500-line file, 5 lines changed:
Naive (read_file โ write_file) |
Chisel (patch_apply) |
Reduction | |
|---|---|---|---|
| Tokens in (upload) | ~5 000 (full file) | ~120 (11 diff lines + headers) | 42ร |
| Tokens out (model output) | ~5 000 (full file rewrite) | ~15 (success ack) | 333ร |
| Round-trip total | ~10 000 | ~135 | ~74ร |
| Failure mode | Silent hallucination corrupts entire file | PatchFailed โ original untouched |
โ |
Read / search โ 2 000-line file:
| Task | Naive (read_file full) |
Chisel (shell_exec) |
Reduction |
|---|---|---|---|
| Find a symbol | ~20 000 tokens (full read) | ~40 tokens (grep matched lines) |
500ร |
| Count occurrences | ~20 000 tokens | ~5 tokens (grep -c integer) |
4 000ร |
| Extract lines 40โ60 | ~20 000 tokens | ~210 tokens (sed -n '40,60p') |
95ร |
| Directory tree | ~20 000 tokens | ~300 tokens (find / ls -R) |
67ร |
Savings scale linearly with file size. A 2 000-line file costs 4ร more than the 500-line baseline above.
Tools
Every path argument is canonicalized and confined to the configured root before any I/O โ .. traversal and symlink escapes are rejected. This confinement is enforced inside chisel-core and applies equally when the library is embedded directly.
When using the MCP server (chisel), all tools additionally require Authorization: Bearer <secret>.
| Tool | Description |
|---|---|
patch_apply |
Apply a unified diff atomically; accepts raw or ````diff` fenced patches โ primary edit tool; sends only changed lines, not the full file |
append |
Append content to an existing file |
write_file |
Write (create or overwrite) a file; creates parent dirs automatically |
create_directory |
Create a directory tree (mkdir -p semantics) |
move_file |
Move or rename a file within root |
shell_exec |
Run a whitelisted command โ grep sed awk find cat head tail wc sort uniq cut tr diff file stat ls du rg |
Full reference: docs/tools.md
Bring your own MCP
Chisel ships two embeddable libraries alongside the standalone server. Neither carries any HTTP, MCP protocol, or async runtime dependency โ drop them into your own server and own the transport entirely.
| Package | What it is | Use it when |
|---|---|---|
chisel-core |
Pure Rust sync library โ path confinement, all file ops, shell exec | Writing a Rust MCP server |
chisel-wasm |
chisel-core compiled to wasm32-wasip1 |
Writing an MCP server in Node.js, Python, Deno, or any WASI runtime |
Full integration guide โ chisel-core/README.md
Standalone usage
Binary
Option A โ Download pre-built binary (recommended)
Go to the latest release and download the binary for your platform:
| Platform | File |
|---|---|
| macOS Apple Silicon | chisel-macos-arm64 |
| macOS Intel | chisel-macos-x86_64 |
| Linux x86-64 | chisel-linux-x86_64 |
| Linux ARM64 | chisel-linux-arm64 |
# Make executable and run
chmod +x chisel-macos-arm64 # adjust filename for your platform
MCP_APP_SECRET=mysecret ./chisel-macos-arm64 --root /path/to/data
Option B โ Build from source
cargo build --release -p chisel
Running
# Secret via env var (preferred)
MCP_APP_SECRET=mysecret ./chisel --root /path/to/data
# Or via --secret flag (env var takes precedence if both set)
./chisel --root /path/to/data --secret mysecret
# Read-only mode (shell_exec still works; writes are blocked)
MCP_APP_SECRET=mysecret ./chisel --root /path/to/data --read-only
The server binds to 127.0.0.1:3000 by default. Use --port to change the port.
Docker
Three ways to run Chisel in Docker โ pick the one that fits your workflow.
Option 1 โ Docker Compose (recommended)
# 1. Create your .env
echo "MCP_APP_SECRET=changeme" > .env
# 2. Create the data directory that will be exposed to the LLM
mkdir -p data
# 3. Start (builds the image on first run, then stays running)
docker compose up -d
# Tail logs
docker compose logs -f
# Stop
docker compose down
docker-compose.yml mounts ./data โ /data inside the container and binds 127.0.0.1:3000:3000 โ the port is never exposed beyond localhost.
Option 2 โ docker run
# Build
docker build -t chisel:latest .
# Run (replace /absolute/path/to/data with your actual data directory)
docker run -d \
--name chisel \
-e MCP_APP_SECRET=changeme \
-v /absolute/path/to/data:/data \
-p 127.0.0.1:3000:3000 \
--restart unless-stopped \
chisel:latest
# Read-only mode (writes blocked, shell_exec still works)
docker run -d \
--name chisel \
-e MCP_APP_SECRET=changeme \
-v /absolute/path/to/data:/data:ro \
-p 127.0.0.1:3000:3000 \
chisel:latest chisel --root /data --read-only
Option 3 โ Custom port
docker run -d \
--name chisel \
-e MCP_APP_SECRET=changeme \
-v /absolute/path/to/data:/data \
-p 127.0.0.1:8080:3000 \
chisel:latest
Host port 8080 maps to container port 3000. Update your MCP client URL to http://127.0.0.1:8080/mcp accordingly.
The container runs as a non-root user (
chisel) and never binds on0.0.0.0. For remote access, place Caddy or nginx in front โ see Behind a reverse proxy.
Configure your MCP client
{
"mcpServers": {
"chisel": {
"url": "http://127.0.0.1:3000/mcp",
"headers": {
"Authorization": "Bearer <your-secret>"
}
}
}
}
Behind a reverse proxy (Caddy)
Chisel deliberately refuses to bind on 0.0.0.0. Remote or multi-client access goes through a reverse proxy that handles TLS.
Caddy โ automatic HTTPS
mcp.yourdomain.com {
reverse_proxy 127.0.0.1:3000
}
Caddy auto-provisions a Let's Encrypt certificate. The bearer token still authenticates every request end-to-end.
# Install Caddy (macOS)
brew install caddy
# Run
caddy run --config Caddyfile
Your MCP client URL becomes https://mcp.yourdomain.com/mcp.
nginx
server {
listen 443 ssl;
server_name mcp.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
}
}
Extending chisel
See chisel-core/README.md for the full integration guide covering:
- Rust API reference with code examples
spawn_blockingpattern for async handlers- Node.js (โฅ 22) WASM / WASI integration
- Python (
wasmtime-py) WASM integration - Error types and security properties
Workspace layout
chisel/
โโโ chisel/ # HTTP server binary (MCP Streamable HTTP transport)
โโโ chisel-core/ # Portable sync library โ no HTTP, no async, no MCP protocol
โโโ chisel-wasm/ # wasm32-wasip1 build of chisel-core for Node.js / Python / Deno
graph TD
A[LLM / MCP client] -->|HTTP + Bearer| B[chisel binary]
B --> C[chisel-core]
D[Your Rust MCP server] --> C
E[Node.js MCP server] -->|WASI| F[chisel-wasm .wasm]
G[Python MCP server] -->|WASI| F
F --> C
Security model
Security properties are split across two layers. Each row is verified by the test suite referenced at the bottom of this section.
chisel โ MCP server / HTTP layer
| # | Property | Mechanism | Default |
|---|---|---|---|
| 1 | Bearer-token auth โ every request authenticated | subtle::ConstantTimeEq; timing-safe comparison; missing or empty secret โ process exits at startup |
required |
| 2 | Rate limiting โ brute-force and runaway-agent protection | governor token-bucket; excess requests โ HTTP 429 before auth is checked |
100 req/s |
| 3 | Request body cap โ memory exhaustion prevention | axum::DefaultBodyLimit; oversized body โ HTTP 413 before any parsing |
4 MiB |
| 4 | Audit log โ every tool op traceable | tracing::info on success, tracing::warn on failure; records op name, path, error |
info |
| 5 | Loopback-only binding โ no accidental public exposure | TcpListener::bind("127.0.0.1:<port>"); 0.0.0.0 is never used |
โ |
chisel-core โ portable library (enforced on every op, including embedded use)
| # | Property | Mechanism |
|---|---|---|
| 6 | Kernel-enforced root confinement โ directory traversal, symlink escape, TOCTOU all blocked | cap_std::fs::Dir; every path component traversed via openat(fd, component, O_NOFOLLOW); confinement is enforced at the kernel level during I/O, not in userspace before it |
| 7 | Atomic writes โ failed patch never corrupts the target file | Dir::create(".name.PID.tmp") + Dir::rename(tmp โ target); both ops confined inside the same root fd; on any failure the tmp file is discarded and the original is untouched |
| 8 | Read-only mode โ blanket write protection | check_writable(read_only) runs before any I/O inside every write op; no code path bypasses it |
| 9 | Shell whitelist + direct execve โ no injection, no arbitrary commands |
Fixed compile-time whitelist; std::process::Command spawns directly (no sh -c); path-like args validated via validate_path before spawn |
graph LR
req[Incoming request] --> body{Body โค limit?\nDefaultBodyLimit}
body -- too large --> 413[HTTP 413]
body -- ok --> rate{Rate limit\ntoken-bucket}
rate -- exceeded --> 429[HTTP 429]
rate -- ok --> auth{Bearer token\nconstant-time compare}
auth -- reject --> 401[HTTP 401]
auth -- pass --> path{Path confinement\ncap-std openat + O_NOFOLLOW}
path -- outside root --> err[OutsideRoot error]
path -- inside root --> ro{Read-only mode?}
ro -- write op --> readonly[ReadOnly error]
ro -- read / shell --> exec[Execute tool]
exec --> audit[Audit log\ntracing::info/warn]
exec --> atomic[Atomic write\nDir::create + Dir::rename]
Attack and misuse prevention
Each layer targets a specific class of failure โ accidental or deliberate.
1. Authentication โ unauthorised access
Every HTTP request must carry Authorization: Bearer <secret>. The comparison uses subtle::ConstantTimeEq, which takes the same number of CPU cycles regardless of how many characters match. A timing-based brute-force attack that probes the token character-by-character cannot succeed because no timing signal leaks.
The server refuses to start if the secret is empty or absent โ misconfiguration is a hard error, not a silent fallback to no auth.
2. Path confinement โ escape from root directory
This is the core guarantee. All filesystem tools (patch_apply, append, write_file, create_directory, move_file) operate exclusively through a cap_std::fs::Dir handle rooted at the configured root directory.
input string
โ strip root prefix โ relative path
โ cap_std::fs::Dir::open / write / rename โฆ
โโ kernel: openat(root_fd, "sub/file", O_NOFOLLOW)
openat(sub_fd, "file", O_NOFOLLOW)
Every component of the path is traversed via openat with O_NOFOLLOW. The kernel enforces confinement โ no userspace prefix check can be bypassed:
| Attack | Example input | What happens |
|---|---|---|
| Directory traversal | /data/sub/../../etc/passwd |
.. components tracked per-fd; escape above root blocked by kernel โ error |
| Absolute path bypass | /etc/hosts |
Root prefix stripped; remaining path confined to root fd โ OutsideRoot |
| Symlink in component | /data/link/file where link โ /etc |
O_NOFOLLOW on link open โ kernel rejects โ error |
| TOCTOU swap | Valid path at check time, swapped to symlink before I/O | No separate check/use window โ confinement is enforced during the I/O itself |
validate_path (the userspace canonicalize + prefix check from earlier versions) is still used exclusively for shell_exec path arguments, where we pass strings to spawned processes that cap-std cannot confine.
3. Shell injection โ arbitrary command execution
shell_exec does not invoke a shell interpreter. It calls std::process::Command directly with the command and arguments as separate OS-level strings:
shell_exec("grep", ["-r", "foo", "/data"])
โ execve("/usr/bin/grep", ["-r", "foo", "/data"]) โ no sh -c wrapper
Shell metacharacters (;, &&, |, $(), backticks, etc.) in any argument are passed as literal bytes to the target process. There is no shell to interpret them.
A fixed compile-time whitelist (grep sed awk find cat head tail wc sort uniq cut tr diff file stat ls du rg) is checked before the process is spawned. Any command not on the list returns CommandNotAllowed immediately โ the process is never started.
Path-like arguments (starting with / or containing ..) are validated against root via validate_path before the process starts.
4. Partial write โ file corruption on failed patch
patch_apply never writes directly to the target file. The flow is entirely confined within the cap-std Dir:
1. Parse and validate the diff
2. Dir::create(".filename.PID.tmp") โ confined temp file, same directory
3. On success โ Dir::rename(tmp, target) โ single syscall, cannot be interrupted mid-write
4. On failure โ return PatchFailed, tmp is dropped and cleaned up
If the hunk context does not match the current file (the file has drifted since the diff was generated), the operation aborts at step 1 and the original file is never touched. A process crash between steps 2 and 3 leaves the .tmp file โ the original is still intact.
5. Read-only mode โ blanket write protection
Starting with --read-only causes all five write tools (patch_apply, append, write_file, create_directory, move_file) to return ReadOnly immediately without performing any I/O. The check happens inside chisel-core before any disk access โ there is no code path that bypasses it.
shell_exec remains available in read-only mode because it only reads (whitelisted commands are all inspection tools; mkdir and mv are explicitly excluded from the whitelist).
6. Rate limiting โ brute-force and runaway-agent protection
Every request passes through a token-bucket rate limiter (governor) before authentication. When the configured rate (default: 100 req/s) is exceeded, the server immediately returns HTTP 429 Too Many Requests without doing any work.
This prevents two threat classes:
- Token brute-force: an attacker cannot enumerate tokens faster than the configured rate, even with a leaked secret in partial form.
- Runaway agent loops: a malfunctioning LLM client flooding the server is throttled before it can cause resource exhaustion.
Configure with --rate-limit <N> (or set to 0 to disable). The limiter is the outermost layer โ requests that exceed the rate never reach the auth check.
7. Request body cap โ memory exhaustion prevention
All incoming request bodies are capped at 4 MiB by default (DefaultBodyLimit). An oversized body is rejected before any parsing, authentication, or tool dispatch occurs.
Configure with --body-limit <bytes>. For typical LLM usage (unified diffs and file content), 4 MiB is generous โ increase only if you intentionally send large file writes.
8. Audit logging โ operation traceability
Every tool invocation emits a structured tracing log line recording the operation name, the target path (or command), and whether it succeeded or failed:
INFO chisel::tools::filesystem op=patch_apply path=/data/foo.txt
WARN chisel::tools::filesystem op=write_file path=/etc/passwd error=OutsideRoot { ... }
INFO chisel::tools::shell op=shell_exec cmd=grep exit_code=0
Log verbosity is controlled via the RUST_LOG environment variable (e.g. RUST_LOG=chisel=debug). The default level is info, which captures every tool call result without flooding output with framework internals.
9. Network exposure โ no accidental public binding
The server calls TcpListener::bind("127.0.0.1:<port>") โ not 0.0.0.0. It is impossible for the process itself to accept connections from outside the machine. Remote access must be deliberately routed through a reverse proxy (Caddy, nginx), which is where TLS termination and any additional access controls live.
The Docker image runs as a non-root user (mcp) and the docker-compose.yml maps the port as 127.0.0.1:3000:3000, preserving the loopback restriction even inside a container.
Security test coverage
Every property above is verified by a dedicated test suite at [chisel/tests/security.rs](chisel/tests/security.rs).
Run with cargo test --test security -p chisel.
| Property | Tests |
|---|---|
| ยง1 Authentication | auth_missing_secret_is_hard_error ยท auth_empty_secret_is_hard_error ยท auth_missing_header_returns_401 ยท auth_wrong_token_returns_401 ยท auth_basic_scheme_returns_401 ยท auth_prefix_of_secret_returns_401 ยท auth_valid_token_passes |
| ยง2 Path confinement | path_directory_traversal_is_blocked ยท path_absolute_outside_root_returns_outside_root_error ยท path_symlink_in_component_is_blocked ยท path_toctou_symlink_swap_is_blocked ยท path_deeply_nested_outside_root_is_blocked |
| ยง3 Shell injection | shell_dangerous_commands_blocked_before_spawn ยท shell_metacharacters_are_literal ยท shell_path_arg_outside_root_blocked_before_spawn ยท shell_traversal_in_arg_blocked_before_spawn |
| ยง4 Partial write | partial_write_failed_patch_leaves_file_intact ยท partial_write_no_tmp_artefact_on_failure |
| ยง5 Read-only mode | readonly_all_write_tools_are_blocked ยท readonly_shell_exec_remains_available ยท readonly_no_disk_mutation_occurs |
| ยง6 Network binding | network_bind_address_is_loopback_only ยท network_sse_endpoint_returns_404 |
All 23 tests pass on every CI run. A failure means a documented security guarantee has regressed.
$ cargo test --test security -p chisel
running 23 tests
test auth_empty_secret_is_hard_error ... ok
test auth_missing_secret_is_hard_error ... ok
test network_bind_address_is_loopback_only ... ok
test path_absolute_outside_root_returns_outside_root_error ... ok
test path_deeply_nested_outside_root_is_blocked ... ok
test readonly_all_write_tools_are_blocked ... ok
test readonly_no_disk_mutation_occurs ... ok
test path_directory_traversal_is_blocked ... ok
test shell_dangerous_commands_blocked_before_spawn ... ok
test shell_path_arg_outside_root_blocked_before_spawn ... ok
test path_symlink_in_component_is_blocked ... ok
test shell_traversal_in_arg_blocked_before_spawn ... ok
test partial_write_failed_patch_leaves_file_intact ... ok
test partial_write_no_tmp_artefact_on_failure ... ok
test path_toctou_symlink_swap_is_blocked ... ok
test readonly_shell_exec_remains_available ... ok
test shell_metacharacters_are_literal ... ok
test auth_missing_header_returns_401 ... ok
test auth_valid_token_passes ... ok
test auth_basic_scheme_returns_401 ... ok
test auth_prefix_of_secret_returns_401 ... ok
test auth_wrong_token_returns_401 ... ok
test network_sse_endpoint_returns_404 ... ok
test result: ok. 23 passed; 0 failed
Development
# Run all tests
cargo test --workspace
# Run only the server tests
cargo test -p chisel
# Build the WASM target
rustup target add wasm32-wasip1
cargo build --target wasm32-wasip1 -p chisel-wasm
# Build Docker image
docker build -t chisel:dev .
Platform support
| Platform | Server binary (chisel) |
chisel-core (lib, no shell) |
chisel-wasm |
|---|---|---|---|
| Linux (x86_64, arm64) | โ Full support | โ | โ |
| macOS (Apple Silicon, Intel) | โ Full support | โ | โ |
| Windows | โ Not supported | โ ๏ธ Compiles, untested | โ |
Linux / macOS โ primary targets. All nine security properties hold. Docker image is Debian-based. shell_exec whitelist (grep, sed, awk, find, cat, ls, โฆ) assumes a standard Unix environment.
Windows โ not supported for the server binary for three reasons:
shell_execwhitelist is entirely Unix tools; most do not exist natively on Windowscap-stdkernel confinement uses POSIXopenat + O_NOFOLLOWsemantics; Win32 reparse-point / junction handling differs- Symlink creation requires elevated privileges (
SeCreateSymbolicLinkPrivilege) โ security tests cannot run without Administrator or Developer Mode
**chisel-wasm** โ fully portable. WASM has no OS dependency; runs identically on any WASI-capable runtime (Node.js โฅ 22, Deno, Wasmtime) regardless of host OS. shell_exec is excluded from the WASM build.
Contributing
Contributions are welcome. A few guidelines:
- Bug fixes and small improvements โ open a PR directly.
- New tools or behaviour changes โ open an issue first to agree on scope before writing code.
- Security issues โ do not open a public issue. File a private report and include reproduction steps.
Before submitting:
cargo test --workspace # all tests must pass
cargo test --test security -p chisel # security suite must be green
cargo clippy --workspace -- -D warnings
cargo fmt --check
New security-relevant code should come with a matching test in chisel/tests/security.rs that names the exact attack vector it covers.
Future considerations
S3 / R2 object storage backend
A potential extension is a thin sync layer that hydrates S3 or R2 objects into a local scratch directory (RAM-backed or tmpfs), runs all Chisel operations against that directory through the normal chisel-core path, then flushes changed objects back on completion.
The agent would interact exclusively with Chisel โ path confinement, atomic patching, and shell tooling all behave identically. S3/R2 would serve purely as the persistence layer, transparent to the model.
Open problems before this is viable: concurrent writes to the same object need optimistic locking (ETag-based check-and-swap on flush), and a native cap-std equivalent does not exist for object storage, so confinement must be enforced at the scratch directory level rather than the storage layer. The core shell_exec whitelist also assumes a Unix filesystem and would require the object to be fully materialized locally before any command runs.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.