Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div align="center">
<img src="docs/banner.png" alt="AiGate" width="400">
<p>
<img src="https://img.shields.io/badge/Go-1.24%2B-00ADD8?style=flat-square&logo=go" alt="Go Version">
<img src="https://img.shields.io/badge/Go-1.25%2B-00ADD8?style=flat-square&logo=go" alt="Go Version">
<img src="https://img.shields.io/badge/OS-Linux%20%7C%20macOS%20%7C%20WSL-darkblue?style=flat-square" alt="OS Support">
<img src="https://img.shields.io/badge/License-MIT-green?style=flat-square" alt="License">
</p>
Expand Down Expand Up @@ -53,7 +53,7 @@ AI coding tools rely on application-level permission systems that can be bypasse
- **Network isolation** - `bwrap --unshare-net` + `slirp4netns` + `iptables` (+ `ip6tables` for IPv6 when available) restrict egress to allowed domains (Linux)
- **Command blocking** - Deny execution of dangerous commands (curl, wget, ssh)
- **Output masking** - Redact secrets (API keys, tokens) from stdout/stderr before they reach the terminal
- **Resource limits** - cgroups v2 enforce memory, CPU, PID limits (Linux)
- **Audit log + dashboard** - Every run and blocked command is recorded to `~/.aigate/audit.jsonl`; `aigate serve` exposes a local web dashboard over it
- **Tool-agnostic** - Works with any AI tool: Claude Code, Cursor, Copilot, Aider
- **Sensible defaults** - Ships with deny rules for .env, secrets/, .ssh/, *.pem, etc.
- **Project-level config** - `.aigate.yaml` extends global rules per project
Expand All @@ -79,6 +79,7 @@ aigate deny net --except api.anthropic.com # Restrict network
aigate allow read .env # Remove a deny rule
aigate run -- claude # Run AI tool in sandbox
aigate status # Show current rules
aigate serve # Local web dashboard over the audit log
aigate help-ai # Show AI-friendly usage examples
aigate reset --force # Remove everything
```
Expand Down
25 changes: 18 additions & 7 deletions actions/help_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ SETUP (one-time)
sudo aigate setup # Create OS group "ai-agents" and user "ai-runner"
aigate init # Create default config at ~/.aigate/config.yaml
aigate init --force # Re-create config (overwrites existing)
aigate doctor # Check prerequisites and active isolation mode

FILE RESTRICTIONS (deny read)
aigate deny read .env # Block a single file
Expand Down Expand Up @@ -53,6 +54,14 @@ RUNNING SANDBOXED
CHECKING STATUS
aigate status # Show all rules, group/user, limits

DASHBOARD & AUDIT LOG
Every run and blocked command is appended to ~/.aigate/audit.jsonl (JSON Lines).
Events: run_started (rule counts) and blocked (matched deny_exec rule).

tail -f ~/.aigate/audit.jsonl # Follow events as plain text
aigate serve # Local web dashboard → http://127.0.0.1:8080
aigate serve --addr 127.0.0.1:9000 # Custom address (or AIGATE_ADDR env var)

CONFIGURATION
Global config: ~/.aigate/config.yaml
Project config: .aigate.yaml (in project root, merged with global)
Expand Down Expand Up @@ -90,6 +99,7 @@ CONFIGURATION
- openai
- anthropic
- aws_key
- aws_secret
- github
- bearer

Expand All @@ -116,13 +126,14 @@ OUTPUT MASKING (mask_stdout)
addition to kernel-level sandbox protections (defense-in-depth).

Built-in presets:
openai sk-... / sk-proj-... → sk-***
anthropic sk-ant-... → sk-ant-***
aws_key AKIA... (access key ID) → AKIA***
github ghp_, gho_, ghu_, ghs_, ghr_ → ghp_***
bearer Bearer <token> → Bearer ***
openai sk-... / sk-proj-... → sk-***
anthropic sk-ant-... → sk-ant-***
aws_key AKIA... (access key ID) → AKIA***
aws_secret AWS_SECRET_ACCESS_KEY=... → AWS_SECRET_ACCESS_KEY=***
github ghp_, gho_, ghu_, ghs_, ghr_ → ghp_***
bearer Bearer <token> → Bearer ***

All 5 presets are enabled by default (aigate init).
All 6 presets are enabled by default (aigate init).

Pattern options:
regex RE2-compatible regular expression (required)
Expand All @@ -146,7 +157,7 @@ WHAT THE AI AGENT SEES INSIDE THE SANDBOX
[aigate] deny_read: .env, secrets/, *.pem
[aigate] deny_exec: curl, wget, ssh
[aigate] allow_net: api.anthropic.com (all other outbound connections will be blocked)
[aigate] mask_stdout: openai, anthropic, aws_key, github, bearer
[aigate] mask_stdout: openai, anthropic, aws_key, aws_secret, github, bearer

Denied files contain a marker instead of their content:
[aigate] access denied: this file is protected by sandbox policy. See /tmp/.aigate-policy for all active restrictions.
Expand Down
21 changes: 17 additions & 4 deletions docs/AI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,40 @@ domain/ Pure data structures

services/ Core business logic
platform.go Platform interface + Executor interface + resolvePatterns
platform_linux.go Linux: setfacl, groupadd/useradd, RunSandboxed dispatch
platform_linux.go Linux: setfacl, groupadd/useradd, RunSandboxed dispatch, splitDNSByFamily
platform_linux_bwrap.go Linux bwrap path: buildBwrapArgs, runWithBwrap, runWithBwrapNetFilter
platform_darwin.go macOS: chmod +a, dscl, sandbox-exec
config_service.go Config load/save/merge (global + project)
config_service.go Config load/save/merge (global + project), InitDefaultConfig
rule_service.go Rule CRUD (add/remove/list deny rules)
runner_service.go Sandboxed process launcher
masker.go MaskingWriter + builtin mask_stdout presets
audit_service.go Append/read ~/.aigate/audit.jsonl; AuditWriter wraps run output

internal/web/ Local dashboard (aigate serve)
server.go HTTP server (loopback by default)
handlers.go JSON API + page handlers over the audit log
static/, templates/ Embedded JS/CSS/HTML assets

actions/ CLI command handlers
init.go Create group, user, default config
setup.go Create OS group + user (sudo)
init.go Write default config
deny.go Add deny rules (read, exec, net subcommands)
allow.go Remove deny rules
run.go Run command inside sandbox
status.go Show current sandbox state
reset.go Remove group, user, config
doctor.go Check prerequisites and active isolation mode
help_ai.go Print AI-friendly usage reference
(serve is defined inline in main.go)

helpers/ Logging and error types
logger.go zerolog console logger
errors.go Sentinel errors

integration/ End-to-end CLI tests
cli_test.go Build binary, run real commands
cli_test.go Build binary, run real commands
sandbox_test.go Sandbox behaviour end-to-end
sandbox_escape_test.go Adversarial escape tests (-short skips them)
```

