sha256:02df0d74f0636351a0ce5832b00d4064ce4bfc76ecf0a0aea43444b05b190d57
Last pushed
5 days by migmartri
Type
Sandbox Kit
Manifest digest
sha256:02df0d74f0636351a0ce5832b00d4064ce4bfc76ecf0a0aea43444b05b190d57
schemaVersion: "2"
kind: sandbox
name: chainloop-trace-claude
version: v1.109.0
displayName: Claude Code (Chainloop-traced)
description: Claude Code traced with Chainloop. Repos already initialized for `chainloop trace` use their committed config (attested on `git push`); all other repos are wrapped in `chainloop trace run` (attested on clean agent exit). Fork of the built-in `claude` agent.
extends: claude
args:
chainloopConfig:
default: ""
description: 'Absolute path INSIDE the sandbox to a chainloop config.toml to adopt - i.e. where you mounted the host''s, which sbx mounts at the SAME absolute path it has on the host. The wrapper copies it to ~/.config/chainloop/config.toml so every later `chainloop` invocation finds it, including the git hooks that persistent mode runs as their own processes (a --config flag would not reach those). Brings the org and the control-plane/CAS/platform endpoints with it, which is what makes this worthwhile for self-hosted. NOTE its [auth] token is your personal login session: short-lived (~days) and your full identity. Prefer chainloopToken for anything unattended.'
chainloopOrg:
default: ""
description: Chainloop organization. `trace run` mode ONLY - ignored in persistent mode, where the org comes from the repo's .chainloop.yml.
chainloopProject:
default: ""
description: Chainloop project. `trace run` mode ONLY - ignored in persistent mode.
chainloopToken:
default: ""
description: 'Chainloop org-scoped API token (chainloop organization api-token create). Authenticates the attestation push in BOTH modes; in persistent mode it must have access to the org pinned in the repo''s .chainloop.yml. OPTIONAL only because chainloopConfig is the other way to authenticate - supply one or the other, or the wrapper refuses to start. When both are present this one WINS: the CLI prefers an exported CHAINLOOP_TOKEN over a config-file login session and logs "Both user credentials and $CHAINLOOP_TOKEN set. Ignoring user credentials." (app/cli/cmd/root.go).'
chainloopWorkflow:
default: ai-coding-session
description: Chainloop workflow name. `trace run` mode ONLY. Matches the default `chainloop trace init` writes, so both modes land on the same workflow unless you say otherwise.
traceMode:
default: auto
description: 'auto = persistent when the repo is already trace-initialized, else trace run. run = always single-shot (DESTRUCTIVE on a trace-initialized repo: teardown wipes .git/chainloop-trace/ and strips the committed agent hooks). persistent = force persistent.'
enum:
- auto
- run
- persistent
sandbox:
entrypoint:
- /home/agent/.local/bin/cl-trace-wrap.sh
agentInstructions:
content: |
## Chainloop tracing
This session is recorded by Chainloop trace. Tool/MCP usage and coding
activity are attested and sent to Chainloop (as a CHAINLOOP_AI_CODING_SESSION
attestation) when the agent exits cleanly, or on `git push` when the
repository is initialized for persistent tracing.
permissions:
network:
allow:
- api.cp.chainloop.dev:443
- api.cas.chainloop.dev:443
- api.app.chainloop.dev:443
- t.chainloop.dev:443
- crb.chainloop.dev:443
- timestamp.digicert.com:80
- buf.build:443
- dl.chainloop.dev:443
- chainloop-baafegchfnekdcde.z02.azurefd.net:443
- github.com:443
environment:
variables:
CHAINLOOP_ORG: ${{ kit.args.chainloopOrg }}
CHAINLOOP_PROJECT: ${{ kit.args.chainloopProject }}
CHAINLOOP_TOKEN: ${{ kit.args.chainloopToken }}
CHAINLOOP_TRACE_MODE: ${{ kit.args.traceMode }}
CHAINLOOP_WORKFLOW: ${{ kit.args.chainloopWorkflow }}
CL_TRACE_CONFIG_SOURCE: ${{ kit.args.chainloopConfig }}
NO_PROXY: localhost,127.0.0.1,::1,gateway.docker.internal,api.cp.chainloop.dev,api.cas.chainloop.dev,api.app.chainloop.dev
no_proxy: localhost,127.0.0.1,::1,gateway.docker.internal,api.cp.chainloop.dev,api.cas.chainloop.dev,api.app.chainloop.dev
setup:
install:
- command: mkdir -p /usr/local/bin && curl -sfL https://dl.chainloop.dev/cli/install.sh | bash -s -- --path /usr/local/bin && chainloop version
user: "0"
description: Install the EE chainloop CLI on PATH
files:
- path: /home/agent/.local/bin/cl-trace-wrap.sh
content: |
#!/usr/bin/env bash
set -euo pipefail
run_plain() {
exec claude --dangerously-skip-permissions "$@"
}
# Every path that would hand back an UNTRACED agent is fatal instead.
# This kit exists to make a session provable, and a sandbox that quietly
# records nothing is the one outcome worse than not starting: you find
# out only when the attestation you expected is not there.
die() {
echo "[chainloop-trace] ERROR: $1" >&2
exit 1
}
if ! command -v chainloop >/dev/null 2>&1; then
die "chainloop CLI not on PATH - the kit's install step did not complete
(check egress to dl.chainloop.dev). Refusing to start an untraced agent."
fi
# Adopt a mounted host config.toml when one was pointed at. sbx mounts an
# extra read-only workspace at the SAME absolute path it has on the host,
# which is NOT where the CLI looks: `chainloop config view` inside the
# sandbox reports /home/agent/.config/chainloop/config.toml (Linux XDG),
# verified. Copy rather than pass --config, because in persistent mode the
# managed git hooks invoke `chainloop` as their own processes and a flag
# from this wrapper would never reach them.
# Deliberately not named CHAINLOOP_CONFIG: the CLI's viper prefix is
# CHAINLOOP, so that name could bind to its own --config flag.
if [ -n "$CL_TRACE_CONFIG_SOURCE" ]; then
if [ ! -r "$CL_TRACE_CONFIG_SOURCE" ]; then
die "chainloopConfig points at '$CL_TRACE_CONFIG_SOURCE', which is not
readable in the sandbox. Mount it read-only and give the path it has ON
THE HOST - that is where sbx mounts it inside too, e.g.
sbx run ... \"\$HOME/Library/Application Support/chainloop:ro\""
fi
mkdir -p "$HOME/.config/chainloop"
install -m 600 "$CL_TRACE_CONFIG_SOURCE" "$HOME/.config/chainloop/config.toml"
echo "[chainloop-trace] Adopted chainloop config from $CL_TRACE_CONFIG_SOURCE" >&2
fi
# Something has to authenticate the attestation. An explicit token wins
# over a config-file login session - the CLI prefers an exported
# CHAINLOOP_TOKEN and logs "Both user credentials and $CHAINLOOP_TOKEN
# set. Ignoring user credentials." (app/cli/cmd/root.go) - but with
# neither, nothing can be pushed, so refuse rather than record nothing.
if [ -z "$CHAINLOOP_TOKEN" ] && [ ! -r "$HOME/.config/chainloop/config.toml" ]; then
die "no Chainloop credentials. Pass an org-scoped API token with
--kit-arg chainloopToken=<token>
or mount your host config and point at it with
--kit-arg chainloopConfig=<path-inside-sandbox>"
fi
# NB: shell default-expansion (dollar-brace VAR colon-dash default) is
# rejected by the kit validator inside setup.files content - WORKDIR is
# the only supported placeholder - so the default is spelled out long.
# Every arg has a default, so the kit always defines these and bare
# $VAR is safe under `set -u`.
mode="$CHAINLOOP_TRACE_MODE"
if [ -z "$mode" ]; then
mode=auto
fi
# traceMode's enum is enforced by sbx before the sandbox is built, so a
# bad value here can only arrive via a runtime `-e` override.
case "$mode" in
auto|run|persistent) ;;
*) die "invalid CHAINLOOP_TRACE_MODE='$mode' (want auto|run|persistent)" ;;
esac
detected=0
if [ "$mode" != run ]; then
repo_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")
for f in "$repo_root/.chainloop.yml" "$repo_root/.chainloop.yaml"; do
if [ -f "$f" ] && grep -qE '^[[:space:]]*projectName:[[:space:]]*["'"'"']?[A-Za-z0-9]' "$f" \
&& grep -q "chainloop trace hook" "$repo_root/.claude/settings.json" 2>/dev/null; then
detected=1
break
fi
done
fi
if [ "$mode" = auto ]; then
if [ "$detected" = 1 ]; then mode=persistent; else mode=run; fi
fi
if [ "$mode" = persistent ]; then
if [ "$detected" != 1 ]; then
die "traceMode=persistent, but this repo carries no committed trace config
(needs .chainloop.yml with projectName AND the 'chainloop trace hook'
entries in .claude/settings.json). Run 'chainloop trace init' in the
repo, or use traceMode=run with chainloopOrg/chainloopProject."
fi
echo "[chainloop-trace] Repo already initialized for chainloop trace - persistent mode" >&2
echo "[chainloop-trace] identity from .chainloop.yml; attestation is pushed on 'git push'" >&2
run_plain "$@"
fi
# trace run mode: .chainloop.yml is ignored by design, so the identity
# has to come from the kit args.
missing=""
if [ -z "$CHAINLOOP_ORG" ]; then missing="$missing chainloopOrg"; fi
if [ -z "$CHAINLOOP_PROJECT" ]; then missing="$missing chainloopProject"; fi
if [ -z "$CHAINLOOP_WORKFLOW" ]; then missing="$missing chainloopWorkflow"; fi
if [ -n "$missing" ]; then
die "trace run mode needs an explicit attestation identity ('chainloop trace
run' ignores .chainloop.yml by design), but these kit args are empty:$missing
Supply them at launch, e.g.
--kit-arg chainloopOrg=my-org --kit-arg chainloopProject=my-project
or initialize the repo for persistent tracing with 'chainloop trace init'."
fi
echo "[chainloop-trace] Recording this session to Chainloop (trace run)" >&2
echo "[chainloop-trace] org=$CHAINLOOP_ORG project=$CHAINLOOP_PROJECT workflow=$CHAINLOOP_WORKFLOW" >&2
exec chainloop trace run \
--org "$CHAINLOOP_ORG" \
--project "$CHAINLOOP_PROJECT" \
--workflow "$CHAINLOOP_WORKFLOW" \
--claude \
-- claude --dangerously-skip-permissions "$@"
mode: "0755"