Commit Graph

8 Commits

Author SHA1 Message Date
Thomas Friedel 6af5180513 feat(sandbox): extend managed allowlist with build + dataset egress domains
Widens the Bash-subprocess egress allowlist for common agent workflows:
Anthropic direct (api, statsig), Ubuntu mirrors, NodeSource/deb for Node,
crates.io (Rust), yarn, full npm + pypi groups, GitHub release assets,
and wildcards for geoboundaries, googleapis, jsdelivr — covers dataset
fetches and CDN-backed library assets that previously failed silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 23:39:34 +02:00
Thomas Friedel c5f6764470 feat(sandbox): enable Claude Code built-in sandbox for Bash + WebFetch egress
Closes #10. Until now the sandbox container had unrestricted outbound NAT
on sandbox-net — a compromised agent could curl arbitrary hosts, pip/npm
install malicious packages, or exfiltrate files via WebFetch. The
anthropic-proxy path-allowlist (#6) guarded only the credential-injection
proxy, not general egress.

Two layers added, both shipped as managed (enterprise) settings so user
and project settings cannot downgrade them:

- sandbox.*  → bubblewrap + local proxy gating Bash subprocesses (and all
  their children: pip, npm, git, curl). enableWeakerNestedSandbox=true
  because we run inside Docker without privileged user namespaces;
  accepted because the Docker container boundary is unchanged and this
  adds new egress filtering that previously did not exist.
- permissions.* → deny-by-default WebFetch with explicit domain allows
  (WebFetch bypasses the OS sandbox and needs its own allowlist).

failIfUnavailable=true + allowUnsandboxedCommands=false close the two
silent-fallback paths: if bubblewrap/socat install breaks, the container
fails loud instead of serving unsandboxed bash; and the
dangerouslyDisableSandbox escape hatch is disabled so sandbox violations
can't be bypassed via a permission prompt in our unattended deployment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 23:31:07 +02:00
Thomas Friedel af2e69a4bb feat(sandbox): recover chat session mapping after OWUI restart
Closes #5. In-process _chat_sessions dict is empty after any restart, which
silently started a fresh Claude session on the next turn and lost the prior
conversation. On cache miss we now scan the per-chat CLAUDE_CONFIG_DIR on
disk, pick the newest session JSONL by mtime, and resume from it — Claude's
own filesystem layout is already the source of truth. UUID regex guards
against unexpected files in that directory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 23:04:25 +02:00
Thomas Friedel 3cec4dc871 feat(sandbox): pin CLI, reap orphans, isolate chats, cleanup script
Addresses four hardening items in one change:

- #8 Pin @anthropic-ai/claude-code to 2.1.114 via Dockerfile ARG.
  `docker compose build --build-arg CLAUDE_CODE_VERSION=X.Y.Z` upgrades.

- #2 Move `client.kill()` from an `except CancelledError` branch into a
  `finally` guarded by an `exited_cleanly` flag. Any abnormal exit path
  (httpx timeout, generator GC'd, unexpected exception) now reaps the
  open-terminal child — previously only asyncio cancellation did, so
  dropped browsers left claude billing tokens until EXECUTE_TIMEOUT.

- #9 Per-chat CLAUDE_CONFIG_DIR=~/chat-<id>/.claude. Two concurrent
  chats for the same OWUI user no longer race each other on a shared
  ~/.claude/. Safe to shard now that credentials live in the proxy
  rather than .credentials.json.

- #1 sandbox/cleanup.sh installed at /opt/cleanup.sh. Not auto-run;
  README documents dry-run-by-default usage via `docker compose exec`.
  CHAT_TTL_DAYS and SESSION_TTL_DAYS control retention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:20:29 +02:00
Thomas Friedel 4158af9ebe feat(sandbox): route Anthropic traffic through credential-injecting proxy
Adds a Caddy-based reverse proxy (`anthropic-proxy/`) that forwards
/v1/* to api.anthropic.com with the real credential injected at the
proxy, not in the sandbox. The sandbox now only sees
ANTHROPIC_BASE_URL=http://anthropic-proxy:8081 and a placeholder
ANTHROPIC_AUTH_TOKEN, so `env` / `cat /proc/self/environ` yield nothing.

Proxy auth mode:
- CLAUDE_CODE_OAUTH_TOKEN → `Authorization: Bearer` + appends
  `oauth-2025-04-20` to anthropic-beta (required for subscription OAuth
  on /v1/messages).
- ANTHROPIC_API_KEY → `x-api-key`.

Pipe changes:
- Drop ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN valves.
- Add ANTHROPIC_BASE_URL valve (defaults to the compose service DNS).
- Replace credential env injection with ANTHROPIC_AUTH_TOKEN=proxied.

Closes acceptance criteria for issue #6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:59:59 +02:00
Thomas Friedel 7e7fa4cb77 feat: sandboxed Claude Code pipe via open-terminal
Adds `claude_agent_pipe_sandboxed.py`, a self-contained OpenWebUI pipe
that shells `claude --output-format stream-json` inside an open-webui/
open-terminal container instead of running the Claude Agent SDK
in-process. Each OWUI user gets a dedicated Linux account (mapped via
the X-User-Id header) with a per-chat workspace directory that persists
across turns and whose generated PNG/PDF/CSV artifacts are auto-inlined
into the chat.

Scaffolding under `sandbox/`:
- Dockerfile extending ghcr.io/open-webui/open-terminal with Claude
  Code pre-installed system-wide (inherited by every provisioned user)
- docker-compose.yml + README covering standalone deployment
- Async HTTP client + stream-json runner for programmatic use

`sync_pipe.py` pushes the pipe to a running OWUI via the admin
functions API so iteration doesn't require manual repaste.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:02:01 +02:00
Thomas Friedel a4898f6d9a Disable fast path — always run full agent loop 2026-04-18 15:19:57 +02:00
Thomas Friedel 1544ec1944 Initial commit: Claude Code pipe for Open WebUI 2026-04-18 14:55:03 +02:00