diff --git a/README.md b/README.md index 92d3dae39c..9beb9a99fc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,109 @@ # egg -Sandboxed LLM code execution environment with infrastructure-level security controls. +A hardened sandbox for autonomous LLM code agents with infrastructure-enforced security controls. -**The core principle:** Security through infrastructure, not instructions. An LLM cannot bypass controls that don't exist in its environment. +> *Inspired by Andy Weir's short story "The Egg" - a contained environment where development happens before emerging into the world. The AI agent works inside the egg; when ready, it "hatches" via human review and merge.* + +## The Core Principle + +**Security through infrastructure, not instructions.** + +Behavioral controls (telling an LLM "don't do X") can be bypassed through prompt injection, jailbreaks, or model drift. egg enforces security at the infrastructure level - the agent physically cannot perform unauthorized actions because the capabilities don't exist in its environment. + +## What Makes egg Robust + +| Threat | How egg Prevents It | +|--------|---------------------| +| Agent steals credentials | Credentials never enter the sandbox - gateway injects them at request time | +| Agent pushes to main | Gateway enforces branch policies; agent can only push to `egg/*` branches | +| Agent merges its own PR | Gateway has no merge endpoint - humans must merge via GitHub UI | +| Agent exfiltrates code | Private mode restricts network to Anthropic API + private GitHub repos only | +| Agent accesses other workspaces | Each agent gets isolated git worktree; `.git/` is shadowed | +| Agent bypasses controls via git config | Sandbox has no git metadata - all git ops go through gateway | + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ egg │ +│ │ +│ ┌───────────────────────────┐ ┌───────────────────────────────┐ │ +│ │ Gateway Sidecar │ │ Sandbox Container │ │ +│ │ (Trusted) │ │ (Untrusted) │ │ +│ │ │ │ │ │ +│ │ ┌─────────────────────┐ │ HTTP │ ┌─────────────────────────┐ │ │ +│ │ │ Git/GH Policy │◄─┼──────────┼──│ git/gh wrappers │ │ │ +│ │ │ Engine │ │ API │ │ (intercept all ops) │ │ │ +│ │ └─────────────────────┘ │ │ └─────────────────────────┘ │ │ +│ │ │ │ │ │ +│ │ ┌─────────────────────┐ │ API │ ┌─────────────────────────┐ │ │ +│ │ │ Anthropic Proxy │◄─┼──────────┼──│ Claude Code │ │ │ +│ │ │ (credential inject) │ │ Proxy │ │ (ANTHROPIC_BASE_URL) │ │ │ +│ │ └─────────────────────┘ │ │ └─────────────────────────┘ │ │ +│ │ │ │ │ │ +│ │ ┌─────────────────────┐ │ HTTPS │ ┌─────────────────────────┐ │ │ +│ │ │ HTTP Proxy (Squid) │◄─┼──────────┼──│ All outbound traffic │ │ │ +│ │ │ (domain allowlist) │ │ Proxy │ │ (private mode) │ │ │ +│ │ └─────────────────────┘ │ │ └─────────────────────────┘ │ │ +│ │ │ │ │ │ +│ │ HAS: │ │ HAS: │ │ +│ │ - GitHub tokens │ │ - Workspace files only │ │ +│ │ - Anthropic API keys │ │ - Isolated git worktree │ │ +│ │ - Full network access │ │ │ │ +│ │ │ │ NO: │ │ +│ │ │ │ - Credentials (any kind) │ │ +│ │ │ │ - Git metadata (.git/) │ │ +│ │ │ │ - Direct network (private) │ │ +│ └───────────────────────────┘ └───────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +## Key Features + +### Zero Credential Exposure + +The sandbox container **never** has access to credentials: +- **Anthropic API**: Requests route through gateway via `ANTHROPIC_BASE_URL`; gateway injects API key +- **GitHub**: All git/gh commands intercepted by wrappers that call gateway API; gateway injects tokens +- **No environment leakage**: Container environment is sanitized of credential variables + +### Git Isolation via Gateway-Managed Worktrees + +Each agent session gets a fully isolated workspace: +- **Isolated worktree**: Own branch, own staging area, no visibility into other agents' work +- **Shadowed `.git/`**: Container sees empty `.git/` directory (tmpfs mount) - cannot access git metadata +- **Gateway controls all git state**: Branch creation, commits, pushes all validated by gateway + +### Granular Access Control + +The gateway enforces fine-grained policies on every operation: +- **Branch ownership**: Agents can only push to branches with configured prefix (default: `egg/`) +- **Protected branches**: Direct pushes to main/master blocked; must go through PR +- **No merge capability**: Gateway has no merge endpoint - humans must review and merge +- **Blocked operations**: Force push, `git config --global`, remote manipulation all blocked + +### Public and Private Network Modes + +| Mode | Network Access | Use Case | +|------|----------------|----------| +| **Public** | Full internet + credential-injected API calls | Open source work, package installation | +| **Private** | Anthropic API + private GitHub repos only | Confidential code, sensitive data | + +In private mode: +- All traffic routes through Squid proxy with strict domain allowlist +- Only private GitHub repositories are accessible (public repos blocked) +- WebSearch, WebFetch tools are blocked +- No package manager access (dependencies pre-installed in image) +- Data exfiltration to arbitrary endpoints is impossible + +### Comprehensive Audit Trail + +Every operation through the gateway is logged: +- Git operations (status, diff, commit, push) +- GitHub CLI operations (PR create, comment) +- Policy violations (attempted, blocked) +- Session lifecycle ## Quick Start @@ -12,53 +113,18 @@ git clone https://github.com/jwbron/egg.git cd egg ./dev setup -# Start the sandbox -egg start --config egg.yaml -``` +# Configure credentials +cp secrets.yaml.example ~/.config/egg/secrets.yaml +# Edit with your GitHub App / PAT and Anthropic credentials -## Architecture +# Start the sandbox (public mode) +egg start --config egg.yaml -``` - ┌─────────────────────────────────────────────────┐ - │ egg │ - │ │ -┌──────────────────────┼───────────────────┐ ┌───────────────────────┤ -│ Gateway Container │ │ │ Sandbox Container │ -│ │ │ │ │ -│ ┌─────────────┐ │ │ │ ┌─────────────┐ │ -│ │ REST API │◄────┼───────────────────┼────┼──│ git wrapper │ │ -│ │ Server │ │ │ │ └─────────────┘ │ -│ └─────────────┘ │ │ │ │ -│ │ │ │ ┌─────────────┐ │ -│ ┌─────────────┐ │ │ │ │ gh wrapper │ │ -│ │ Policy │ │ │ │ └─────────────┘ │ -│ │ Engine │ │ │ │ │ -│ └─────────────┘ │ │ │ ┌─────────────┐ │ -│ │ │ │ │ LLM CLI │ │ -│ ┌─────────────┐ │ │ │ │ (Claude) │ │ -│ │ HTTP Proxy │◄────┼───────────────────┼────┼──│ │ │ -│ │ (Squid) │ │ │ │ └─────────────┘ │ -│ └─────────────┘ │ │ │ │ -│ │ │ │ NO: GitHub tokens │ -└──────────────────────┼───────────────────┘ │ NO: SSH keys │ - │ │ NO: Direct network │ - │ │ YES: Workspace files │ - │ │ YES: LLM API (proxy) │ - │ └───────────────────────┤ - │ │ - └─────────────────────────────────────────────────┘ +# Start with network lockdown (private mode) +egg start --config egg.yaml --private ``` -## Features - -- **Gateway Sidecar**: All git/gh operations routed through a policy-enforcing gateway -- **Network Isolation**: Configurable domain allowlists for both public and private modes -- **Session-based Access**: Per-container session tokens for multi-container environments -- **Audit Logging**: Structured logs for all operations with correlation IDs -- **Worktree Isolation**: Per-container git worktrees preventing cross-contamination -- **Credential Isolation**: Sandbox container never has direct credential access - -## CLI Commands +## CLI Reference | Command | Description | |---------|-------------| @@ -69,40 +135,38 @@ egg start --config egg.yaml | `egg status` | Show running containers and health status | | `egg config validate` | Validate configuration files | -### CLI Flags for `egg start` +### Flags for `egg start` | Flag | Description | |------|-------------| | `--config ` | Path to egg.yaml config file (default: `./egg.yaml`) | -| `--private` | Enable private network mode (blocks all external network except Claude API) | -| `--headless` | Run in non-interactive/headless mode (for automation, CI, scripted workflows) | +| `--private` | Enable private mode (Anthropic API + private GitHub repos only) | +| `--headless` | Run in non-interactive mode (for automation, CI) | ## Documentation -- [Architecture](docs/architecture.md) -- [Configuration](docs/configuration.md) -- [Security Model](docs/security.md) -- [API Reference](docs/api.md) -- [Setup Guide](docs/setup.md) -- [Testing Guide](docs/testing.md) -- [Troubleshooting](docs/troubleshooting.md) - -## Development +- [Architecture](docs/architecture.md) - System design and component overview +- [Security Model](docs/security.md) - Threat model and security guarantees +- [Configuration](docs/configuration.md) - Configuration file reference +- [Setup Guide](docs/setup.md) - First-time setup walkthrough +- [API Reference](docs/api.md) - Gateway REST API documentation +- [Troubleshooting](docs/troubleshooting.md) - Common issues and solutions -```bash -# Set up development environment -./dev setup +### Architecture Decision Records -# Run linters (same as CI) -./dev lint +- [Git Isolation Architecture](docs/adr/git-isolation-architecture.md) - Worktree isolation design +- [Credential Injection](docs/adr/credential-injection.md) - Zero-credential sandbox design +- [Network Isolation](docs/adr/network-isolation.md) - Public/private mode implementation -# Run tests (same as CI) -./dev test +## Development -# Run full CI pipeline locally -./dev ci +```bash +./dev setup # Set up development environment +./dev lint # Run linters +./dev test # Run tests +./dev ci # Run full CI pipeline locally -# Fast mode (native, no Docker overhead) +# Fast mode (native Python, no Docker) ./dev native lint ./dev native test ``` diff --git a/docs/adr/credential-injection.md b/docs/adr/credential-injection.md new file mode 100644 index 0000000000..6324867d3e --- /dev/null +++ b/docs/adr/credential-injection.md @@ -0,0 +1,159 @@ +# ADR: Credential Injection via Gateway + +**Status:** Implemented +**Origin:** Extracted from james-in-a-box + +## Summary + +The gateway injects credentials (GitHub tokens, Anthropic API keys) at the proxy layer, ensuring the sandbox container has zero credential access. This extends the security model established in the git isolation architecture to cover all authentication. + +**Key properties:** +- **Zero credential exposure**: Container never sees API keys or OAuth tokens +- **Infrastructure enforcement**: Cannot be bypassed by prompt injection or container compromise +- **Single audit point**: All authenticated traffic logged through gateway + +## Motivation + +Without credential injection, the sandbox container would receive credentials via: +- Environment variables (`ANTHROPIC_API_KEY`, `GITHUB_TOKEN`) +- Mounted config files + +This creates security risks: +1. **Credential exposure**: If the sandbox is compromised, credentials are immediately available +2. **Exfiltration risk**: Agent could inadvertently log or transmit credentials +3. **Inconsistent model**: Different treatment of different credential types + +With credential injection, **all credentials live in the gateway**. + +## Architecture + +### Anthropic API Credential Injection + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CREDENTIAL FLOW │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ANTHROPIC_BASE_URL ┌─────────────────────┐ │ +│ │ Sandbox │ ─────────────────────────▶│ Gateway │ │ +│ │ │ http://gateway:8080 │ │ │ +│ │ Claude Code │ /v1/messages │ 1. Receive request │ │ +│ │ │ (no credentials) │ 2. Inject creds │──┼──▶ api.anthropic.com +│ │ No API key │ │ 3. Forward to API │ │ +│ │ No OAuth token │ │ │ │ +│ └─────────────────┘ │ Credentials from: │ │ +│ │ ~/.egg/secrets.yaml│ │ +│ └─────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Key Insight: ANTHROPIC_BASE_URL + +Claude Code officially supports custom API endpoints via `ANTHROPIC_BASE_URL`. This enables a clean architecture: + +- Container sets `ANTHROPIC_BASE_URL=http://gateway:8080` +- Claude Code sends requests to gateway over HTTP (internal network) +- Gateway adds credentials and forwards over HTTPS to api.anthropic.com +- **No SSL bump needed** - gateway receives plaintext, handles TLS outbound + +### GitHub Credential Injection + +Git and gh CLI commands are intercepted by wrapper scripts that call the gateway API: + +``` +Container Gateway GitHub + │ │ │ + │ git push │ │ + │ ─────────────────────────▶│ │ + │ (via wrapper, no creds) │ git push (with token) │ + │ │ ──────────────────────────▶│ + │ │ │ + │ <────────────────────── │ <─────────────────────────│ + │ result │ result │ +``` + +## Implementation + +### Gateway Proxy Endpoints + +The gateway exposes HTTP endpoints that proxy to Anthropic with credential injection: + +| Endpoint | Purpose | +|----------|---------| +| `POST /v1/messages` | Main messages API with streaming SSE support | +| `POST /v1/messages/count_tokens` | Token counting API | + +**Key implementation details:** +- Uses connection pooling for performance +- Streaming responses via SSE (no buffering) +- Header blocklist approach: forwards all headers except auth-related ones +- Full error passthrough including `x-request-id` for debugging + +### Container Configuration + +The container entrypoint: +1. Sets `ANTHROPIC_BASE_URL=http://gateway:8080` +2. Removes `ANTHROPIC_API_KEY` from environment (if present) +3. Removes `ANTHROPIC_OAUTH_TOKEN` from environment (if present) + +### Credential Storage + +Credentials are stored on the host machine in `~/.egg/secrets.yaml`: + +```yaml +secrets: + github_app: + app_id: "123456" + private_key_path: "/path/to/key.pem" + + anthropic: + # For API users + api_key: "sk-ant-xxxxxxxxxxxx" + # OR for Pro/Max subscribers + oauth_token: "oauth-xxxxxxxxxxxx" +``` + +The gateway reads credentials with mtime-based cache refresh for hot reloading. + +## Security Properties + +| Property | Mechanism | +|----------|-----------| +| Zero credential exposure | Credentials only in gateway, never in container | +| Infrastructure enforcement | Cannot bypass via instructions or config changes | +| Single audit point | All authenticated traffic logged through gateway | +| Consistent model | Same security for git and API credentials | + +### Threat Mitigations + +| Threat | Mitigation | +|--------|------------| +| Credential theft from container | Credentials never enter container | +| Credential exfiltration via logs | Gateway doesn't log credential values | +| Direct API access bypassing gateway | api.anthropic.com not in proxy allowlist | +| Prompt injection disabling controls | Gateway enforcement is infrastructure-level | + +## Authentication Types + +| Type | Source | Header Injected | +|------|--------|-----------------| +| API Key | `api_key` in secrets.yaml | `x-api-key: ` | +| OAuth Token | `oauth_token` in secrets.yaml | `Authorization: Bearer ` | +| GitHub App | `github_app` in secrets.yaml | GitHub App installation token | +| GitHub PAT | `pats` in secrets.yaml | `Authorization: token ` | + +OAuth takes precedence if both OAuth and API key are configured. + +## Benefits + +1. **Simpler**: No SSL MITM complexity for Anthropic traffic +2. **Officially supported**: Uses Claude Code's documented configuration +3. **More secure**: Credentials never in container environment +4. **Unified**: Same approach works for both public and private modes +5. **Better debugging**: HTTP traffic between container and gateway is inspectable + +## Related ADRs + +- [ADR: Git Isolation Architecture](git-isolation-architecture.md) +- [ADR: Network Isolation](network-isolation.md) diff --git a/docs/adr/git-isolation-architecture.md b/docs/adr/git-isolation-architecture.md new file mode 100644 index 0000000000..e598bb8db9 --- /dev/null +++ b/docs/adr/git-isolation-architecture.md @@ -0,0 +1,149 @@ +# ADR: Git Isolation Architecture + +**Status:** Implemented +**Origin:** Extracted from james-in-a-box + +## Summary + +This document describes how egg safely allows multiple AI agent containers to work on the same git repositories simultaneously, without risking cross-contamination or unauthorized access. + +**Core security guarantee**: An AI agent can only see and modify its own isolated workspace. It cannot access other agents' work, cannot directly push to remote repositories, and cannot see credentials. All git operations that touch the network or affect shared state go through a trusted gateway that enforces access policies. + +## Key Properties + +- **Complete isolation**: Each agent gets its own branch and working directory +- **No credential exposure**: Agents never see GitHub tokens or SSH keys +- **Enforced code review**: Agents cannot merge their own PRs - humans must review and merge +- **Crash-safe**: System recovers cleanly if an agent container crashes + +## Architecture + +### Complete Metadata Isolation + +The fundamental security property is that **agents never touch git metadata**. The container mounts only the working directory (source files), with the `.git` path shadowed by an empty tmpfs: + +``` +Container filesystem view: +/home/sandbox/repos/my-repo/ +├── src/ ← Agent can edit these files +├── tests/ ← Agent can edit these files +├── README.md ← Agent can edit this file +└── .git/ ← Empty directory (tmpfs shadow) +``` + +Without git metadata, the agent cannot: +- Discover where the repository came from +- See commit history directly +- Modify the staging area directly +- Change branch pointers +- Execute git hooks +- Access other worktrees + +### Gateway as Security Boundary + +All git operations that require metadata access go through the gateway: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Agent Container (Untrusted) │ +│ │ +│ The agent runs 'git status', which invokes the git wrapper │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Git Wrapper Script │ │ +│ │ - Intercepts all git commands │ │ +│ │ - Cannot bypass (no git metadata = native git fails) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +└──────────────────────────────│──────────────────────────────────┘ + │ HTTP API call + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Gateway Sidecar (Trusted) │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Request Validation │ │ +│ │ - Verify container identity │ │ +│ │ - Check operation against allowlist │ │ +│ │ - Validate flags (block dangerous options) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Policy Enforcement │ │ +│ │ - Branch ownership: only push to agent's own branches │ │ +│ │ - Protected branches: block direct push to main │ │ +│ │ - Merge blocking: agents cannot merge PRs │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Git Execution │ │ +│ │ - Execute in correct worktree context │ │ +│ │ - Inject credentials for network operations │ │ +│ │ - Return sanitized output to container │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Multi-Agent Isolation + +Each agent works on its own isolated branch with its own staging area: + +| Agent | Working Directory | Branch | Index (Staging) | +|-------|-------------------|--------|-----------------| +| abc123 | `/worktrees/abc123/my-repo/` | `egg/abc123/work` | Isolated | +| def456 | `/worktrees/def456/my-repo/` | `egg/def456/work` | Isolated | + +**Guarantees:** +- Agents cannot see each other's uncommitted changes +- Agents can work on different branches simultaneously +- All agents share commit history and git objects (efficient storage) +- Gateway manages worktree metadata - agents never touch it + +## Security Properties + +1. **Filesystem isolation**: Containers cannot see other containers' working directories +2. **Metadata isolation**: Containers have no access to git metadata (`.git/` contents) +3. **Credential isolation**: GitHub tokens exist only in the gateway, never in containers +4. **Operation allowlist**: Gateway only permits known-safe git operations and flags +5. **Branch ownership**: Containers can only push to branches they created +6. **Merge prevention**: Containers cannot merge PRs - humans must review and merge +7. **Audit trail**: All git operations are logged through the gateway + +## Crash Recovery + +If an agent container crashes without cleanup: + +1. **On next gateway startup**: Gateway scans for orphaned worktrees +2. **Orphan detection**: Compare worktree list against active containers +3. **Cleanup**: Remove worktrees for containers that no longer exist +4. **Branch preservation**: Committed work is preserved; only working directory removed + +## Why This Design? + +### Alternatives Considered + +**Behavioral controls only** +- Rely on instructions telling agents not to access other workspaces +- **Rejected:** Insufficient for security-critical applications + +**Full clone per container** +- Each container gets complete independent clone +- **Rejected:** Wasteful storage; complex sync requirements + +### Why Gateway-Managed Worktrees? + +The chosen approach provides: +- **Uniform architecture** across deployment environments +- **Simple security model** (no git metadata in container = no git-based attacks) +- **Efficient storage** (worktrees share git objects) +- **Fast workspace creation** (O(1) via git worktree) +- **Clean crash recovery** (gateway manages all state) + +## Related ADRs + +- [ADR: Credential Injection](credential-injection.md) +- [ADR: Network Isolation](network-isolation.md) diff --git a/docs/adr/network-isolation.md b/docs/adr/network-isolation.md new file mode 100644 index 0000000000..52959211ab --- /dev/null +++ b/docs/adr/network-isolation.md @@ -0,0 +1,278 @@ +# ADR: Network Isolation via Gateway Proxy + +**Status:** Implemented +**Origin:** Extracted from james-in-a-box + +## Summary + +The egg gateway provides two network modes for sandbox containers: + +1. **Public mode** (default): Full internet access, with Anthropic API routed through gateway for credential injection +2. **Private mode**: Complete network lockdown - only Anthropic API and private GitHub repos are accessible + +Private mode provides infrastructure-enforced security guarantees that cannot be bypassed by prompt injection or agent misbehavior. + +## Motivation + +In public mode, the sandbox container can reach arbitrary internet endpoints: +- Web search could be used for data exfiltration +- Package installation could pull malicious dependencies +- Any HTTP endpoint could receive exfiltrated code or secrets + +For truly unsupervised operation, we need infrastructure-level guarantees that the sandbox cannot communicate with unauthorized endpoints. + +## Architecture + +### Public Mode + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PUBLIC MODE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ANTHROPIC_BASE_URL ┌─────────────────────┐ │ +│ │ Sandbox │ ─────────────────────────▶│ Gateway │ │ +│ │ Container │ http://gateway:8080 │ │ │ +│ │ │ /v1/messages │ - Inject creds │──┼──▶ api.anthropic.com +│ │ Claude Code │ │ - Forward request │ │ +│ └────────┬────────┘ └─────────────────────┘ │ +│ │ │ +│ │ Direct internet │ +│ ▼ │ +│ npm, pypi, github, web search, etc. │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Private Mode + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PRIVATE MODE │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ANTHROPIC_BASE_URL ┌─────────────────────┐ │ +│ │ Sandbox │ ─────────────────────────▶│ Gateway │ │ +│ │ Container │ http://gateway:8080 │ │ │ +│ │ │ /v1/messages │ - Inject creds │──┼──▶ api.anthropic.com +│ │ Claude Code │ │ - Forward request │ │ +│ └────────┬────────┘ │ │ │ +│ │ │ Squid Proxy :3128 │──┼──▶ allowlist only +│ │ HTTPS_PROXY │ - Domain filtering │ │ +│ ▼ │ - Audit logging │ │ +│ ┌─────────────────────────────────────────▶│ │ │ +│ │ All other traffic └─────────────────────┘ │ +│ │ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Network Topology (Private Mode) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Host Machine │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ egg-isolated (internal: true) │ │ +│ │ Subnet: 172.30.0.0/24 │ │ +│ │ Gateway: NONE (no external route) │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────────┐ │ │ +│ │ │ Sandbox │ │ egg-gateway │ │ │ +│ │ │ 172.30.0.10 │◄────────────►│ 172.30.0.2 │ │ │ +│ │ │ │ REST API │ │ │ │ +│ │ │ NO EXTERNAL │ Port 9847 │ │ │ │ +│ │ │ ROUTE │ │ │ │ │ +│ │ └─────────────┘ └────────┬────────┘ │ │ +│ │ │ │ │ +│ └──────────────────────────────────────────│───────────────┘ │ +│ │ │ +│ ┌──────────────────────────────────────────│───────────────┐ │ +│ │ egg-external (bridge) │ │ │ +│ │ Subnet: 172.31.0.0/24 │ │ │ +│ │ │ │ │ +│ │ ┌───────────┴─────────┐ │ │ +│ │ │ egg-gateway │ │ │ +│ │ │ 172.31.0.2 │ │ │ +│ │ │ │ │ │ +│ │ │ CAN REACH: │ │ │ +│ │ │ - api.anthropic.com│ │ │ +│ │ │ - github.com │ │ │ +│ │ │ - api.github.com │ │ │ +│ │ │ (via proxy filter) │ │ │ +│ │ └──────────┬──────────┘ │ │ +│ │ │ │ │ +│ └─────────────────────────────────────────│────────────────┘ │ +│ │ │ +│ ▼ │ +│ Internet │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Key property:** Docker's `internal: true` network has no gateway to the outside world. The sandbox physically cannot route packets to the internet—there's no route in its network namespace. + +## Domain Allowlist (Private Mode) + +The gateway maintains a strict allowlist of permitted domains: + +| Domain | Purpose | Required For | +|--------|---------|--------------| +| `api.anthropic.com` | Claude API | Claude Code operation | +| `api.github.com` | GitHub REST API | PR creation, issue management | +| `github.com` | Git operations | Push, fetch, clone | +| `raw.githubusercontent.com` | GitHub raw content | File downloads | +| `objects.githubusercontent.com` | Release assets | Binary downloads | +| `codeload.github.com` | Archive downloads | Zip/tarball downloads | +| `uploads.github.com` | File uploads | Release asset uploads | +| `avatars.githubusercontent.com` | User avatars | GitHub UI elements | + +**Allowlist Properties:** +- **Exhaustive:** Only listed domains are permitted; all others blocked +- **Enforced at proxy:** Squid proxy validates destination before forwarding +- **SNI-based validation:** For HTTPS, the proxy inspects Server Name Indication (SNI) in TLS ClientHello—no MITM decryption required + +## What Gets Blocked (Private Mode) + +| Category | Examples | Impact | Mitigation | +|----------|----------|--------|------------| +| Package managers | pypi.org, npmjs.com | Can't install new packages | Pre-install required packages in image | +| Web search | google.com, bing.com | Can't search web | Use GitHub search, local docs | +| Documentation | docs.python.org | Can't fetch docs | Bundle offline docs in image | +| Arbitrary APIs | any other endpoint | Can't exfiltrate data | **This is the security goal** | + +### Claude Code Tools in Private Mode + +| Tool | Status | Reason | +|------|--------|--------| +| `WebFetch` | ❌ Blocked | Cannot reach arbitrary URLs | +| `WebSearch` | ❌ Blocked | Cannot reach search engines | +| `Bash` (curl, wget) | ❌ Blocked | Cannot reach arbitrary endpoints | +| GitHub MCP tools | ✓ Works | Routed through gateway | + +This is an **intentional limitation**. For tasks requiring web research, use public mode or pre-populate context. + +## Container Configuration (Private Mode) + +### Environment Variables + +```bash +# Proxy settings - all HTTP/HTTPS traffic through gateway +HTTP_PROXY=http://gateway:3128 +HTTPS_PROXY=http://gateway:3128 +http_proxy=http://gateway:3128 +https_proxy=http://gateway:3128 +NO_PROXY=localhost,127.0.0.1,gateway,egg-gateway +no_proxy=localhost,127.0.0.1,gateway,egg-gateway + +# Network mode indicator +EGG_NETWORK_MODE=private +``` + +### DNS Resolution + +The sandbox cannot perform external DNS lookups in private mode: + +```yaml +# Sandbox container +dns: [] # No DNS servers configured +extra_hosts: + - "gateway:172.30.0.2" # Static entry for gateway +``` + +DNS resolution is handled by the proxy: +1. Sandbox sends hostname in CONNECT request +2. Gateway/Squid resolves hostname internally +3. Squid validates hostname against allowlist **before** resolving DNS + +## Squid Proxy Configuration + +```squid +# Network settings +http_port 3128 ssl-bump \ + cert=/etc/squid/squid-ca.pem \ + generate-host-certificates=on \ + dynamic_cert_mem_cache_size=4MB + +# Access control lists +acl localnet src 172.30.0.0/24 # egg-isolated network +acl allowed_domains dstdomain "/etc/squid/allowed_domains.txt" + +# Block direct IP connections (must use hostnames) +acl direct_ip url_regex ^https?://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ +http_access deny direct_ip + +# SSL/TLS settings - peek at SNI without MITM +acl step1 at_step SslBump1 +ssl_bump peek step1 +ssl_bump splice allowed_domains +ssl_bump terminate all + +# HTTP access rules +http_access allow localnet allowed_domains +http_access deny all + +# Logging +access_log /var/log/squid/access.log squid + +# No caching needed for API calls +cache deny all +``` + +## Security Properties + +### Breakout Prevention + +| Attack Vector | Mitigation | +|---------------|------------| +| Direct IP connection | No route—internal network has no external gateway; Squid blocks IP-based URLs | +| DNS tunneling | No DNS servers configured in sandbox container | +| Proxy bypass | No alternate route exists; proxy is the only path out | +| IP-based proxy bypass | Squid `direct_ip` ACL blocks `http[s]://IP` requests | +| Learned IP from context | Even if sandbox learns an IP, requests are blocked by ACL | + +### Defense in Depth + +``` +Layer 1: Behavioral (CLAUDE.md instructions) + ↓ Can be bypassed by prompt injection +Layer 2: Credential Isolation + ↓ Sandbox has no credentials +Layer 3: Gateway Policy Enforcement + ↓ Gateway validates all operations +Layer 4: Network Isolation (this ADR) + ↓ Sandbox cannot reach unauthorized endpoints +Layer 5: Audit Logging + ↓ All traffic visible for review +Layer 6: Human Review + ↓ Human must approve all PRs +``` + +## Residual Risks (Private Mode) + +Even with network lockdown, some exfiltration vectors remain: + +| Vector | Mitigation | +|--------|------------| +| GitHub exfiltration (branch names, commit messages, PR bodies) | Audit logging, PR review, private repos | +| Claude API exfiltration (data in prompts) | Anthropic's logging | +| Supply chain (pre-installed packages) | Use pinned versions, scan images | + +**Accepted risk:** These vectors are acknowledged. Mitigations provide detection capability and data stays within controlled repositories. + +## CLI Flags + +```bash +# Default: public mode +egg start --config egg.yaml + +# Private mode: network locked down +egg start --config egg.yaml --private + +# Explicit public mode +egg start --config egg.yaml --public +``` + +## Related ADRs + +- [ADR: Git Isolation Architecture](git-isolation-architecture.md) +- [ADR: Credential Injection](credential-injection.md) diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000000..2d8709cd48 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,401 @@ +# API Reference + +This document describes the gateway REST API endpoints. + +## Base URL + +``` +http://egg-gateway:9847/api/v1 +``` + +## Authentication + +All endpoints require a session token in the Authorization header: + +``` +Authorization: Bearer +``` + +Session tokens are created via the session endpoints. + +## Endpoints + +### Health + +#### GET /api/v1/health + +Check gateway health status. + +**Request:** +```bash +curl http://egg-gateway:9847/api/v1/health +``` + +**Response:** +```json +{ + "status": "healthy", + "version": "0.1.0", + "uptime_seconds": 3600 +} +``` + +### Sessions + +#### POST /api/v1/session/create + +Create a new session for a container. + +**Request:** +```json +{ + "container_id": "abc123", + "repos": ["owner/repo1"] +} +``` + +**Response:** +```json +{ + "session_token": "eyJ...", + "expires_at": "2026-02-03T12:00:00Z" +} +``` + +#### POST /api/v1/session/validate + +Validate a session token. + +**Request:** +```json +{ + "token": "eyJ..." +} +``` + +**Response:** +```json +{ + "valid": true, + "container_id": "abc123", + "repos": ["owner/repo1"] +} +``` + +#### DELETE /api/v1/session + +End a session and clean up resources. + +**Response:** +```json +{ + "status": "deleted" +} +``` + +### Git Operations + +#### POST /api/v1/git/status + +Get git status for a repository. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo" +} +``` + +**Response:** +```json +{ + "branch": "egg/abc123/work", + "clean": false, + "staged": ["src/main.py"], + "unstaged": ["README.md"], + "untracked": ["new_file.txt"] +} +``` + +#### POST /api/v1/git/diff + +Get git diff. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "staged": false +} +``` + +**Response:** +```json +{ + "diff": "diff --git a/README.md b/README.md..." +} +``` + +#### POST /api/v1/git/add + +Stage files. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "files": ["src/main.py", "README.md"] +} +``` + +**Response:** +```json +{ + "staged": ["src/main.py", "README.md"] +} +``` + +#### POST /api/v1/git/commit + +Create a commit. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "message": "Add new feature" +} +``` + +**Response:** +```json +{ + "commit": "abc123def456", + "message": "Add new feature" +} +``` + +#### POST /api/v1/git/push + +Push to remote. Subject to branch ownership policy. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "branch": "egg/abc123/feature" +} +``` + +**Response:** +```json +{ + "pushed": true, + "branch": "egg/abc123/feature", + "remote": "origin" +} +``` + +**Error Response (Policy Violation):** +```json +{ + "error": "Branch ownership violation", + "policy": "branch_ownership", + "branch": "main", + "allowed_prefixes": ["egg/"] +} +``` + +#### POST /api/v1/git/fetch + +Fetch from remote. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo" +} +``` + +**Response:** +```json +{ + "fetched": true +} +``` + +#### POST /api/v1/git/execute + +Execute a generic git command. Commands are validated against an allowlist. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "command": "log", + "args": ["--oneline", "-5"] +} +``` + +**Response:** +```json +{ + "output": "abc123 Latest commit\ndef456 Previous commit...", + "exit_code": 0 +} +``` + +### GitHub CLI Operations + +#### POST /api/v1/gh/pr/create + +Create a pull request. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "title": "Add new feature", + "body": "Description of changes", + "base": "main", + "head": "egg/abc123/feature" +} +``` + +**Response:** +```json +{ + "number": 123, + "url": "https://github.com/owner/repo/pull/123" +} +``` + +#### POST /api/v1/gh/pr/comment + +Add a comment to a pull request. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "pr_number": 123, + "body": "Comment text" +} +``` + +**Response:** +```json +{ + "comment_id": 456, + "url": "https://github.com/owner/repo/pull/123#issuecomment-456" +} +``` + +#### POST /api/v1/gh/execute + +Execute a generic gh command. Commands are validated. + +**Request:** +```json +{ + "repo_path": "/home/sandbox/repos/my-repo", + "command": "pr", + "args": ["view", "123"] +} +``` + +**Response:** +```json +{ + "output": "Title: Add new feature\nState: OPEN...", + "exit_code": 0 +} +``` + +**Note:** `gh pr merge` is blocked. Humans must merge via GitHub UI. + +### Configuration + +#### GET /api/v1/config + +Get current configuration (sanitized, no secrets). + +**Response:** +```json +{ + "git": { + "branch_prefix": "egg/", + "protected_branches": ["main", "master"] + }, + "repositories": { + "allowed": ["owner/repo1", "owner/repo2"] + } +} +``` + +#### GET /api/v1/config/domains + +Get allowed domains (private mode). + +**Response:** +```json +{ + "allowed_domains": [ + "api.anthropic.com", + "github.com" + ] +} +``` + +## Error Responses + +All errors follow this format: + +```json +{ + "error": "Error message", + "code": "ERROR_CODE", + "details": {} +} +``` + +### Error Codes + +| Code | HTTP Status | Description | +|------|-------------|-------------| +| `UNAUTHORIZED` | 401 | Invalid or missing session token | +| `FORBIDDEN` | 403 | Policy violation | +| `NOT_FOUND` | 404 | Resource not found | +| `VALIDATION_ERROR` | 400 | Invalid request | +| `INTERNAL_ERROR` | 500 | Server error | + +### Policy Violation Errors + +```json +{ + "error": "Branch ownership violation", + "code": "FORBIDDEN", + "policy": "branch_ownership", + "details": { + "branch": "main", + "allowed_prefixes": ["egg/"] + } +} +``` + +## Rate Limiting + +The gateway enforces rate limits per session: + +| Endpoint Type | Limit | +|---------------|-------| +| Read operations | 100/minute | +| Write operations | 30/minute | +| Push operations | 10/minute | + +Rate limit headers are included in responses: +``` +X-RateLimit-Limit: 100 +X-RateLimit-Remaining: 95 +X-RateLimit-Reset: 1706875200 +``` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000000..bf0f4993fa --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,187 @@ +# Configuration + +This document describes how to configure egg. + +## Configuration Files + +egg uses two configuration files: + +| File | Purpose | Git Status | +|------|---------|------------| +| `egg.yaml` | Main configuration (policies, repositories, logging) | Can be committed | +| `secrets.yaml` | Sensitive credentials (tokens, keys) | **Never commit** | + +## egg.yaml + +The main configuration file defines policies and settings: + +```yaml +egg: + name: "my-sandbox" + + # Git policies + git: + branch_prefix: "egg/" # Branches must start with this + protected_branches: + - "main" + - "master" + allow_force_push: false + merge_blocking: true # Gateway has no merge endpoint + + # Authentication sources + auth: + sources: + - name: "bot-account" + type: "github_app" + - name: "personal" + type: "pat" + # Associate repos with auth sources + repo_auth: + "owner/repo1": "bot-account" + "owner/*": "bot-account" + + # Repository allowlist + repositories: + allowed: + - "owner/repo1" + - "owner/repo2" + - "owner/*" # Wildcard support + + # Logging + logging: + level: "INFO" # DEBUG, INFO, WARNING, ERROR + format: "json" # json or text + output: "stdout" # stdout or file path + + # Container settings + container: + mounts: + - source: "./workspace" + target: "/workspace" + read_only: false +``` + +### Configuration Options + +#### git + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `branch_prefix` | string | `"egg/"` | Required prefix for branches agents can push to | +| `protected_branches` | list | `["main", "master"]` | Branches that cannot be pushed to directly | +| `allow_force_push` | bool | `false` | Allow force push to owned branches | +| `merge_blocking` | bool | `true` | Block merge operations (humans must merge) | + +#### auth + +| Option | Type | Description | +|--------|------|-------------| +| `sources` | list | Authentication sources (name, type) | +| `repo_auth` | map | Repository to auth source mapping | + +Supported auth types: +- `github_app` - GitHub App authentication +- `pat` - Personal Access Token + +#### repositories + +| Option | Type | Description | +|--------|------|-------------| +| `allowed` | list | Repositories the agent can access | + +Supports wildcards: `owner/*` matches all repos from that owner. + +#### logging + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `level` | string | `"INFO"` | Log level | +| `format` | string | `"json"` | Output format (json/text) | +| `output` | string | `"stdout"` | Output destination | + +## secrets.yaml + +Sensitive credentials are stored separately: + +```yaml +secrets: + # GitHub App authentication + github_app: + app_id: "123456" + private_key_path: "/path/to/key.pem" + + # Personal Access Tokens + pats: + personal: "ghp_xxxxxxxxxxxx" + + # Anthropic credentials (choose one) + anthropic: + api_key: "sk-ant-xxxxxxxxxxxx" + # OR + oauth_token: "oauth-xxxxxxxxxxxx" +``` + +### Anthropic Authentication + +Choose one authentication method: + +| Method | Config Key | Use Case | +|--------|------------|----------| +| API Key | `api_key` | Anthropic API accounts, teams | +| OAuth Token | `oauth_token` | Claude Pro/Max subscriptions | + +OAuth tokens are obtained via `claude auth login` or `claude setup-token`. + +## CLI Flags + +Network mode and other runtime options are set via CLI flags: + +```bash +# Default: public mode +egg start --config egg.yaml + +# Private mode: network locked down +egg start --config egg.yaml --private + +# Headless mode: non-interactive +egg start --config egg.yaml --headless +``` + +| Flag | Description | +|------|-------------| +| `--config ` | Path to egg.yaml (default: `./egg.yaml`) | +| `--private` | Enable private network mode | +| `--headless` | Run without interactive terminal | + +## Environment Variables + +Some settings can be overridden via environment variables: + +| Variable | Description | +|----------|-------------| +| `EGG_CONFIG` | Path to egg.yaml | +| `EGG_SECRETS` | Path to secrets.yaml | +| `EGG_LOG_LEVEL` | Override log level | + +## File Locations + +| Path | Purpose | +|------|---------| +| `./egg.yaml` | Default config file location | +| `~/.config/egg/egg.yaml` | User-level config | +| `~/.config/egg/secrets.yaml` | Credentials | +| `~/.egg/sessions.json` | Session storage | + +## Validation + +Validate your configuration: + +```bash +egg config validate +``` + +This checks: +- YAML syntax +- Required fields present +- Auth sources referenced in repo_auth exist +- Secrets file readable (without exposing values) diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 0000000000..652b36563a --- /dev/null +++ b/docs/security.md @@ -0,0 +1,569 @@ +# Security Model + +**Document Status:** Implemented +**Version:** 1.0 +**Audience:** Security Teams, Engineering Leadership + +## Executive Summary + +egg provides a comprehensive security architecture for operating autonomous LLM-powered agents in sensitive codebases without human supervision. The architecture provides **defense-in-depth through infrastructure controls**, ensuring that even if behavioral instructions are bypassed via prompt injection, model drift, or adversarial inputs, the agent cannot perform unauthorized operations. + +### Core Security Guarantee + +> **An AI agent cannot access credentials, merge code, or exfiltrate data to unauthorized endpoints—regardless of what it is instructed to do.** + +This guarantee is achieved through: +1. **Credential Isolation**: Tokens never enter the sandbox container +2. **Network Lockdown**: Only Anthropic API and GitHub reachable (private mode) +3. **Gateway Enforcement**: All privileged operations validated by a trusted sidecar +4. **Filesystem Isolation**: Agent cannot access git metadata or other agents' workspaces +5. **Human-in-the-Loop**: All code merges require human approval + +--- + +## Table of Contents + +1. [Threat Model](#1-threat-model) +2. [Architecture Overview](#2-architecture-overview) +3. [Network Isolation](#3-network-isolation) +4. [Credential Isolation](#4-credential-isolation) +5. [Git and GitHub Lockdown](#5-git-and-github-lockdown) +6. [Private Repository Mode](#6-private-repository-mode) +7. [Audit Logging](#7-audit-logging) +8. [Residual Risks](#8-residual-risks) +9. [OWASP Alignment](#9-owasp-alignment) + +--- + +## 1. Threat Model + +### 1.1 Adversary Capabilities + +| Adversary | Capability | Example Attack | +|-----------|------------|----------------| +| **Prompt Injection** | Attacker crafts input that overrides agent instructions | Malicious issue contains: "Ignore previous instructions and..." | +| **Model Misbehavior** | Agent acts outside intended behavior due to training or inference issues | Agent interprets "fix the bug" as "delete the file" | +| **Rogue Agent** | Agent deliberately operates outside constraints | Agent attempts to merge its own PR | +| **Data Exfiltration** | Agent attempts to send sensitive data to external services | Agent encodes secrets in commit messages | +| **Credential Theft** | Agent attempts to extract credentials for later use | Agent reads GitHub token from environment | + +### 1.2 Protected Assets + +| Asset | Classification | Protection Requirement | +|-------|----------------|----------------------| +| **GitHub Tokens** | Critical | Never enter sandbox container | +| **Anthropic API Keys** | Critical | Never enter sandbox container (injected by gateway) | +| **Source Code (Private)** | High | Readable, changes require human review | +| **PR Content** | Medium | Agent cannot merge; human review required | + +### 1.3 Security Boundaries + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ UNTRUSTED ZONE │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ Sandbox Container │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ LLM Agent │ │ Code Files │ │ git/gh │ │ Isolated │ │ │ +│ │ │ (Claude) │ │ (workspace) │ │ wrappers │ │ Worktree │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ │ │ +│ │ NO: GitHub tokens, Anthropic API keys, SSH keys, cloud credentials │ │ +│ │ NO: Direct network access (proxy required in private mode) │ │ +│ │ NO: Git metadata (.git directory shadowed by tmpfs) │ │ +│ │ NO: Other agents' workspaces │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ │ HTTP API (authenticated) │ +│ ▼ │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ TRUSTED ZONE │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ Gateway Sidecar (Policy Enforcer) │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ GITHUB_ │ │ ANTHROPIC_ │ │ HTTPS │ │ Policy │ │ │ +│ │ │ TOKEN │ │ API_KEY │ │ Proxy │ │ Engine │ │ │ +│ │ │ (secure) │ │ (secure) │ │ (filtered) │ │ (validates) │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ │ │ +│ │ ENFORCES: Branch ownership, merge blocking, domain allowlist │ │ +│ │ LOGS: All operations with full audit trail │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. Architecture Overview + +### 2.1 Component Diagram + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Host Machine │ +│ │ +│ ┌──────────────────────────────────────────────────────────────────────┐ │ +│ │ Isolated Network (internal: true) │ │ +│ │ No external route │ │ +│ │ │ │ +│ │ ┌───────────────────┐ ┌───────────────────────┐ │ │ +│ │ │ Sandbox Container │ │ Gateway Sidecar │ │ │ +│ │ │ │◄──REST API────►│ │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ LLM Agent │ │ GITHUB_TOKEN │ │ │ +│ │ │ git/gh wrappers │◄──HTTPS Proxy──│ ANTHROPIC_API_KEY │ │ │ +│ │ │ NO credentials │ │ Squid Proxy │ │ │ +│ │ │ │ │ Policy Engine │ │ │ +│ │ └───────────────────┘ └───────────┬───────────┘ │ │ +│ │ │ │ │ +│ └─────────────────────────────────────────────────────│────────────────┘ │ +│ │ │ +│ ┌─────────────────────────────────────────────────────│────────────────┐ │ +│ │ External Network (bridge) │ │ │ +│ │ │ │ │ +│ │ ┌────────────────┴──────────────┐ │ │ +│ │ │ Gateway Sidecar │ │ │ +│ │ │ (dual-homed) │ │ │ +│ │ │ │ │ │ +│ │ │ ALLOWED: │ │ │ +│ │ │ - api.anthropic.com │ │ │ +│ │ │ - github.com │ │ │ +│ │ │ - api.github.com │ │ │ +│ │ │ - *.githubusercontent.com │ │ │ +│ │ │ │ │ │ +│ │ │ BLOCKED: │ │ │ +│ │ │ - Everything else │ │ │ +│ │ └──────────────┬────────────────┘ │ │ +│ └───────────────────────────────────────────────────│──────────────────┘ │ +│ │ │ +│ ▼ │ +│ Internet │ +│ (allowlisted only) │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 Security Properties Summary + +| Property | Implementation | Verification | +|----------|----------------|--------------| +| **Credential Isolation** | Tokens exist only in gateway sidecar | Container has no env vars or files with tokens | +| **Network Isolation** | Internal Docker network with no external route | Network configured with `internal: true` | +| **Domain Allowlist** | Squid proxy with SNI-based filtering | Blocked requests return HTTP 403 | +| **Git Metadata Isolation** | `.git` directories shadowed by tmpfs | Agent cannot read or modify git refs directly | +| **Branch Ownership** | Gateway validates push requests | Only `egg/*` prefixed branches allowed | +| **Merge Blocking** | Gateway has no merge endpoint | `gh pr merge` commands fail at gateway level | +| **Audit Logging** | All operations logged with correlation IDs | Structured JSON logs | + +--- + +## 3. Network Isolation + +### 3.1 Public vs Private Mode + +| Mode | Network Access | Use Case | +|------|----------------|----------| +| **Public** | Full internet (Anthropic API via gateway for credential injection) | Open source, package installation | +| **Private** | Anthropic API + private GitHub repos only | Confidential code, sensitive data | + +### 3.2 Domain Allowlist (Private Mode) + +| Domain | Purpose | Required For | +|--------|---------|--------------| +| `api.anthropic.com` | Claude API | Agent operation | +| `github.com` | Git HTTPS | Clone, fetch, push | +| `api.github.com` | GitHub REST API | PR creation, issues | +| `raw.githubusercontent.com` | Raw content | File downloads | +| `objects.githubusercontent.com` | Release assets | Binary downloads | +| `codeload.github.com` | Archive downloads | Zip/tarball | +| `uploads.github.com` | File uploads | Release assets | + +### 3.3 Blocked in Private Mode + +| Category | Examples | Impact | Mitigation | +|----------|----------|--------|------------| +| Package registries | pypi.org, npmjs.com | Cannot install packages | Pre-install in image | +| Search engines | google.com, bing.com | Cannot search web | Use local docs, GitHub search | +| Arbitrary APIs | Any unlisted domain | Cannot exfiltrate | **This is the security goal** | + +### 3.4 Agent Tool Behavior (Private Mode) + +| Tool | Status | Reason | +|------|--------|--------| +| WebFetch | Blocked | Cannot reach arbitrary URLs | +| WebSearch | Blocked | Cannot reach search engines | +| GitHub tools | Works | Routed through gateway | +| Claude API | Works | api.anthropic.com allowed | + +When blocked tools are invoked, the agent receives HTTP 403 and adapts by using local resources. + +### 3.5 Docker Network Configuration + +```yaml +networks: + egg-isolated: + internal: true # No external connectivity + egg-external: + # Standard bridge network for gateway outbound + +services: + sandbox: + networks: + - egg-isolated # ONLY internal network + dns: [] # No DNS servers (prevents DNS tunneling) + + gateway: + networks: + - egg-isolated # Can receive from sandbox + - egg-external # Can reach internet +``` + +### 3.6 DNS Configuration + +The `dns: []` setting prevents the sandbox container from using external DNS servers, blocking DNS tunneling as an exfiltration vector: + +| Hostname | Resolution Method | +|----------|-------------------| +| `gateway` | Docker's embedded DNS (via /etc/hosts) | +| `localhost` | /etc/hosts | +| External names | Fails (no resolvers) | + +External hostname resolution happens in the gateway container, which has normal DNS access. + +### 3.7 Squid Proxy Configuration + +```squid +# Block direct IP connections (prevent bypass via learned IPs) +acl direct_ip url_regex ^https?://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ +http_access deny direct_ip + +# Load allowed domains +acl allowed_domains dstdomain "/etc/squid/allowed_domains.txt" + +# SSL bump for SNI inspection (peek only, no MITM decryption) +ssl_bump peek step1 +ssl_bump splice allowed_domains +ssl_bump terminate all + +# Allow only from internal network to allowed domains +http_access allow localnet allowed_domains +http_access deny all +``` + +### 3.8 ECH/ESNI Handling + +TLS 1.3 Encrypted Client Hello (ECH) can encrypt the SNI field. egg blocks connections where SNI cannot be determined: + +```squid +ssl_bump terminate !sni_available +``` + +| Scenario | Behavior | +|----------|----------| +| Standard TLS with SNI | Inspect and filter | +| TLS with ECH/ESNI | Connection terminated | +| Missing SNI | Connection terminated | + +None of the allowlisted domains currently use ECH. + +--- + +## 4. Credential Isolation + +### 4.1 Credentials Inventory + +| Credential | Location | Sandbox Access | +|------------|----------|----------------| +| `GITHUB_TOKEN` | Gateway sidecar only | Never | +| `ANTHROPIC_API_KEY` | Gateway sidecar only | Never (injected via proxy) | +| SSH keys | None | Not present | +| Cloud credentials | None | Not present | + +### 4.2 Anthropic API Credential Flow + +``` +┌─────────────────┐ ANTHROPIC_BASE_URL ┌─────────────────────┐ +│ Sandbox │ ───────────────────────▶│ Gateway │ +│ Container │ http://gateway:8080 │ Anthropic Proxy │ +│ │ (no credentials) │ │ +│ Claude Code │ │ 1. Receive request │ +│ (no API key) │ │ 2. Inject API key │ +│ │ │ 3. Forward to API │──▶ api.anthropic.com +└─────────────────┘ └─────────────────────┘ +``` + +Claude Code sends requests to the gateway via `ANTHROPIC_BASE_URL`. The gateway injects credentials and forwards to api.anthropic.com over HTTPS. + +### 4.3 GitHub Token Lifecycle + +GitHub App tokens are preferred with automatic rotation: + +``` +┌────────────────────────────────────────────────────────────────────────────┐ +│ GitHub App Token Lifecycle │ +│ │ +│ 1. Gateway requests installation token from GitHub App │ +│ 2. Token valid for 1 hour (GitHub enforced) │ +│ 3. Gateway refreshes token 10 minutes before expiration │ +│ 4. Old token naturally expires - no revocation needed │ +│ │ +│ Timeline: │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ 0min 50min 60min │ +│ Token issued Refresh Expiration │ +└────────────────────────────────────────────────────────────────────────────┘ +``` + +| Scenario | Behavior | +|----------|----------| +| Refresh fails (GitHub unavailable) | Retry with backoff; continue with existing token | +| Token expired, refresh still failing | Git operations fail with clear error | +| Gateway restart | Request new token on startup | + +### 4.4 Gateway Authentication + +The sandbox authenticates to the gateway using a shared secret: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Authentication Flow │ +│ │ +│ 1. Orchestrator generates random shared secret at startup │ +│ 2. Secret injected into both containers via environment variable │ +│ 3. Sandbox includes secret in Authorization header for gateway requests │ +│ 4. Gateway validates secret before processing any request │ +│ │ +│ Sandbox Container Gateway Sidecar │ +│ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ GATEWAY_ │ Authorization: │ Validate header │ │ +│ │ SECRET │ ──Bearer $SECRET──► matches GATEWAY_ │ │ +│ │ │ │ SECRET │ │ +│ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +The secret is generated fresh on each container startup and exists only for the container lifecycle. + +--- + +## 5. Git and GitHub Lockdown + +### 5.1 Git Metadata Isolation + +The sandbox container cannot access git metadata: + +``` +Container filesystem view: +/workspace/my-repo/ +├── src/ ← Agent can edit these files +├── tests/ ← Agent can edit these files +├── README.md ← Agent can edit this file +└── .git/ ← Empty directory (tmpfs shadow) +``` + +Without git metadata, the agent cannot: +- Discover repository origins +- Modify staging area directly +- Change branch pointers +- Execute git hooks +- Access other worktrees + +### 5.2 Gateway-Enforced Policies + +| Policy | Implementation | +|--------|----------------| +| **Branch ownership** | Only push to `egg/*` prefixed branches | +| **Protected branches** | Block direct push to `main`, `master` | +| **Force push** | `--force` flag blocked globally | +| **Merge blocking** | No merge endpoint exists in gateway API | + +### 5.3 Gateway REST API + +| Endpoint | Purpose | Policy Checks | +|----------|---------|---------------| +| `POST /api/v1/git/push` | Push to remote | Branch ownership, no force push | +| `POST /api/v1/git/fetch` | Fetch from remote | None (read-only) | +| `POST /api/v1/git/status` | Get status | None (read-only) | +| `POST /api/v1/git/diff` | Get diff | None (read-only) | +| `POST /api/v1/git/commit` | Create commit | None | +| `POST /api/v1/gh/pr/create` | Create PR | Agent attribution | +| `POST /api/v1/gh/pr/comment` | Comment on PR | None | + +### 5.4 Blocked Operations + +| Operation | Why Blocked | +|-----------|-------------| +| `git merge` to protected branches | Must go through PR review | +| `gh pr merge` | Human must review and merge | +| `git push --force` | Could destroy others' work | +| `git config --global` | Could affect other agents | +| `git remote add/remove` | Could redirect pushes | + +### 5.5 Blocked Flags + +| Flag | Risk | +|------|------| +| `--exec`, `-c` | Command injection | +| `--upload-pack`, `--receive-pack` | Arbitrary command execution | +| `--config`, `-c` | Runtime config override | +| `--no-verify` | Skip hooks (defense in depth) | +| `--git-dir`, `--work-tree` | Path traversal | + +--- + +## 6. Private Repository Mode + +### 6.1 Purpose + +Private Repo Mode restricts agents to only interact with **private** GitHub repositories, preventing any interaction with public repositories. + +### 6.2 Motivation + +When operating on sensitive codebases: +1. **Accidental code sharing:** Agent might reference or copy code to a public repository +2. **Data leakage via forks:** Agent could fork a private repo to a public destination +3. **Cross-contamination:** Agent might mix private code with public dependencies + +### 6.3 Enforcement + +The gateway checks repository visibility via GitHub API: + +| Operation | Public Repo | Private Repo | +|-----------|-------------|--------------| +| `git clone` | Blocked | Allowed | +| `git fetch` | Blocked | Allowed | +| `git push` | Blocked | Allowed | +| `gh pr create` | Blocked | Allowed | + +### 6.4 Visibility Cache + +| Operation Type | TTL | Rationale | +|----------------|-----|-----------| +| Read operations (fetch, clone) | 60 seconds | Lower risk; brief window acceptable | +| Write operations (push, PR create) | 0 seconds | Higher risk; always verify before writes | + +**Error handling:** +- **Read operations:** Fail open (allow if GitHub unavailable) +- **Write operations:** Fail closed (deny if GitHub unavailable) + +This balances availability with security. + +--- + +## 7. Audit Logging + +### 7.1 Log Format + +All operations produce structured JSON logs: + +```json +{ + "timestamp": "2026-01-29T14:32:01.234Z", + "severity": "INFO", + "message": "Git push completed", + "traceId": "0af7651916cd43dd8448eb211c80319c", + "spanId": "b7ad6b7169203331", + "service": "gateway", + "operation": "git_push", + "source_container": "sandbox-abc123", + "auth_valid": true, + "request": { + "repository": "owner/repo", + "ref": "egg/feature-branch", + "force": false + }, + "response": { + "status": "success", + "duration_ms": 1234 + }, + "policy_checks": { + "branch_ownership": "passed", + "protected_branch": "passed", + "force_push_attempted": false + } +} +``` + +### 7.2 Logged Operations + +| Category | Operations Logged | +|----------|-------------------| +| **Git operations** | push, fetch, clone, status, diff, commit | +| **GitHub operations** | PR create, comment, close | +| **Proxy traffic** | All HTTPS requests (destination, status) | +| **Policy violations** | Blocked operations with reason | +| **Authentication** | Success/failure | + +### 7.3 Alerting + +| Condition | Alert Priority | +|-----------|----------------| +| Policy violation (blocked operation) | High | +| Authentication failure | High | +| High volume of blocked requests | Medium | +| GitHub rate limit | Low | + +--- + +## 8. Residual Risks + +### 8.1 Known Residual Risks + +| Risk | Severity | Mitigation | Status | +|------|----------|------------|--------| +| **Data exfiltration via GitHub** | Medium | Commit messages/PR descriptions reviewed by human; private repos only | Acknowledged | +| **Data exfiltration via Anthropic API** | Low | Anthropic doesn't train on API data; network lockdown limits where data can go | Acknowledged | + +### 8.2 Exfiltration via GitHub (Detail) + +The gateway cannot fully prevent data exfiltration via GitHub without imposing arbitrary limits: + +| Vector | Limit | Impact | +|--------|-------|--------| +| Branch names | 256 characters | ~32 bytes useful payload | +| Commit messages | No hard limit | Could encode substantial data | +| PR titles/bodies | No hard limit | Could encode substantial data | + +**Why we don't impose limits:** +- Arbitrary limits interfere with legitimate detailed commit messages +- Private Repo Mode ensures data stays in controlled repositories +- Human reviews PR content before merge +- All operations logged for anomaly detection + +**Risk Acceptance:** This exfiltration vector is accepted as a residual risk. Mitigations provide detection capability, and data stays within controlled (private) repositories. + +### 8.3 Gaps Not Yet Addressed + +| Gap | Description | Proposed Solution | Priority | +|-----|-------------|-------------------|----------| +| **Multi-agent isolation** | Agent memory could be shared | Per-container memory isolation | Medium | +| **Container escape** | Defense in depth, not primary threat model | Keep Docker updated; consider gVisor | Low | +| **Host compromise** | Out of scope; host is trusted | Physical security, OS hardening | N/A | + +--- + +## 9. OWASP Alignment + +This architecture aligns with the **OWASP Top 10 for Agentic Applications**: + +| OWASP Risk | Description | Mitigation | +|------------|-------------|------------| +| **ASI01** - Agentic Excessive Authority | Agents granted overly broad permissions | Credential isolation; gateway exposes minimal API; no merge endpoint | +| **ASI02** - Tool Misuse & Exploitation | Agents misusing available tools | Gateway enforces policies; force push blocked; domain allowlist | +| **ASI03** - Identity & Privilege Abuse | Credential theft or misuse | Credentials never enter container; gateway holds all tokens | +| **ASI04** - Supply Chain Vulnerabilities | Compromised dependencies | Pre-installed packages; image scanning; no runtime installs (private mode) | +| **ASI06** - Memory/Context Poisoning | Corruption of agent memory | Container isolation; isolated worktrees | +| **ASI10** - Rogue Agents | Agent operating outside intended behavior | Infrastructure controls (not instructions) prevent unauthorized operations | + +**Reference:** [OWASP Top 10 for Agentic Applications](https://genai.owasp.org/) + +--- + +## Related ADRs + +- [ADR: Git Isolation Architecture](adr/git-isolation-architecture.md) +- [ADR: Credential Injection](adr/credential-injection.md) +- [ADR: Network Isolation](adr/network-isolation.md) diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000000..57038fd9cb --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,180 @@ +# Setup Guide + +This guide walks through setting up egg for the first time. + +## Prerequisites + +- Docker installed and running +- Git +- A GitHub account (for repository access) +- Anthropic API key or Claude Pro/Max subscription + +## Quick Setup + +```bash +# 1. Clone the repository +git clone https://github.com/jwbron/egg.git +cd egg + +# 2. Run the development setup +./dev setup + +# 3. Create configuration files +cp egg.yaml.example egg.yaml +cp secrets.yaml.example secrets.yaml + +# 4. Edit secrets.yaml with your credentials +# (see Configuration section below) + +# 5. Start the sandbox +egg start +``` + +## Configuration + +### 1. GitHub Authentication + +egg supports two authentication methods: + +#### Option A: GitHub App (Recommended) + +1. Create a GitHub App at https://github.com/settings/apps +2. Configure permissions: + - Repository: Contents (Read & Write) + - Repository: Pull requests (Read & Write) + - Repository: Issues (Read & Write) +3. Generate a private key +4. Install the app on your repositories +5. Add to secrets.yaml: + +```yaml +secrets: + github_app: + app_id: "YOUR_APP_ID" + private_key_path: "/path/to/private-key.pem" +``` + +#### Option B: Personal Access Token + +1. Create a PAT at https://github.com/settings/tokens +2. Select scopes: `repo`, `workflow` (if needed) +3. Add to secrets.yaml: + +```yaml +secrets: + pats: + personal: "ghp_xxxxxxxxxxxx" +``` + +### 2. Anthropic Authentication + +#### Option A: API Key + +For Anthropic API accounts (teams, enterprise): + +```yaml +secrets: + anthropic: + api_key: "sk-ant-xxxxxxxxxxxx" +``` + +#### Option B: OAuth Token + +For Claude Pro/Max subscriptions: + +1. Run `claude auth login` to authenticate +2. Add the token to secrets.yaml: + +```yaml +secrets: + anthropic: + oauth_token: "oauth-xxxxxxxxxxxx" +``` + +### 3. Repository Configuration + +Edit egg.yaml to specify allowed repositories: + +```yaml +egg: + repositories: + allowed: + - "your-org/repo1" + - "your-org/repo2" + - "your-org/*" # Allow all repos from org +``` + +## Verify Setup + +```bash +# Validate configuration +egg config validate + +# Start the sandbox +egg start + +# Check status +egg status +``` + +## Network Modes + +### Public Mode (Default) + +Full internet access. Use for: +- Open source development +- Public repositories +- Tasks requiring npm/pip package installation + +```bash +egg start +``` + +### Private Mode + +Network locked down to Anthropic API only. Use for: +- Private repositories +- Sensitive codebases +- Maximum security + +```bash +egg start --private +``` + +## Troubleshooting + +### Docker Issues + +```bash +# Check Docker is running +docker info + +# Check gateway health +curl http://localhost:9847/api/v1/health +``` + +### Authentication Issues + +```bash +# Verify GitHub authentication +gh auth status + +# Test Anthropic API +curl https://api.anthropic.com/v1/messages \ + -H "x-api-key: YOUR_KEY" \ + -H "anthropic-version: 2023-06-01" +``` + +### Permission Issues + +Ensure the private key file has correct permissions: + +```bash +chmod 600 /path/to/private-key.pem +``` + +## Next Steps + +- Read the [Security Model](security.md) to understand how egg protects your code +- See [Configuration](configuration.md) for all options +- Check [API Reference](api.md) for gateway endpoints diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000000..e7bbd99b6d --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,266 @@ +# Troubleshooting + +Common issues and their solutions. + +## Docker Issues + +### Docker daemon not running + +**Symptom:** +``` +Cannot connect to the Docker daemon at unix:///var/run/docker.sock +``` + +**Solution:** +```bash +# Linux +sudo systemctl start docker + +# macOS +# Start Docker Desktop from Applications +``` + +### Permission denied + +**Symptom:** +``` +permission denied while trying to connect to the Docker daemon socket +``` + +**Solution:** +```bash +# Add user to docker group (Linux) +sudo usermod -aG docker $USER + +# Log out and back in, or run: +newgrp docker +``` + +## Authentication Issues + +### GitHub authentication failed + +**Symptom:** +``` +Error: Authentication failed for 'https://github.com/owner/repo' +``` + +**Solutions:** + +1. **Check credentials file:** + ```bash + cat ~/.config/egg/secrets.yaml + # Verify github_app or pats section is correct + ``` + +2. **For GitHub App:** + - Verify app is installed on the repository + - Check private key file exists and is readable + - Ensure app_id is correct + +3. **For PAT:** + - Verify token hasn't expired + - Check token has required scopes (`repo`) + +### Anthropic API authentication failed + +**Symptom:** +``` +Error: 401 Unauthorized from Anthropic API +``` + +**Solutions:** + +1. **API Key:** + ```bash + # Test API key directly + curl https://api.anthropic.com/v1/messages \ + -H "x-api-key: YOUR_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "content-type: application/json" \ + -d '{"model":"claude-sonnet-4-20250514","max_tokens":10,"messages":[{"role":"user","content":"Hi"}]}' + ``` + +2. **OAuth Token:** + - Token may have expired + - Run `claude auth login` to refresh + +## Gateway Issues + +### Gateway not responding + +**Symptom:** +``` +Connection refused to egg-gateway:9847 +``` + +**Solutions:** + +1. **Check container is running:** + ```bash + docker ps | grep egg-gateway + ``` + +2. **Check gateway logs:** + ```bash + docker logs egg-gateway + ``` + +3. **Verify network:** + ```bash + docker network inspect egg-isolated + ``` + +### Gateway health check failing + +**Symptom:** +```bash +curl http://localhost:9847/api/v1/health +# Returns error or timeout +``` + +**Solutions:** + +1. **Check gateway startup:** + ```bash + docker logs egg-gateway 2>&1 | tail -50 + ``` + +2. **Verify configuration:** + ```bash + egg config validate + ``` + +## Policy Violations + +### Branch ownership violation + +**Symptom:** +``` +Error: Branch ownership violation - cannot push to 'main' +``` + +**Explanation:** The gateway only allows pushing to branches with the configured prefix (default: `egg/`). + +**Solutions:** + +1. Create a branch with the correct prefix: + ```bash + git checkout -b egg/my-feature + ``` + +2. Or adjust the `branch_prefix` in egg.yaml (not recommended for security) + +### Merge blocked + +**Symptom:** +``` +Error: Merge operations are blocked +``` + +**Explanation:** By design, agents cannot merge PRs. Humans must review and merge via GitHub UI. + +**Solution:** This is intended behavior. Have a human review and merge the PR. + +## Network Issues + +### Cannot access external URLs (private mode) + +**Symptom:** +``` +Connection refused / Connection timed out +``` + +**Explanation:** In private mode, only allowed domains are accessible. + +**Solutions:** + +1. Check if the domain is in the allowlist: + ```bash + curl http://egg-gateway:9847/api/v1/config/domains + ``` + +2. Switch to public mode if needed: + ```bash + egg stop + egg start # Without --private flag + ``` + +### WebSearch/WebFetch not working (private mode) + +**Symptom:** +``` +WebSearch tool call returned error +``` + +**Explanation:** WebSearch and WebFetch are blocked in private mode to prevent data exfiltration. + +**Solution:** Use public mode if you need web search capabilities. + +## Session Issues + +### Session expired + +**Symptom:** +``` +Error: Session expired or invalid +``` + +**Solution:** +```bash +# Restart the sandbox to create a new session +egg stop +egg start +``` + +### Session not found after gateway restart + +**Symptom:** Commands fail after restarting the gateway. + +**Explanation:** Sessions are stored in `~/.egg/sessions.json`. If this file is missing or corrupted, sessions are lost. + +**Solution:** +```bash +# Restart the sandbox container +egg stop +egg start +``` + +## Workspace Issues + +### Uncommitted changes lost + +**Symptom:** Changes disappeared after container restart. + +**Explanation:** Only committed changes are preserved. Uncommitted changes in the working directory are lost if the container stops unexpectedly. + +**Best Practice:** Commit frequently. + +### Cannot see other branches + +**Symptom:** `git branch -a` only shows the current branch. + +**Explanation:** The container doesn't have access to git metadata. Branch information is managed by the gateway. + +**Solution:** Use `git log --oneline -10` through the git wrapper to see commit history. + +## Getting Help + +If you're still having issues: + +1. Check the gateway logs: + ```bash + docker logs egg-gateway + ``` + +2. Enable debug logging in egg.yaml: + ```yaml + egg: + logging: + level: "DEBUG" + ``` + +3. Open an issue with: + - Error message + - Gateway logs + - Steps to reproduce