Repository navigation
feat: run claw-wrap as a Kubernetes sidecar - #12
Merged
Merged
Conversation
Reads Docker/Kubernetes secret mounts. Follows symlinks (Secret volumes use ..data links), requires owner root or daemon uid, rejects group-writable or other-accessible files, caps size at 64 KiB, trims trailing newlines, supports jq, and is never cached so rotation applies immediately.
Tool options: - match: argv in allowed_args: one anchored pattern per argument, exact arity. Arguments are never joined, so spaces/newlines cannot widen a match. - working_dir pins the tool cwd (the caller's cwd may not exist daemon-side). - use_stdin: false gives the tool /dev/null stdin and disables PTY. - request_env allowlists which env vars the caller may set. - Injected credential values (>= 8 bytes) are redacted from tool output. Runtime: - --runtime-gid, --auth-mode, --socket-mode (ported from feature/docker-compose) and strict --uid parsing. - CLAW_WRAP_RUNTIME_DIR moves socket/auth/proxy token for daemon and wrapper; the env credential file stays put. - Reload config on Kubernetes ConfigMap ..data symlink swaps. - audit.stdout writes JSONL audit entries to stdout; audit cwd is the effective working directory. - Warn when no tools are configured; log once when caller exe verification is unavailable (separate PID namespace). Fixes: - Tool output could be truncated: Wait() closed StdoutPipe readers before the pumpers drained them. Use owned pipes with an idle-based drain deadline, which also keeps detached children from holding a call open. - Data race between handleTimeout and cmd.Wait on ProcessState. - finalizeOutput no longer drops stderr when stdout finalization fails. examples/mcparcel/wrappers.yaml is covered by regression tests.
Debian slim with tini as PID 1 (reaps detached tool daemons) and a non-root claw user. The workflow builds linux/amd64 and linux/arm64, pushes to ghcr.io on v* tags and only builds on PRs touching the image.
Covers the shared runtime dir, UID/GID modes, security model without exe verification, Secrets via file:, ConfigMap reload, probes, audit to stdout and the contract for tools that start background daemons. CONFIG.md gains file:, working_dir, use_pty, use_stdin, request_env, match: argv, audit and daemon flags.
govulncheck flags GO-2026-6218, -6090, -6089, -5972, -5856 (stdlib, fixed in go1.26.5/6) and GO-2026-5026 (x/net, fixed in v0.55.0).
…it secrets
Split the output redactor into two stages. Literal secrets are replaced
first and only a trailing proper prefix of a secret is held back, so output
such as an interactive "Continue? [y/N] " prompt is released immediately
instead of sitting in a 128-byte carry while the tool waits for input.
redact_output regex rules then run on that output only, so they can no
longer rewrite the start of a secret that is still arriving
("key=tok_" + "live_123456" with a tok -> *** rule leaked "_live_123456").
When one secret is a prefix of another, the longer one is awaited.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes claw-wrap usable as a sidecar next to an AI agent in one pod: the agent container holds no secrets and calls wrapped tools over a socket on a shared emptyDir; the sidecar runs the real binaries with credentials from a Kubernetes Secret.
The guide is
docs/KUBERNETES.md, with a manifest inexamples/kubernetes/and a tool config inexamples/mcparcel/. I deployed that manifest to a kind cluster (Kubernetes 1.35) with a fake tool that daemonizes and echoes its secret. Allowed and blocked calls, redaction, daemon survival, zombie reaping by tini, audit on stdout, PID isolation and ConfigMap hot reload all behaved as documented.What would have broken in a pod
invalid working directory. New per-toolworking_dir...datasymlink and the watcher only looked atwrappers.yaml. (It worked on macOS because kqueue follows the symlink.)env:rejects symlinks and root-owned files). Newfile:backend.CLAW_WRAP_RUNTIME_DIRpoints both sides at a subdirectory the daemon creates.audit.file: /dev/stdoutis refused (symlink). Newaudit.stdout.New options
match: argvinallowed_args: one anchored regex per argument, exact arity.match: commandjoins args with spaces, socall front.read --server https://evil.examplepasses a typical^call\s+front\.read(\s|$)rule; a test documents this.use_stdin: false:/dev/nullstdin and no PTY, so prompts fail fast.request_env: allowlist for caller-supplied env. The agent holds the HMAC secret, so without this it can pass tool-specific variables (a server URL, say).env/config_file(8+ bytes) are redacted from tool output. This applies to every tool, so it's a behaviour change worth noting in the release.--runtime-gid,--auth-mode,--socket-mode, ported fromfeature/docker-compose(without its Homebrew/Node image).Bugs found along the way
Wait()closedStdoutPipereaders before the pumpers drained them (8 KB lost out of 200 KB in a test). Now uses owned pipes with an idle-based drain deadline, which also means a tool that leaves a detached daemon holding stdout can't keep a call open.handleTimeoutandcmd.WaitonProcessState.finalizeOutputreturned early on a stdout error and dropped stderr.Image
A root
Dockerfilebuilds Debian slim with tini as PID 1 (reaps tool daemons that get reparented) and a non-root user.image.ymlbuilds linux/amd64 and linux/arm64, pushes to ghcr.io onv*tags, and only builds on PRs that touch the image.Testing
go vetandgo test -race ./...pass on macOS and on Linux (non-root container, like CI).actionlintis clean,goreleaser checkpasses, and a goreleaser snapshot build produced all four targets.Not addressed
exec.CommandContextSIGKILLs the leader before the graceful SIGTERM to the group. This predates this PR.TestDaemon_Run_ConfiguresBackendsAndCleansUpOnFailurehangs when run as root. Also existing.