Sign inSign up

sbx/git-ssh-sign-kit

Verified Publisher

By Docker, Inc

Updated 8 days ago

Configures git to sign commits using the SSH key forwarded from the host's SSH agent.

Sandbox Kit
0

5.9K

sbx/git-ssh-sign-kit repository overview

Digest

sha256:11e5a8e3ebc9…

Size

2.2 kB

Schema

v2

Pushed

8 days ago

Specificationspec.yaml

MIXIN

Configures git to sign commits using the SSH key forwarded from the host's SSH agent.

Apply this mixin to a sandbox

sbx run <agent> --kit docker.io/sbx/git-ssh-sign-kit:latest

Make sure you have docker sbx installed

Run the following command to install sbx on your machine.

macOS
brew install docker/tap/sbx
Windows
winget install Docker.sbx
Learn more about docker sbx

git-ssh-sign

A mixin that configures git to sign commits and tags using the SSH key forwarded from your host's SSH agent. Works with any agent kit (claude, codex, cursor, etc.).

Sandboxes forward your host's SSH agent automatically — the private key stays on your host. See Signed commits for the underlying mechanism this kit builds on.

Prerequisites

On the host, load your SSH key into the agent:

ssh-add ~/.ssh/id_ed25519

Then start the sandbox with the kit attached, from its published OCI artifact on Docker Hub:

sbx run claude --kit "docker.io/sbx/git-ssh-sign-kit:latest" ~/my-project

Or from a git URL targeting this repo:

sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=git-ssh-sign" ~/my-project

Inside the sandbox, verify that the forwarded agent exposes your key:

ssh-add -L
ssh-ed25519 AAAA... [email protected]

If it returns nothing, the key isn't loaded on the host yet — re-run ssh-add there and try again. Git signing fails loudly, with a message naming the exact state and its recovery; see When signing fails.

Verifying

git log --show-signature -1
commit abc1234...
Good "git" signature for [email protected] with ED25519 key SHA256:...

When signing fails

Git surfaces a failed signature as two lines:

warning: gpg.ssh.defaultKeyCommand failed: [git-ssh-sign] ...
error: user.signingKey needs to be set for ssh signing

The second line is git's, not this kit's, and it is misleading — the kit leaves user.signingKey unset on purpose. Git prints the key command's stderr verbatim in the warning: line above it, so the [git-ssh-sign] lines are the ones that name the real state and its fix.

Inside the sandbox, confirm which state you are in:

echo "$SSH_AUTH_SOCK"      # always set in a sandbox — proves nothing on its own
test -S "$SSH_AUTH_SOCK"   # the real check: is the relay socket present?
ssh-add -l                 # 0 = keys loaded; 1 = connected but no usable key; 2 = no connection

SSH_AUTH_SOCK is exported into every sandbox whether or not agent forwarding was actually wired, so a set variable and a missing socket are the common failure — the in-sandbox relay is not running.

Recovery happens on the host:

  1. Check that forwarding is enabled at all:

    sbx settings get ssh.agentForwardingEnabled   # must be true
    sbx settings set ssh.agentForwardingEnabled true
    

    Check this before anything else. While it is false the forwarded agent is never wired up, so ssh-add in the sandbox reports a broken agent (exit 1 or 2, depending on how far the connection gets) rather than a disabled feature, and it survives any number of key reloads and sandbox restarts. It defaults to true, so false means something turned it off, e.g. declining agent forwarding in sbx setup.

  2. Check the host agent holds the key: ssh-add -l, then ssh-add ~/.ssh/id_ed25519 if it does not.

  3. If step 1 changed the setting, restart the daemon: sbx daemon restart. Changes to ssh.agentForwardingEnabled and ssh.agentSocketPath only reach sandboxes that already exist once the daemon has restarted.

  4. From that same shell — the daemon adopts the requesting client's SSH_AUTH_SOCK — restart the sandbox's container so the in-container relay is relaunched:

    sbx stop <sandbox-name>
    sbx run --name <sandbox-name>
    

    Stop-then-run is the supported restart cycle; there is no sbx start. Substitute the sandbox's own name — hostname prints it inside the sandbox, but $SANDBOX_NAME is not set in a host shell, so type the literal name there.

To get one commit through unsigned while you sort that out:

git -c commit.gpgsign=false commit -m "..."

See also Docker's troubleshooting guide.

How it works

Git signing requires two things to be available when Git signs the commit: signing config (what format to use and how to resolve a key) and the actual key material from the forwarded SSH agent.

Signing machinery — written at install time to /etc/gitconfig

The install command writes gpg.format, gpg.ssh.defaultKeyCommand, and gpg.ssh.allowedSignersFile to the system-level git config, and makes sure user.signingKey stays unset. This file is read by git at process startup and is never overwritten by the sandbox infrastructure, so the config is always present when git commit begins.

user.signingKey has to stay empty: git consults gpg.ssh.defaultKeyCommand only when it is unset. Setting it to any value — even a placeholder meant to produce a nicer error — disables dynamic key resolution outright and breaks the working case.

Signing policy — scoped to repositories that have a remote

commit.gpgSign and tag.gpgSign are not in /etc/gitconfig. They live in /etc/git/signing-enabled.inc, pulled in conditionally:

[includeIf "hasconfig:remote.*.url:**"]
	path = /etc/git/signing-enabled.inc

A machine-wide commit.gpgSign = true makes every git commit in the sandbox depend on a live SSH agent — including throwaway repositories that test suites create with git init and that have nothing to do with your commits. When the forwarded agent goes away, those all start failing too, with an error that points at user.signingKey. Scoping by "has a remote" keeps automatic signing on in the repositories you push from, and off in local scratch repositories. It is a proxy, not a guarantee: commits made in a fresh git init before the first git remote add stay unsigned and are pushed that way.

Two things this deliberately does not do:

  • It does not scope by gitdir: to the workspace path. A sandbox can mount more than one host directory, and repositories cloned inside the sandbox would fall outside any single workspace prefix.
  • It does not make signing failures soft. Inside the scope, a missing agent still fails the commit rather than quietly producing an unsigned one.

git commit -S still signs in any repository, scope or no scope, because the machinery above is machine-wide.

includeIf "hasconfig:…" needs git ≥ 2.36. The install command probes for it against a throwaway repository rather than parsing a version string; on an older git it falls back to machine-wide commit.gpgSign so the kit fails closed (signs too much) rather than open (signs nothing).

Key material — resolved at signing time

gpg.ssh.defaultKeyCommand runs /home/agent/.config/git/ssh-signing-key-command, a static script shipped under the kit's files/home/ tree. It is configured as /bin/sh <path>, not as a bare path: files delivered from a kit's OCI artifact land mode 0644 (per-file executable bits are not representable in a v2 layer, see spec/OCI-v2.md) and Git execs the key command itself, so a bare path would fail with cannot exec: Permission denied before any of the script's own diagnostics could run.

When Git needs a signing key, it runs that command. The command reads the first public key from ssh-add -L, writes /home/agent/.config/git/allowed_signers for signature verification, and prints the key in Git's inline key::... format.

This avoids writing key material at install or startup time, when the forwarded SSH agent may not be connected yet. It also avoids relying on Git hooks for signing.

Composing with repo-local hooks

This kit does not set core.hooksPath and does not install a pre-commit hook. Project-level hooks, hook managers, and repo-local core.hooksPath settings can run independently of commit signing.

Composing with github-ssh

To also enable SSH push/pull to GitHub from the sandbox, combine this kit with github-ssh:

sbx run \
  --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=git-ssh-sign" \
  --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=github-ssh" \
  claude