## Key Design Decisions
Expand All @@ -47,6 +59,7 @@ integration/ End-to-end CLI tests
- **No CGO**: All platform operations use `exec.Command` to call system utilities (setfacl, groupadd, dscl, chmod, bwrap, slirp4netns, iptables/ip6tables).
- **IPv6 sandbox is opt-in by capability detection**: `ipv6SandboxSupported()` requires both kernel v6 enabled and `ip6tables` on PATH. Either missing → sandbox runs IPv4-only. Partial v6 (NAT but no filter) is refused — it would silently bypass `allow_net`.
- **Config merging**: Global config (`~/.aigate/config.yaml`) + project config (`.aigate.yaml`) merge with project extending global.
- **Audit log is append-only JSON Lines**: `AuditService` writes `run_started` / `blocked` events to `~/.aigate/audit.jsonl` under a mutex. `aigate serve` (`internal/web`) is a read-only dashboard over that file; it never mutates sandbox state. Keep the dashboard out of the sandbox enforcement path.

## Testing

Expand Down
50 changes: 49 additions & 1 deletion docs/user/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ curl -L https://github.com/AxeForging/aigate/releases/latest/download/aigate-dar
sudo mv aigate-darwin-arm64 /usr/local/bin/aigate
```

### From Source (Go 1.24+)
### From Source (Go 1.25+)
```sh
go install github.com/AxeForging/aigate@latest
```
Expand Down Expand Up @@ -192,6 +192,17 @@ Example output:
Isolation mode: bwrap + slirp4netns (full isolation)
```

