Refactor and improve documentation, add examples
This commit is contained in:
51
docs/README.md
Normal file
51
docs/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Fence Documentation
|
||||
|
||||
Fence is a sandboxing tool that restricts **network** and **filesystem** access for arbitrary commands. It's most useful for running semi-trusted code (package installs, build scripts, CI jobs, unfamiliar repos) with controlled side effects.
|
||||
|
||||
## Getting Started
|
||||
|
||||
- **[Quickstart](quickstart.md)** - Install fence and run your first sandboxed command in 5 minutes
|
||||
- **[Why Fence](why-fence.md)** - What problem it solves (and what it doesn't)
|
||||
|
||||
## Guides
|
||||
|
||||
- **[Concepts](concepts.md)** - Mental model: OS sandbox + local proxies + config
|
||||
- **[Troubleshooting](troubleshooting.md)** - Common failure modes and fixes
|
||||
- **[Using Fence with AI Agents](agents.md)** - Defense-in-depth and policy standardization
|
||||
- **[Recipes](recipes/README.md)** - Common workflows (npm/pip/git/CI)
|
||||
- **[Config Templates](templates/)** - Copy/paste templates you can start from
|
||||
|
||||
## Reference
|
||||
|
||||
- [README](../README.md) - CLI usage + configuration reference
|
||||
- [Architecture](../ARCHITECTURE.md) - How fence works under the hood
|
||||
- [Security Model](security-model.md) - Threat model, guarantees, and limitations
|
||||
- [Security Policy](../SECURITY.md) - Vulnerability reporting policy
|
||||
|
||||
## Examples
|
||||
|
||||
See [`examples/`](../examples/README.md) for runnable demos.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Common commands
|
||||
|
||||
```bash
|
||||
# Block all network (default)
|
||||
fence <command>
|
||||
|
||||
# Use custom config
|
||||
fence --settings ./fence.json <command>
|
||||
|
||||
# Debug mode (verbose output)
|
||||
fence -d <command>
|
||||
|
||||
# Monitor mode (show blocked requests)
|
||||
fence -m <command>
|
||||
|
||||
# Expose port for servers
|
||||
fence -p 3000 <command>
|
||||
|
||||
# Run shell command
|
||||
fence -c "echo hello && ls"
|
||||
```
|
||||
47
docs/agents.md
Normal file
47
docs/agents.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Using Fence with AI Agents
|
||||
|
||||
Many popular coding agents already include sandboxing. Fence can still be useful when you want a **tool-agnostic** policy layer that works the same way across:
|
||||
|
||||
- local developer machines
|
||||
- CI jobs
|
||||
- custom/internal agents or automation scripts
|
||||
- different agent products (as defense-in-depth)
|
||||
|
||||
## Recommended approach
|
||||
|
||||
Treat an agent as "semi-trusted automation":
|
||||
|
||||
- **Restrict writes** to the workspace (and maybe `/tmp`)
|
||||
- **Allowlist only the network destinations** you actually need
|
||||
- Use `-m` (monitor mode) to audit blocked attempts and tighten policy
|
||||
|
||||
Fence can also reduce the risk of running agents with fewer interactive permission prompts (e.g. "skip permissions"), **as long as your Fence config tightly scopes writes and outbound destinations**. It's defense-in-depth, not a substitute for the agent's own safeguards.
|
||||
|
||||
## Example: API-only agent
|
||||
|
||||
```json
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["api.openai.com", "api.anthropic.com"]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": ["."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
fence --settings ./fence.json <agent-command>
|
||||
```
|
||||
|
||||
## Protecting your environment
|
||||
|
||||
Fence includes additional "dangerous file protection (writes blocked regardless of config) to reduce persistence and environment-tampering vectors like:
|
||||
|
||||
- `.git/hooks/*`
|
||||
- shell startup files (`.zshrc`, `.bashrc`, etc.)
|
||||
- some editor/tool config directories
|
||||
|
||||
See `ARCHITECTURE.md` for the full list and rationale.
|
||||
56
docs/concepts.md
Normal file
56
docs/concepts.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Concepts
|
||||
|
||||
Fence combines two ideas:
|
||||
|
||||
1. **An OS sandbox** to enforce "no direct network" and restrict filesystem operations.
|
||||
2. **Local filtering proxies** (HTTP + SOCKS5) to selectively allow outbound traffic by domain.
|
||||
|
||||
## Network model
|
||||
|
||||
By default, fence blocks all outbound network access.
|
||||
|
||||
When you allow domains, fence:
|
||||
|
||||
- Starts local HTTP and SOCKS5 proxies
|
||||
- Sets proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`)
|
||||
- Allows the sandboxed process to connect only to the local proxies
|
||||
- Filters outbound connections by **destination domain**
|
||||
|
||||
### Localhost controls
|
||||
|
||||
- `allowLocalBinding`: lets a sandboxed process *listen* on local ports (e.g. dev servers).
|
||||
- `allowLocalOutbound`: lets a sandboxed process connect to `localhost` services (e.g. Redis/Postgres on your machine).
|
||||
- `-p/--port`: exposes inbound ports so things outside the sandbox can reach your server.
|
||||
|
||||
These are separate on purpose. A typical safe default for dev servers is:
|
||||
|
||||
- allow binding + expose just the needed port(s)
|
||||
- disallow localhost outbound unless you explicitly need it
|
||||
|
||||
## Filesystem model
|
||||
|
||||
Fence is designed around "read mostly, write narrowly":
|
||||
|
||||
- **Reads**: allowed by default (you can block specific paths via `denyRead`).
|
||||
- **Writes**: denied by default (you must opt-in with `allowWrite`).
|
||||
- **denyWrite**: overrides `allowWrite` (useful for protecting secrets and dangerous files).
|
||||
|
||||
Fence also protects some dangerous targets regardless of config (e.g. shell startup files and git hooks). See `ARCHITECTURE.md` for the full list.
|
||||
|
||||
## Debug vs Monitor mode
|
||||
|
||||
- `-d/--debug`: verbose output (proxy activity, filter decisions, sandbox command details).
|
||||
- `-m/--monitor`: show blocked requests/violations only (great for auditing and policy tuning).
|
||||
|
||||
Workflow tip:
|
||||
|
||||
1. Start restrictive.
|
||||
2. Run with `-m` to see what gets blocked.
|
||||
3. Add the minimum domains/paths required.
|
||||
|
||||
## Platform notes
|
||||
|
||||
- **macOS**: uses `sandbox-exec` with generated Seatbelt profiles.
|
||||
- **Linux**: uses `bubblewrap` for namespaces + `socat` bridges to connect the isolated network namespace to host-side proxies.
|
||||
|
||||
If you want the under-the-hood view, see [Architecture](../ARCHITECTURE.md).
|
||||
52
docs/configuration.md
Normal file
52
docs/configuration.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Configuration
|
||||
|
||||
Fence reads settings from `~/.fence.json` by default (or pass `--settings ./fence.json`).
|
||||
|
||||
Example config:
|
||||
|
||||
```json
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["github.com", "*.npmjs.org", "registry.yarnpkg.com"],
|
||||
"deniedDomains": ["evil.com"]
|
||||
},
|
||||
"filesystem": {
|
||||
"denyRead": ["/etc/passwd"],
|
||||
"allowWrite": [".", "/tmp"],
|
||||
"denyWrite": [".git/hooks"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network Configuration
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `allowedDomains` | List of allowed domains. Supports wildcards like `*.example.com` |
|
||||
| `deniedDomains` | List of denied domains (checked before allowed) |
|
||||
| `allowUnixSockets` | List of allowed Unix socket paths (macOS) |
|
||||
| `allowAllUnixSockets` | Allow all Unix sockets |
|
||||
| `allowLocalBinding` | Allow binding to local ports |
|
||||
| `allowLocalOutbound` | Allow outbound connections to localhost, e.g., local DBs (defaults to `allowLocalBinding` if not set) |
|
||||
| `httpProxyPort` | Fixed port for HTTP proxy (default: random available port) |
|
||||
| `socksProxyPort` | Fixed port for SOCKS5 proxy (default: random available port) |
|
||||
|
||||
## Filesystem Configuration
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `denyRead` | Paths to deny reading (deny-only pattern) |
|
||||
| `allowWrite` | Paths to allow writing |
|
||||
| `denyWrite` | Paths to deny writing (takes precedence) |
|
||||
| `allowGitConfig` | Allow writes to `.git/config` files |
|
||||
|
||||
## Other Options
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `allowPty` | Allow pseudo-terminal (PTY) allocation in the sandbox (for MacOS) |
|
||||
|
||||
## See Also
|
||||
|
||||
- Config templates: [`docs/templates/`](docs/templates/)
|
||||
- Workflow guides: [`docs/recipes/`](docs/recipes/)
|
||||
128
docs/quickstart.md
Normal file
128
docs/quickstart.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Quickstart
|
||||
|
||||
## Installation
|
||||
|
||||
### From Source (recommended for now)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Use-Tusk/fence
|
||||
cd fence
|
||||
go build -o fence ./cmd/fence
|
||||
sudo mv fence /usr/local/bin/
|
||||
```
|
||||
|
||||
### Using Go Install
|
||||
|
||||
```bash
|
||||
go install github.com/Use-Tusk/fence/cmd/fence@latest
|
||||
```
|
||||
|
||||
### Linux Dependencies
|
||||
|
||||
On Linux, you also need:
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install bubblewrap socat
|
||||
|
||||
# Fedora
|
||||
sudo dnf install bubblewrap socat
|
||||
|
||||
# Arch
|
||||
sudo pacman -S bubblewrap socat
|
||||
```
|
||||
|
||||
## Verify Installation
|
||||
|
||||
```bash
|
||||
fence --version
|
||||
```
|
||||
|
||||
## Your First Sandboxed Command
|
||||
|
||||
By default, fence blocks all network access:
|
||||
|
||||
```bash
|
||||
# This will fail - network is blocked
|
||||
fence curl https://example.com
|
||||
```
|
||||
|
||||
You should see something like:
|
||||
|
||||
```text
|
||||
curl: (56) CONNECT tunnel failed, response 403
|
||||
```
|
||||
|
||||
## Allow Specific Domains
|
||||
|
||||
Create a config file at `~/.fence.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["example.com"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now try again:
|
||||
|
||||
```bash
|
||||
fence curl https://example.com
|
||||
```
|
||||
|
||||
This time it succeeds!
|
||||
|
||||
## Debug Mode
|
||||
|
||||
Use `-d` to see what's happening under the hood:
|
||||
|
||||
```bash
|
||||
fence -d curl https://example.com
|
||||
```
|
||||
|
||||
This shows:
|
||||
|
||||
- The sandbox command being run
|
||||
- Proxy activity (allowed/blocked requests)
|
||||
- Filter rule matches
|
||||
|
||||
## Monitor Mode
|
||||
|
||||
Use `-m` to see only violations and blocked requests:
|
||||
|
||||
```bash
|
||||
fence -m npm install
|
||||
```
|
||||
|
||||
This is useful for:
|
||||
|
||||
- Auditing what a command tries to access
|
||||
- Debugging why something isn't working
|
||||
- Understanding a package's network behavior
|
||||
|
||||
## Running Shell Commands
|
||||
|
||||
Use `-c` to run compound commands:
|
||||
|
||||
```bash
|
||||
fence -c "echo hello && ls -la"
|
||||
```
|
||||
|
||||
## Expose Ports for Servers
|
||||
|
||||
If you're running a server that needs to accept connections:
|
||||
|
||||
```bash
|
||||
fence -p 3000 -c "npm run dev"
|
||||
```
|
||||
|
||||
This allows external connections to port 3000 while keeping outbound network restricted.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Read **[Why Fence](why-fence.md)** to understand when fence is a good fit (and when it isn't).
|
||||
- Learn the mental model in **[Concepts](concepts.md)**.
|
||||
- Use **[Troubleshooting](troubleshooting.md)** if something is blocked unexpectedly.
|
||||
- Start from copy/paste configs in **[`docs/templates/`](templates/README.md)**.
|
||||
- Follow workflow-specific guides in **[Recipes](recipes/README.md)** (npm/pip/git/CI).
|
||||
20
docs/recipes/README.md
Normal file
20
docs/recipes/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Recipes
|
||||
|
||||
These are "cookbook" guides for common workflows. Most follow the same loop:
|
||||
|
||||
1. Start with a restrictive config
|
||||
2. Run with monitor mode (`-m`) to see what gets blocked
|
||||
3. Allow the minimum domains/paths needed
|
||||
|
||||
## Package installs
|
||||
|
||||
- [npm install](npm-install.md)
|
||||
- [pip / poetry](pip-poetry.md)
|
||||
|
||||
## Git / fetching code
|
||||
|
||||
- [git clone / git fetch](git-clone.md)
|
||||
|
||||
## CI
|
||||
|
||||
- [CI jobs](ci.md)
|
||||
36
docs/recipes/ci.md
Normal file
36
docs/recipes/ci.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Recipe: CI jobs
|
||||
|
||||
Goal: make CI steps safer by default: minimal egress and controlled writes.
|
||||
|
||||
## Suggested baseline
|
||||
|
||||
```json
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": []
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": [".", "/tmp"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
fence --settings ./fence.json -c "make test"
|
||||
```
|
||||
|
||||
## Add only what you need
|
||||
|
||||
Use monitor mode to discover what a job tries to reach:
|
||||
|
||||
```bash
|
||||
fence -m --settings ./fence.json -c "make test"
|
||||
```
|
||||
|
||||
Then allowlist only:
|
||||
|
||||
- your artifact/cache endpoints
|
||||
- the minimum package registries required
|
||||
- any internal services the job must access
|
||||
32
docs/recipes/git-clone.md
Normal file
32
docs/recipes/git-clone.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Recipe: `git clone` / `git fetch`
|
||||
|
||||
Goal: allow fetching code from a limited set of hosts.
|
||||
|
||||
## HTTPS clone (GitHub example)
|
||||
|
||||
```json
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["github.com", "api.github.com", "codeload.github.com"]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": ["."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
fence --settings ./fence.json git clone https://github.com/OWNER/REPO.git
|
||||
```
|
||||
|
||||
## SSH clone
|
||||
|
||||
SSH traffic may go through SOCKS5 (`ALL_PROXY`) depending on your git/ssh configuration.
|
||||
|
||||
If it fails, use monitor/debug mode to see what was blocked:
|
||||
|
||||
```bash
|
||||
fence -m --settings ./fence.json git clone git@github.com:OWNER/REPO.git
|
||||
```
|
||||
37
docs/recipes/npm-install.md
Normal file
37
docs/recipes/npm-install.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Recipe: `npm install`
|
||||
|
||||
Goal: allow npm to fetch packages, but block unexpected egress.
|
||||
|
||||
## Start restrictive
|
||||
|
||||
```json
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["registry.npmjs.org", "*.npmjs.org"]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": [".", "node_modules", "/tmp"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
fence --settings ./fence.json npm install
|
||||
```
|
||||
|
||||
## Iterate with monitor mode
|
||||
|
||||
If installs fail, run:
|
||||
|
||||
```bash
|
||||
fence -m --settings ./fence.json npm install
|
||||
```
|
||||
|
||||
Then add the minimum extra domains required for your workflow (private registries, GitHub tarballs, etc.).
|
||||
|
||||
Notes:
|
||||
|
||||
- If your dependencies fetch binaries during install, you may need to allow additional domains.
|
||||
- Keep allowlists narrow; prefer specific hostnames over broad wildcards.
|
||||
36
docs/recipes/pip-poetry.md
Normal file
36
docs/recipes/pip-poetry.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Recipe: `pip` / `poetry`
|
||||
|
||||
Goal: allow Python dependency fetching while keeping egress minimal.
|
||||
|
||||
## Start restrictive (PyPI)
|
||||
|
||||
```json
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["pypi.org", "files.pythonhosted.org"]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": [".", "/tmp"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
fence --settings ./fence.json pip install -r requirements.txt
|
||||
```
|
||||
|
||||
For Poetry:
|
||||
|
||||
```bash
|
||||
fence --settings ./fence.json poetry install
|
||||
```
|
||||
|
||||
## Iterate with monitor mode
|
||||
|
||||
```bash
|
||||
fence -m --settings ./fence.json poetry install
|
||||
```
|
||||
|
||||
If you use private indexes, add those domains explicitly.
|
||||
75
docs/security-model.md
Normal file
75
docs/security-model.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Security Model
|
||||
|
||||
Fence is intended as **defense-in-depth** for running semi-trusted commands with reduced side effects (package installs, build scripts, CI jobs, unfamiliar repos).
|
||||
|
||||
It is **not** designed to be a strong isolation boundary against actively malicious code that is attempting to escape.
|
||||
|
||||
## Threat model (what Fence helps with)
|
||||
|
||||
Fence is useful when you want to reduce risk from:
|
||||
|
||||
- Supply-chain scripts that unexpectedly call out to the network
|
||||
- Tools that write broadly across your filesystem
|
||||
- Accidental leakage of secrets via "phone home" behavior
|
||||
- Unfamiliar repos that run surprising commands during install/build/test
|
||||
|
||||
## What Fence enforces
|
||||
|
||||
### Network
|
||||
|
||||
- **Default deny**: outbound network is blocked unless explicitly allowed.
|
||||
- **Allowlisting by domain**: you can specify `allowedDomains` (with wildcard support like `*.example.com`).
|
||||
- **Localhost controls**: inbound binding and localhost outbound are separately controlled.
|
||||
|
||||
Important: domain filtering does **not** inspect content. If you allow a domain, code can exfiltrate via that domain.
|
||||
|
||||
#### How allowlisting works (important nuance)
|
||||
|
||||
Fence combines **OS-level enforcement** with **proxy-based allowlisting**:
|
||||
|
||||
- The OS sandbox / network namespace is expected to block **direct outbound** connections.
|
||||
- Domain allowlisting happens via local HTTP/SOCKS proxies and proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`).
|
||||
|
||||
If a program does not use proxy env vars (or uses a custom protocol/stack), it may **not benefit from domain allowlisting**. In that case it typically fails with connection errors rather than being "selectively allowed."
|
||||
|
||||
Localhost is separate from "external domains":
|
||||
|
||||
- `allowLocalOutbound=false` can intentionally block connections to local services like Redis on `127.0.0.1:6379` (see the dev-server example).
|
||||
|
||||
### Filesystem
|
||||
|
||||
- **Writes are denied by default**; you must opt in with `allowWrite`.
|
||||
- **denyWrite** can block specific files/patterns even if the parent directory is writable.
|
||||
- **denyRead** can block reads from sensitive paths.
|
||||
- Fence includes an internal list of always-protected targets (e.g. shell configs, git hooks) to reduce common persistence vectors.
|
||||
|
||||
## Visibility / auditing
|
||||
|
||||
- `-m/--monitor` helps you discover what a command *tries* to access (blocked only).
|
||||
- `-d/--debug` shows more detail to understand why something was blocked.
|
||||
|
||||
## Limitations (what Fence does NOT try to solve)
|
||||
|
||||
- **Hostile code containment**: assume determined attackers may escape via kernel/OS vulnerabilities.
|
||||
- **Resource limits**: CPU, memory, disk, fork bombs, etc. are out of scope.
|
||||
- **Content-based controls**: Fence does not block data exfiltration to *allowed* destinations.
|
||||
- **Proxy limitations / protocol edge cases**: some programs may not respect proxy environment variables, so they won't get domain allowlisting unless you configure them to use a proxy (e.g. Node.js `http`/`https` without a proxy-aware client).
|
||||
|
||||
### Practical examples of proxy limitations
|
||||
|
||||
The proxy approach works well for many tools (curl, wget, git, npm, pip), but **not by default** for some stacks:
|
||||
|
||||
- Node.js native `http`/`https` (use a proxy-aware client, e.g. `undici` + `ProxyAgent`)
|
||||
- Raw socket connections (custom TCP/UDP protocols)
|
||||
|
||||
Fence's OS-level sandbox is still expected to block direct outbound connections; bypassing the proxy should fail rather than silently succeeding.
|
||||
|
||||
### Domain-based filtering only
|
||||
|
||||
Fence does not inspect request content. If you allow a domain, a sandboxed process can still exfiltrate data to that domain.
|
||||
|
||||
### Not a hostile-code containment boundary
|
||||
|
||||
Fence is defense-in-depth for running semi-trusted code, not a strong isolation boundary against malware designed to escape sandboxes.
|
||||
|
||||
For implementation details (how proxies/sandboxes/bridges work), see [`ARCHITECTURE.md`](../ARCHITECTURE.md).
|
||||
18
docs/templates/README.md
vendored
Normal file
18
docs/templates/README.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Config Templates
|
||||
|
||||
This directory contains Fence config templates. They are small and meant to be copied and customized.
|
||||
|
||||
## Templates
|
||||
|
||||
- `default-deny.json`: no network allowlist; no write access (most restrictive)
|
||||
- `workspace-write.json`: allow writes in the current directory
|
||||
- `npm-install.json`: allow npm registry; allow writes to workspace/node_modules/tmp
|
||||
- `pip-install.json`: allow PyPI; allow writes to workspace/tmp
|
||||
- `local-dev-server.json`: allow binding and localhost outbound; allow writes to workspace/tmp
|
||||
- `agent-api-only.json`: allow common LLM API domains; allow writes to workspace
|
||||
|
||||
## Using a template
|
||||
|
||||
```bash
|
||||
fence --settings ./docs/templates/npm-install.json npm install
|
||||
```
|
||||
8
docs/templates/agent-api-only.json
vendored
Normal file
8
docs/templates/agent-api-only.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["api.openai.com", "api.anthropic.com"]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": ["."]
|
||||
}
|
||||
}
|
||||
8
docs/templates/default-deny.json
vendored
Normal file
8
docs/templates/default-deny.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": []
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": []
|
||||
}
|
||||
}
|
||||
9
docs/templates/local-dev-server.json
vendored
Normal file
9
docs/templates/local-dev-server.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"network": {
|
||||
"allowLocalBinding": true,
|
||||
"allowLocalOutbound": true
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": [".", "/tmp"]
|
||||
}
|
||||
}
|
||||
8
docs/templates/npm-install.json
vendored
Normal file
8
docs/templates/npm-install.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["registry.npmjs.org", "*.npmjs.org"]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": [".", "node_modules", "/tmp"]
|
||||
}
|
||||
}
|
||||
8
docs/templates/pip-install.json
vendored
Normal file
8
docs/templates/pip-install.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"network": {
|
||||
"allowedDomains": ["pypi.org", "files.pythonhosted.org"]
|
||||
},
|
||||
"filesystem": {
|
||||
"allowWrite": [".", "/tmp"]
|
||||
}
|
||||
}
|
||||
5
docs/templates/workspace-write.json
vendored
Normal file
5
docs/templates/workspace-write.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"filesystem": {
|
||||
"allowWrite": ["."]
|
||||
}
|
||||
}
|
||||
79
docs/troubleshooting.md
Normal file
79
docs/troubleshooting.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Troubleshooting
|
||||
|
||||
## "curl: (56) CONNECT tunnel failed, response 403"
|
||||
|
||||
This usually means:
|
||||
|
||||
- the process tried to reach a domain that is **not allowed**, and
|
||||
- the request went through fence's HTTP proxy, which returned `403`.
|
||||
|
||||
Fix:
|
||||
|
||||
- Run with monitor mode to see what was blocked:
|
||||
- `fence -m <command>`
|
||||
- Add the required destination(s) to `network.allowedDomains`.
|
||||
|
||||
## "It works outside fence but not inside"
|
||||
|
||||
Start with:
|
||||
|
||||
- `fence -m <command>` to see what's being denied
|
||||
- `fence -d <command>` to see full proxy and sandbox detail
|
||||
|
||||
Common causes:
|
||||
|
||||
- Missing `allowedDomains`
|
||||
- A tool attempting direct sockets that don't respect proxy environment variables
|
||||
- Localhost outbound blocked (DB/cache on `127.0.0.1`)
|
||||
- Writes blocked (you didn't include a directory in `filesystem.allowWrite`)
|
||||
|
||||
## Node.js HTTP(S) doesn't use proxy env vars by default
|
||||
|
||||
Node's built-in `http`/`https` modules ignore `HTTP_PROXY`/`HTTPS_PROXY`.
|
||||
|
||||
If your Node code makes outbound HTTP(S) requests, use a proxy-aware client.
|
||||
For example with `undici`:
|
||||
|
||||
```javascript
|
||||
import { ProxyAgent, fetch } from "undici";
|
||||
|
||||
const proxyUrl = process.env.HTTPS_PROXY;
|
||||
const response = await fetch(url, {
|
||||
dispatcher: new ProxyAgent(proxyUrl),
|
||||
});
|
||||
```
|
||||
|
||||
Fence's OS-level sandbox should still block direct connections; the above makes your requests go through the filtering proxy so allowlisting works as intended.
|
||||
|
||||
## Local services (Redis/Postgres/etc.) fail inside the sandbox
|
||||
|
||||
If your process needs to connect to `localhost` services, set:
|
||||
|
||||
```json
|
||||
{
|
||||
"network": { "allowLocalOutbound": true }
|
||||
}
|
||||
```
|
||||
|
||||
If you're running a server inside the sandbox that must accept connections:
|
||||
|
||||
- set `network.allowLocalBinding: true` (to bind)
|
||||
- use `-p <port>` (to expose inbound port(s))
|
||||
|
||||
## "Permission denied" on file writes
|
||||
|
||||
Writes are denied by default.
|
||||
|
||||
- Add the minimum required writable directories to `filesystem.allowWrite`.
|
||||
- Protect sensitive targets with `filesystem.denyWrite` (and note fence protects some targets regardless).
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"filesystem": {
|
||||
"allowWrite": [".", "/tmp"],
|
||||
"denyWrite": [".env", "*.key"]
|
||||
}
|
||||
}
|
||||
```
|
||||
40
docs/why-fence.md
Normal file
40
docs/why-fence.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Why Fence?
|
||||
|
||||
Fence exists to reduce the blast radius of running commands you don't fully trust (or don't fully understand yet).
|
||||
|
||||
Common situations:
|
||||
|
||||
- Running `npm install`, `pip install`, or `cargo build` in an unfamiliar repo
|
||||
- Executing build scripts or test runners that can read/write broadly and make network calls
|
||||
- Running CI jobs where you want **default-deny egress** and **tightly scoped writes**
|
||||
- Auditing what a command *tries* to do before you let it do it
|
||||
|
||||
Fence is intentionally simple: it focuses on **network allowlisting** (by domain) and **filesystem write restrictions** (by path), wrapped in a pragmatic OS sandbox (macOS `sandbox-exec`, Linux `bubblewrap`).
|
||||
|
||||
## What problem does it solve?
|
||||
|
||||
Fence helps you answer: "What can this command touch?"
|
||||
|
||||
- **Network**: block all outbound by default; then allow only the domains you choose.
|
||||
- **Filesystem**: default-deny writes; then allow writes only where you choose (and deny sensitive writes regardless).
|
||||
- **Visibility**: monitor blocked requests/violations (`-m`) to iteratively tighten or expand policy.
|
||||
|
||||
This is especially useful for supply-chain risk and "unknown repo" workflows where you want a safer default than "run it and hope".
|
||||
|
||||
## When Fence is useful even if tools already sandbox
|
||||
|
||||
Some coding agents and platforms ship sandboxing (Seatbelt/Landlock/etc.). Fence still provides value when you want:
|
||||
|
||||
- **Tool-agnostic policy**: apply the same rules to any command, not only inside one agent.
|
||||
- **Standardization**: commit/review a config once, use it across developers and CI.
|
||||
- **Defense-in-depth**: wrap an agent (or its subprocesses) with an additional layer and clearer audit signals.
|
||||
- **Practical allowlisting**: start with default-deny egress and use `-m` to discover what domains a workflow actually needs.
|
||||
|
||||
## Non-goals
|
||||
|
||||
Fence is **not** a hardened containment boundary for actively malicious code.
|
||||
|
||||
- It does **not** attempt to prevent resource exhaustion (CPU/RAM/disk), timing attacks, or kernel-level escapes.
|
||||
- Domain allowlisting is not content inspection: if you allow a domain, code can exfiltrate via that domain.
|
||||
|
||||
For details, see [Security Model](security-model.md).
|
||||
Reference in New Issue
Block a user