GeezerVison is showing github.com — feat: sign git commits in workspaces with the Coder SSH key by matifali · Pull Request #32056 · coder/coder
Conversation
matifali
commented
Nov 11, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Closes #22371
Adds an opt-in Sign my commits with my Coder SSH key setting on Account > SSH keys, behind the unsafe git-commit-signing experiment. When it's on, every commit and tag made in the user's workspaces is signed with their Coder git SSH key, including commits made by AI agents running in the workspace. Users don't touch their git config, and no key or config file is written in the workspace.
This replaces the registry git-commit-signing module, which writes the private key to ~/.ssh/git-commit-signing/coder and edits the user's global git config.

How it works
- The user checks the box. It's stored as a
sign_commits_with_ssh_key user preference.
- When the agent connects, the manifest carries the user's public key. The agent sets
GIT_CONFIG_* env vars in new sessions: commit.gpgsign, tag.gpgsign, gpg.format=ssh, user.signingkey, and gpg.ssh.program pointing at the coder binary.
- On
git commit, git runs coder -Y sign .... The hidden gitsign-ssh command fetches the key with the same endpoint coder gitssh uses, signs in memory, and writes the .sig file git reads. The private key never touches disk.
Enable with CODER_EXPERIMENTS=git-commit-signing. Requires git 2.34 or later in the workspace.
Diagrams
Signing flow. One-time setup, what happens when the agent connects, and what happens on every commit:
sequenceDiagram
autonumber
actor U as User
participant UI as Coder UI
participant S as coderd
participant DB as Database
participant A as Workspace agent
participant G as git
participant C as coder binary (gitsign-ssh)
rect rgba(127,127,127,0.1)
Note over U,DB: One-time setup
U->>UI: Check "Sign my commits with my Coder SSH key"
UI->>S: PUT /api/v2/users/me/preferences
S->>DB: Save sign_commits_with_ssh_key = true
end
rect rgba(127,127,127,0.1)
Note over S,A: Each time the agent connects, for example after a restart
A->>S: GetManifest (dRPC, Agent API v2.17)
S->>DB: Experiment on? Read preference and public key
S-->>A: Manifest with git_signing_public_key
A->>A: Add 5 GIT_CONFIG_* env entries to new sessions
end
rect rgba(127,127,127,0.1)
Note over U,C: Every commit or tag
U->>G: git commit
G->>C: coder -Y sign -n git -f PUBKEY -U BUFFER
C->>S: GET /api/v2/workspaceagents/me/gitsshkey (agent token, same as coder gitssh)
S-->>C: Key pair
C->>C: Hash BUFFER with SHA-512 and sign SSHSIG in memory
C->>G: Write BUFFER.sig
G->>G: Embed signature in the commit object
end
Loading
Which sessions sign:
flowchart TD
start([New session in the workspace]) --> exp{git-commit-signing experiment on?}
exp -- No --> none[No signing config. git behaves as usual]
exp -- Yes --> pref{User checked Sign my commits?}
pref -- No --> none
pref -- Yes --> sign[GIT_CONFIG_* set: commit.gpgsign, tag.gpgsign, gpg.format=ssh, gpg.ssh.program=coder, user.signingkey]
sign --> commit[git commit and git tag are signed in the workspace]
Loading
Implementation details
- Setting: a new
sign_commits_with_ssh_key user preference in user_configs (no migration), exposed through the existing preferences API.
- Manifest: when the experiment and the setting are both on, the agent manifest carries the owner's public key in a new
git_signing_public_key field. The Agent API is bumped to v2.17 for it.
- Agent:
agent/agentgitsign injects the config through GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n, appended after any count the template already set.
- No wrapper:
gpg.ssh.program is the coder binary itself. Git runs it with ssh-keygen style arguments, and RunWithSubcommands sends any -Y ... invocation to the hidden gitsign-ssh command. No coder command takes -Y.
- Signing:
gitsign-ssh fetches the key pair with the existing GET /api/v2/workspaceagents/me/gitsshkey, parses it, hashes the payload with SHA-512, signs an SSHSIG blob in memory, and writes <payload>.sig. Verification operations pass through to ssh-keygen unchanged, exit code included.
- UI: the checkbox only appears when the experiment is enabled. The
* wildcard only enables safe experiments.
Known limitations
- Key exposure: signing happens in the workspace with the same key the agent can already fetch for
git push/pull. This adds no new exposure, but it doesn't keep the key out of the workspace either; that would need a separate signing key no endpoint returns.
- Precedence: environment config overrides the user's own signing config, including
commit.gpgsign=false. This is acceptable for an opt-in setting.
- Timing: the agent reads the setting when it connects, so turning it on or off takes effect after the next agent connect, for example a workspace restart.
- Key rotation: rotating the key (
coder publickey --reset) means new commits need the new key registered with the git provider.
- Windows: expected to work, since nothing in the path is OS-specific, but untested.
- Docs: none, because unsafe experiments don't need docs under
docs/.style/content-guidelines.md.
Testing
- Signer:
agent/agentgitsign SSHSIG signatures verify with ssh-keygen -Y verify for ed25519, ECDSA and RSA. A signature made for git is rejected under another namespace.
- CLI: tests cover signing end to end against the existing
gitsshkey endpoint (verified with ssh-keygen), verification pass-through, argument parsing and -Y dispatch.
- Manifest: tests cover gating on the experiment and the setting.
- Agent: tests check the env block and that an existing
GIT_CONFIG_COUNT is preserved.
- Frontend: a Vitest test for the toggle, plus Storybook stories.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add an opt-in "Sign my commits with my Coder SSH key" setting on
Account > SSH keys, behind the unsafe git-commit-signing experiment.
When enabled, the agent configures commit and tag signing through
GIT_CONFIG_* environment variables and points gpg.ssh.program at the
coder binary. `coder gitsign-ssh` hashes the payload and asks a new
agent-only endpoint, POST /api/v2/workspaceagents/me/experimental/gitsign,
to sign the digest with the owner's git SSH key on the server. No file is
written in the workspace.
Sessions opened with ssh -A or coder ssh -A bring the user's own keys, so the agent strips its GIT_CONFIG_* signing entries there and the user's git config applies. Also bump the Agent API to v2.17 for the git_signing_public_key manifest field.
Correct comments that claimed the private key never reaches the workspace, note the experiment gate on the manifest field, and drop a preference setup the signing endpoint no longer reads.
Remove the experimental gitsign endpoint and its SDK types. coder
gitsign-ssh now fetches the key pair with the existing agent gitsshkey
endpoint, as coder gitssh does, and signs the SSHSIG blob in memory, so
the private key is never written to disk. The signer moves to
agent/agentgitsign.
Drop the carve-out that skipped signing in sessions that forward an SSH
agent, and correct the minimum git version for SSH signing to 2.34.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation check for this round of commits. The git-commit-signing experiment is unsafe, and the only docs touched are auto-generated API reference, so review focuses on the Go source comment that generates it.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
CollapseAssistantSteps bool `json:"collapse_assistant_steps"`
|
|
|
AgentChatSendShortcut AgentChatSendShortcut `json:"agent_chat_send_shortcut"`
|
|
|
// SignCommitsWithSSHKey signs git commits and tags in the user's
|
|
|
// workspaces with their Coder git SSH key.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generated API description says the setting "signs git commits and tags" unconditionally, but it only has an effect when the unsafe git-commit-signing experiment is enabled: coderd/agentapi/manifest.go only reads this preference and puts the public key on the manifest when a.Experiments.Enabled(codersdk.ExperimentGitCommitSigning), and agent/agent.go only appends the GIT_CONFIG_* signing entries when manifest.GitSigningPublicKey != "". A reader of the API reference (docs/reference/api/schemas.md) could enable the flag on a deployment without the experiment and wrongly conclude commits are now signed. (Fact Checker, Newcomer)
|
// workspaces with their Coder git SSH key. |
|
// SignCommitsWithSSHKey signs git commits and tags in the user's |
|
// workspaces with their Coder git SSH key when the git-commit-signing |
|
// experiment is enabled. |
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
CollapseAssistantSteps bool `json:"collapse_assistant_steps"`
|
|
|
AgentChatSendShortcut AgentChatSendShortcut `json:"agent_chat_send_shortcut"`
|
|
|
// SignCommitsWithSSHKey signs git commits and tags in the user's
|
|
|
// workspaces with their Coder git SSH key.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocking: This names the key "Coder git SSH key" (lowercase "git"), but every other docs page that names the same key calls it "Coder Git SSH key" (capital "Git"): docs/admin/external-auth.md:152 ("View your Coder Git SSH key:") and docs/workspaces/shared-workspaces.md:150,200. Per docs/.style/style-guide/word-choice.md#one-term-per-concept (One term per concept), pick one name for the thing and use it everywhere. This generated description introduces a second casing for the same key. (Continuity Editor)
|
// workspaces with their Coder git SSH key. |
|
// workspaces with their Coder Git SSH key. |
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This branch was previously deployed
1 inactive
deployment
Reviewers
spikecurtis
Awaiting requested review from spikecurtis
spikecurtis is a code owner
mafredri
Awaiting requested review from mafredri
mafredri is a code owner
1 more reviewer
Reviewers whose approvals may not affect merge requirements
At least 1 approving review is required to merge this pull request.
Uh oh!
There was an error while loading. Please reload this page.
Labels
experimental
Changes that might not necessarily be merged, until its approved to proceed with.
Milestone
No milestone
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.
|