### serve

Run a local web dashboard over the audit log. Read-only, binds to loopback by default — nothing is exposed off-host.

```sh
aigate serve # http://127.0.0.1:8080
aigate serve --addr 127.0.0.1:9000 # custom address (or AIGATE_ADDR env var)
```

The dashboard shows live counters and a timeline of `run_started` and `blocked` events drawn from `~/.aigate/audit.jsonl` (see [Audit log](#audit-log)).

### reset

Remove everything (group, user, config):
Expand All @@ -217,27 +228,49 @@ deny_read:
- "~/.ssh/"
- "*.pem"
- "*.key"
- "*.p12"
- "~/.aws/"
- "~/.gcloud/"
- "~/.kube/config"
- "~/.npmrc"
- "~/.pypirc"
- "terraform.tfstate"
- "*.tfvars"
deny_exec:
- "curl"
- "wget"
- "nc"
- "ncat"
- "netcat"
- "ssh"
- "scp"
- "rsync"
- "ftp"
- "kubectl delete"
- "kubectl exec"
allow_net:
- "api.anthropic.com"
- "api.openai.com"
- "api.github.com"
- "registry.npmjs.org"
- "proxy.golang.org"
resource_limits:
max_memory: "4G"
max_cpu_percent: 80
max_pids: 1000
mask_stdout:
presets:
- openai
- anthropic
- aws_key
- aws_secret
- github
- bearer
patterns:
# Generic key=value / key: value assignments for common secret names
- regex: "(?:api_?key|secret|password|passwd|token|credential)\\s*[=:]\\s*\\S+"
show_prefix: 0
case_insensitive: true
```

### Output Masking (mask_stdout)
Expand All @@ -251,6 +284,7 @@ resource_limits:
| `openai` | `sk-...` / `sk-proj-...` | `sk-***` |
| `anthropic` | `sk-ant-...` | `sk-ant-***` |
| `aws_key` | `AKIA...` (access key ID) | `AKIA***` |
| `aws_secret` | `AWS_SECRET_ACCESS_KEY=...` assignment | `AWS_SECRET_ACCESS_KEY=***` |
| `github` | `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_` | `ghp_***` |
| `bearer` | `Bearer <token>` in headers/logs | `Bearer ***` |

Expand Down Expand Up @@ -380,6 +414,20 @@ Without `bwrap`, aigate falls back to `unshare --user --map-root-user` + shell s

Resource limits (`max_memory`, `max_cpu_percent`, `max_pids`) are defined in the config but **not yet enforced**. Enforcement via cgroups v2 controllers is planned for a future release.

## Audit log

Each sandboxed run appends a line to `~/.aigate/audit.jsonl` (JSON Lines). Two kinds of events are recorded:

- `run_started` — a sandbox launched, with the command and a count of active `deny_read` / `deny_exec` / `allow_net` / masking rules
- `blocked` — a command was refused by the `deny_exec` preflight check, with the matched rule

```json
{"time":"2026-06-08T17:00:00Z","kind":"run_started","command":"claude","work_dir":"/home/me/proj","counts":{"deny_read":15,"deny_exec":11,"allow_net":5,"masking":7}}
{"time":"2026-06-08T17:01:12Z","kind":"blocked","rule":"curl","command":"curl ifconfig.me","source":"preflight"}
```

The file is plain text — `tail -f ~/.aigate/audit.jsonl` works, or run [`aigate serve`](#serve) for a live dashboard.

## Troubleshooting

### "operation requires elevated privileges"
Expand Down
Loading