Sign inSign up

digiticketsgroup/claude-sandbox:spike

Manifest digest

sha256:f4ed0dec429b8cb19179c43cacf2650d3e398a12d7deee14616e07419cb963b5

Last pushed

1 day by adamhardwickdt

Type

Sandbox Kit

Manifest digest

sha256:f4ed0dec429b8cb19179c43cacf2650d3e398a12d7deee14616e07419cb963b5

yaml
schemaVersion: "3"
displayName: DigiTickets Claude sandbox (spike)
description: Claude Code from Docker's published kit, with the estate's PHP toolchain and .env deny policy layered on top.
sourceUrl: https://bitbucket.org/digitickets/claude-containers
version: 0.0.0-spike
licenses:
    - SEE LICENSE IN https://github.com/anthropics/claude-code/blob/main/LICENSE.md
kind: workload
provides:
    - [email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/ca-certificates@20250419
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/iputils-ping@20240905
    - deb/[email protected]
    - deb/less@668
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
    - deb/[email protected]
capabilities:
    - group:
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[0]
              type: com.docker.sandbox/sbx@1
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[0]
    - group:
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[1]
              type: com.docker.sandbox/network-policy@1
              config:
                runtime:
                    allow:
                        - api.anthropic.com:443
                        - platform.claude.com:443
                        - downloads.claude.ai:443
                        - claude.com:443
                        - mcp-proxy.anthropic.com:443
                        - bridge.claudeusercontent.com:443
                        - deb.debian.org:80
                        - dhi.io:80
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[1]
    - group:
        name: Anthropic access
        description: Anthropic API access (API key or claude.ai OAuth)
        optional: true
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[2]
              type: com.docker.sandbox/credential@1
              name: Anthropic access
              config:
                apiKey:
                    inject:
                        - domain: api.anthropic.com
                          format: '%s'
                          header: x-api-key
                        - domain: mcp-proxy.anthropic.com
                          format: '%s'
                          header: x-api-key
                oauth:
                    credentialFile:
                        path: ~/.claude/.credentials.json
                        structure:
                            claudeAiOauth:
                                accessToken: '{{.AccessToken}}'
                                expiresAt: '{{.ExpiresAt}}'
                                refreshToken: '{{.RefreshToken}}'
                                scopes: '{{.Scopes}}'
                            primaryApiKey: '{{.PrimaryApiKey}}'
                    sentinels:
                        accessToken: sk-ant-oat01-proxy-managed
                        refreshToken: sk-ant-ort01-proxy-managed
                    tokenEndpoint:
                        host: platform.claude.com
                        path: /v1/oauth/token
                phase: runtime
                service: anthropic
              description: Anthropic API access (API key or claude.ai OAuth)
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[2]
    - group:
        description: Drive claude sessions programmatically
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[3]
              type: com.docker.sandbox/agent-sessions@1
              config:
                continue:
                    - --continue
                list:
                    - python3
                    - /opt/claude-sessions/interactive-sessions.py
                prompt:
                    - -p
                    - --
                    - '{{.Prompt}}'
                resume:
                    - --resume
                    - '{{.SessionID}}'
              description: Drive claude sessions programmatically
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[3]
    - group:
        description: Open claude's terminal UI on a chosen session
        optional: true
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[4]
              type: com.docker.sandbox/agent-interactive-sessions@1
              config:
                continue:
                    - --continue
                prompt:
                    - --
                    - '{{.Prompt}}'
                resume:
                    - --resume
                    - '{{.SessionID}}'
                sessionPicker:
                    - --resume
              description: Open claude's terminal UI on a chosen session
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[4]
    - group:
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[5]
              type: com.docker.sandbox/lifecycle@1
              config:
                install:
                    - command: |
                        set -e
                        ws="${WORKSPACE_DIR:-/}"
                        esc=$(printf '%s' "$ws" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g')
                        projects="\"/\": { \"hasTrustDialogAccepted\": true }"
                        [ "$ws" = "/" ] || projects="$projects, \"$esc\": { \"hasTrustDialogAccepted\": true }"
                        printf '%s\n' "{
                          \"bypassPermissionsModeAccepted\": true,
                          \"hasCompletedOnboarding\": true,
                          \"hasSeenAutoDefaultNudge\": true,
                          \"projects\": { $projects }
                        }" > /home/agent/.claude.json
                        chown agent:agent /home/agent/.claude.json
                      description: Seed Claude bypass/trust flags (root; overwrites image-shipped file)
                      env:
                        - WORKSPACE_DIR
                      user: "0"
                    - command: mkdir -p /home/agent/.claude && chown agent:agent /home/agent/.claude
                      description: Ensure ~/.claude is agent-owned before settings seed
                      user: "0"
                    - command: |
                        set -e
                        HELPER=''
                        if [ "${SBX_CRED_ANTHROPIC_MODE:-none}" != none ]; then
                          HELPER='  "apiKeyHelper": "echo proxy-managed",
                        '
                        fi
                        # Model selection from the create-phase args. The args'
                        # patterns admit no quote or backslash, so the expanded
                        # value is safe inside the JSON string. effortLevel accepts
                        # low/medium/high/xhigh; max and auto persist only through
                        # CLAUDE_CODE_EFFORT_LEVEL (settings.json rejects them).
                        model=''
                        effort=''
                        MODEL=''
                        EFFORT=''
                        ENV_BLOCK=''
                        [ -z "$model" ] || MODEL="  \"model\": \"$model\",
                        "
                        case "$effort" in
                          '') ;;
                          max|auto)
                            ENV_BLOCK="  \"env\": { \"CLAUDE_CODE_EFFORT_LEVEL\": \"$effort\" },
                        "
                            ;;
                          *)
                            EFFORT="  \"effortLevel\": \"$effort\",
                        "
                            ;;
                        esac
                        # Lifecycle hooks for Narwhal (see /usr/local/bin/sbx-claude-hook):
                        # Stop and StopFailure report a finished turn, Notification the
                        # moments Claude waits on the user, UserPromptSubmit the prompt
                        # that names the conversation (Claude Code lists sessions by id
                        # only). The script never fails, never writes stdout and runs
                        # async, so a session without the desktop app sees no delay and
                        # no hook error.
                        HOOK='{ "type": "command", "command": "/usr/local/bin/sbx-claude-hook claude", "timeout": 10, "async": true }'
                        HOOKS="  \"hooks\": {
                            \"Stop\": [ { \"hooks\": [ $HOOK ] } ],
                            \"StopFailure\": [ { \"hooks\": [ $HOOK ] } ],
                            \"UserPromptSubmit\": [ { \"hooks\": [ $HOOK ] } ],
                            \"Notification\": [ { \"matcher\": \"permission_prompt|idle_prompt|elicitation_dialog|elicitation_url_dialog|agent_needs_input\", \"hooks\": [ $HOOK ] } ]
                          },
                        "
                        # The statusLine command is the only place Claude Code exposes
                        # the live session (model, context window, cost, lines changed);
                        # sbx-agent-hook forwards it to Narwhal's status bar and prints
                        # nothing, so no status line is drawn in the terminal. A mixin that
                        # sets its own statusLine (e.g. claude-sbx-statusline) replaces
                        # this one and the app's session view goes quiet for that sandbox.
                        STATUSLINE="  \"statusLine\": { \"type\": \"command\", \"command\": \"/usr/local/bin/sbx-claude-hook claude-status\" },
                        "
                        printf '%s' "{
                          \"themeId\": 1,
                          \"alwaysThinkingEnabled\": true,
                        ${HELPER}${MODEL}${EFFORT}${ENV_BLOCK}${HOOKS}${STATUSLINE}  \"permissions\": { \"defaultMode\": \"bypassPermissions\" },
                          \"bypassPermissionsModeAccepted\": true,
                          \"skipDangerousModePermissionPrompt\": true
                        }
                        " > /home/agent/.claude/settings.json
                      description: Seed Claude settings.json from the surfaced auth mode
                      env:
                        - SBX_CRED_ANTHROPIC_MODE
                      user: agent
                    - command: |
                        set -e
                        [ -n "$MCP_GATEWAY_URL" ] || exit 0
                        export PATH="$HOME/.local/bin:$HOME/.claude/local:$PATH"
                        claude mcp add mcp-gateway "$MCP_GATEWAY_URL" \
                          --transport http \
                          --scope user \
                          --header "Authorization: Bearer $MCP_SENTINEL_TOKEN_NAME" || true
                      description: Register the sandbox MCP gateway (install-time, race-free)
                      env:
                        - MCP_GATEWAY_URL
                        - MCP_SENTINEL_TOKEN_NAME
                      user: agent
                startup:
                    - background: true
                      command:
                        - sh
                        - -c
                        - command -v apt-get > /dev/null 2>&1 && (apt-get update -qq -y > /dev/null 2>&1 || true)
                      description: Update apt package cache in background
                      user: "0"
                    - command: |
                        set -e
                        [ -n "$MCP_GATEWAY_URL" ] || exit 0
                        export PATH="$HOME/.local/bin:$HOME/.claude/local:$PATH"
                        claude mcp add mcp-gateway "$MCP_GATEWAY_URL" \
                          --transport http \
                          --scope user \
                          --header "Authorization: Bearer $MCP_SENTINEL_TOKEN_NAME" || true
                      description: Register the sandbox MCP gateway (startup fallback)
                      env:
                        - MCP_GATEWAY_URL
                        - MCP_SENTINEL_TOKEN_NAME
                      user: agent
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[5]
    - group:
        optional: true
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[6]
              type: com.docker.sandbox/agent-skills@1
              config:
                path: /home/agent/.claude/skills
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[6]
    - group:
        optional: true
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[7]
              type: com.docker.sandbox/agent-context@1
              config:
                contentFile: /usr/share/sandbox/kit/claude-sandbox/context.md.parts/0.md
                directory: /home/agent/.claude
                filename: CLAUDE.md
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[7]
    - group:
        optional: true
        capabilities:
            - source:
                kit: docker.io/sbx/claude:2.1.318
                path: capabilities[8]
              type: com.docker.sandbox/agent-context@2
              config:
                content: |
                    ## Claude Code

                    Claude Code is installed at `~/.local/bin/claude` by Anthropic's native installer. Launch is
                    `claude --dangerously-skip-permissions`; the image entrypoint starts it. Run `claude` for an interactive session, or
                    `claude -p "<prompt>"` for a one-shot run.

                    Claude Code's permission prompts are turned off in `~/.claude/settings.json`
                    (`permissions.defaultMode = "bypassPermissions"`). That is deliberate: the container is the sandbox, and a prompt
                    nobody can answer just deadlocks a non-interactive session.

                    Authentication is proxy-mediated. Depending on what the host has bound, either the `apiKeyHelper` in
                    `~/.claude/settings.json` returns the literal string `proxy-managed` and the sandbox proxy substitutes the real
                    Anthropic API key on requests to `api.anthropic.com`, or `~/.claude/.credentials.json` holds claude.ai OAuth tokens
                    that are sentinels the proxy swaps at request time. Either way there is no real credential in this container. With
                    nothing bound, `claude` will ask you to `/login`.

                    Skills are read from `~/.claude/skills`, where the host's shared skills store mounts.

                    ## Installing Packages

                    The image ships a small toolset: shell and CLI tools, git, gh, Docker, and Python 3.14 with pip and uv. Install
                    anything else with `sudo apt-get update && sudo apt-get install -y <package>`; `sudo` needs no password. Two apt
                    repositories are configured and reachable through the sandbox proxy: Debian trixie (`deb.debian.org`) and Docker
                    Hardened Images (`dhi.io/deb/debian/main`). The DHI repository carries versioned toolchains such as `golang-1.26`
                    and `nodejs-24`; Debian supplies the rest, for example `default-jdk-headless`. `apt-cache search <term>` lists what
                    is available. Building Python packages from source needs `build-essential` first.

                    ## Environment Persistence

                    The `CLAUDE_ENV_FILE` environment variable is set to `/etc/sandbox-persistent.sh`.

                    According to [Claude Code Documentation](https://code.claude.com/docs/en/settings#bash-tool-behavior):

                    > **CLAUDE_ENV_FILE**
                    >
                    > If set, this file will be sourced before each Bash command execution. This allows environment variables to persist
                    > across multiple Bash tool invocations.

                    This means `/etc/sandbox-persistent.sh` is sourced before every Bash tool call you make. Any `export` statements you
                    append to this file will be available in all subsequent commands without needing a login shell.

                    ### Shell Completions Must NOT Be in CLAUDE_ENV_FILE

                    **NEVER add shell completion scripts to `/etc/sandbox-persistent.sh`.**

                    `CLAUDE_ENV_FILE` is sourced **before every single bash command execution**, not just during shell initialization.
                    Completion scripts rely on special variables (`COMP_WORDS`, `COMP_CWORD`, `COMPREPLY`) that only exist during
                    tab-completion contexts.

                    #### WRONG - Will Break Bash

                    ```bash
                    # DO NOT ADD THESE TO /etc/sandbox-persistent.sh
                    [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
                    [[ -s "$SDKMAN_DIR/etc/bash_completion.sh" ]] && source "$SDKMAN_DIR/etc/bash_completion.sh"
                    ```

                    #### CORRECT - Only Load Core Functionality

                    ```bash
                    # ONLY add the main initialization scripts
                    export NVM_DIR="$HOME/.nvm"
                    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

                    export SDKMAN_DIR="$HOME/.sdkman"
                    [[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]] && source "$SDKMAN_DIR/bin/sdkman-init.sh"
                    ```

                    #### Symptoms of Broken Shell

                    When completion scripts are incorrectly added:

                    - All bash commands return no output (silent failure)
                    - `echo`, `pwd`, and other basic commands produce no results
                    - The bash tool becomes completely unusable

                    #### Solution

                    If you accidentally added completion scripts and broke the shell:

                    1. Remove the completion line(s) from `/etc/sandbox-persistent.sh`
                    1. Exit and restart the Claude Code session
                    1. Verify with `echo "test"` that bash works again
                directory: /home/agent/.claude
                filename: CLAUDE.md
      declarationBlocks:
        - "1:0"
      source:
        kit: docker.io/sbx/claude:2.1.318
        path: capabilities[8]
    - group:
        capabilities:
            - source:
                kit: the set descriptor
                path: capabilities[0]
              type: com.docker.sandbox/network-policy@1
              config:
                runtime:
                    allow:
                        - packagist.org:443
                        - repo.packagist.org:443
                        - getcomposer.org:443
                        - registry.npmjs.org:443
                        - registry.yarnpkg.com:443
                        - nodejs.org:443
                        - codeload.github.com:443
                        - objects.githubusercontent.com:443
      declarationBlocks:
        - "2:0"
      source:
        kit: the set descriptor
        path: capabilities[0]
kits:
    - ref: docker.io/sbx/claude:2.1.318
      digest: sha256:929dfc4822ac9371198d384e5642477fdf55d1b3ea2a0a5f17eda75215fe6922
    - ref: docker.io/digiticketsgroup/claude-sandbox:spike-tools
      digest: sha256:c0e5ca61ac813c61e68cf4c4666bce1d692c4327c954485e26f96cdff357e310