Git and provenance
The broker holds the token. Every push says who wrote it. This page sets up the git broker, the validation gate and the authorship record, and shows how to read what they decided.
$ git push origin feature/dev01/login ! [remote rejected] feature/dev01/login -> feature/dev01/login (refused by validation gate: commit 7be0d13a2c has not been validated. Use Validate this commit in the Git panel, then push again) error: failed to push some refs audit git push deny acme/dev01 acme/api reason=validation-missing
The broker looked up the verdict for this exact commit and repository. Nothing was forwarded upstream; the refusal and its reason are in the git audit stream.
Change the inputs. The sentences are the broker's own, in the order it checks: the branch rule first, then the gate. Git's output is trimmed to the lines that matter.
What it is, and what it is not
Under the default guarded profile, git needs no credential inside the workspace. A broker on your host holds your GitHub App's key and knows which workspace is asking from the socket the request arrives on. Every push it allows is recorded with its author: an agent, a person in the console, or unattributed when the host has no record. An optional validation gate refuses a push unless the exact commit has passed your checks.
It is not signed, third-party-verifiable provenance in the SLSA or Sigstore sense, and it does not see a laptop. It works with GitHub only.
How it works
- Identity is the socket. Each brokered workspace gets its own unix socket, mounted into that one container. A workspace cannot name another one, because it was never given that path.
- The container holds a ticket, not a token. Git's credential helper answers only for the broker and hands git a ticket that lives minutes and only the broker accepts. For GitHub itself it declines outright, so git never prompts.
- The broker reads the push. It is a smart-HTTP proxy, so it sees the ref updates in the push itself, checks the branch rule and the gate there, and mints a per-repository installation token that lives in its memory for one operation.
- The host records where commits came from. When the agent commits, or a person commits from the console's Git panel, the host stores that commit's origin. A commit made anywhere else has no record.
- Each push is labelled. After GitHub accepts it, the host lists the pushed commits, classifies each as
agent,human-consoleorunattributed, adds the AI activity seen in the workspace since its last push, and appends a signedpush.authorshiprecord to the git audit stream.
Defaults
| Setting | Scope | Default | Allowed |
|---|---|---|---|
git_credential in the profile | per profile | broker under guarded and strict; container under open and metered | container, broker |
git_broker_enabled | platform | true | true, false; restart the git broker |
git_validation_gate_enabled | platform, organisation | false: off until an admin turns it on | true, false |
agent_check_allowlist | platform, organisation | empty: the gate has no approved checks to run | check names, e.g. test,lint |
git_branch_pattern | platform | feature/{user}/*, enforced under strict only | a ref glob |
git_token_ttl | platform | 3600 seconds | 300 to 3600 |
git_ticket_ttl | platform | 300 seconds | 30 to 3600 |
| Authorship record | platform | on for every push through the broker, while audit_enabled is on | no setting of its own |
agent_vcs_enabled | platform, organisation | false: off until an admin turns it on | true, false |
vcs_push_min_profile, vcs_pr_min_profile | platform, organisation | off: no agent pushes or opens a pull request | off, supervised, assisted, autonomous |
vcs_max_pushes_per_turn | platform, organisation (lower only) | 1 | 0 to 4 |
Under open or metered, the token is in the container. Those profiles set git_credential to container, which mounts a reusable GitHub token the developer can read. Stay on guarded or strict wherever the broker should be the only way to GitHub.
Configure it
Give the broker a GitHub App
The broker mints its tokens from a GitHub App you create and install on the repositories you choose. Connect it per organisation in the console, under the organisation's Workspaces & repo access tab, on the Organisation GitHub App card: App ID, Installation ID and the private key. The key is stored root-owned with mode 0600 and never enters a container. The integrations page lists the permissions the App needs.
# plan a mint without reading the key or touching the network
$ bin/gh-app-token --repo acme/api --dry-run
# is the broker up, and which workspaces does it serve?
$ bin/git-broker status
# register a workspace's repositories: --repo lists the WHOLE set every time
$ bin/git-broker add --tenant acme --workspace dev01 \
--user dev@example.test --repo acme/api --repo acme/webYou rarely run add by hand: bin/workspace-agent registers a brokered workspace's repositories when it is created or its repositories change. The broker runs as one process per host, started with the console; bin/git-broker status names the process that is actually serving.
Turn on the validation gate
The gate runs the checks an organisation has approved, by name, from the commit's own package.json or Python project files. Approve the checks first, then turn the gate on. Organisation admins do both in the organisation console: the Checks tab for the approved list, and the settings for the gate.
$ bin/policy set --scope tenant --target acme --key agent_check_allowlist --value test,lint
$ bin/policy set --scope tenant --target acme --key git_validation_gate_enabled --value true
$ bin/policy get-for --tenant acme --key git_validation_gate_enabledHold pushes to per-person branches
The branch rule applies only where the resolved profile is strict. {user} becomes the developer's slug, so one person cannot push over another's branch.
$ bin/policy set --scope tenant --target acme --profile strict
$ bin/policy set --key git_branch_pattern --value 'feature/{user}/*'
# would this ref pass the branch rule? decides nothing, changes nothing
$ bin/git-broker check --tenant acme --workspace dev01 --ref refs/heads/mainTasks
Validate a commit and push it
- Commit. In the workspace's Git panel, or let the agent commit. Result: the host records the commit's origin.
- Press Validate this commit. The host copies that commit's files out read-only, installs dependencies fresh from its lockfile, and runs the approved checks with the container's exit code as the result. Result: a verdict of passed or failed, bound to this commit and this repository.
- Push. Result: the broker finds the passing verdict and forwards the push; the allow record in the git stream carries the verdict's id.
- If it failed, the verdict's summary names the check. Fix, commit and validate again: a new commit needs a new verdict.
Read who wrote a push
- Query the git stream for authorship records.
$ bin/audit-log query --stream git --action push.authorship --workspace acme/dev01 --json - Read the classes.
classescounts the pushed commits by kind, andmixedis true when they differ. Each commit undercommitscarries its own kind, and for an agent the run, the approver and the model the run reported. - Read the AI activity beside it.
ai_activitylists model requests and AI API connections from this workspace since its previous push. It is activity in a window, never a claim about any one commit. - Prove it was not edited.
bin/audit-log verifychecks the hash chain and every authorship signature on this host.
{
"ts": "2026-09-28T10:02:41+00:00",
"stream": "git",
"action": "push.authorship",
"decision": "info",
"workspace": "acme/dev01",
"user": "dev@example.test",
"reason": "agent",
"details": {
"ref": "refs/heads/feature/dev01/login",
"old": "41c9e02…",
"new": "7be0d13…",
"authorship": {
"commits": {
"7be0d13…": {
"kind": "agent",
"source": "chat-tool",
"run": "ledger:318",
"approver": "dev@example.test",
"actor": "dev@example.test",
"model_reported": "claude-sonnet-5"
}
},
"range": "complete (old..new)",
"classes": { "agent": 1 },
"mixed": false,
"ai_activity": {
"observed": true,
"claude_proxy_requests": 14,
"models": { "claude-sonnet-5": 14 },
"ai_api_connections": {},
"window": { "basis": "since this workspace's previous push" }
}
}
},
"authorship_sig": { "key_id": "3f9a0c2d7e81b654", "hmac": "…" },
"h": "…"
}Let the agent push and open pull requests
- Turn on agent git for the organisation.
bin/policy set --scope tenant --target acme --key agent_vcs_enabled --value true. Result: reading, writing, committing and branching follow their own minimum profiles. - Choose who may push.
bin/policy set --scope tenant --target acme --key vcs_push_min_profile --value autonomous. Result: only members resolved to autonomous may push; everyone else is refused at the tool. - Choose who may open a pull request. Set
vcs_pr_min_profilethe same way. The request is made by the broker, and the App needs Pull requests access. - Keep the gate on. An agent's push meets the same branch rule and validation gate as a person's, and a person approves before anything leaves the host.
Find out why a push was refused
- Read the prefix. Git prints
refused by branch policy:orrefused by validation gate:followed by the reason. The console's Git panel shows the same sentence with its remedy. - Read the audit record.
bin/audit-log query --stream git --action push --decision deny --workspace acme/dev01. Result: the reason code, such asvalidation-missing,validation-failedorvalidation-other-repo.
Verify it works
$ bin/git-broker status
$ bin/policy resolve --user dev@example.test --repo acme/api --tenant acme
$ bin/audit-log verify
every chain verifies - no record has been edited or removed
authorship: 12 signed and verified, 0 unsigned (written before signing, or with no key available)Good looks like: resolve shows git_credential broker for your developers; inside a workspace, git push works with no token prompt; and verify reports every authorship block as signed and verified. A push from a guarded workspace to a repository it was not granted is refused with the repositories it is authorised for.
Troubleshooting
commit … has not been validated. Use Validate this commit in the Git panel, then push again
The gate is on and there is no verdict, or it is still running. Validate, wait for passed, push again.
commit … was validated against acme/web, not acme/api
A verdict is bound to the repository it measured. Validate the commit for the repository you are pushing to.
deleting … is refused while validation is required
There is no commit to validate. An organisation admin deletes the branch on GitHub.
validation service unreachable
The broker could not read the verdict store, so it refused rather than guess. Check the console is running, then push again.
… does not match the allowed pattern 'feature/{user}/*' for profile strict
Push to a branch that matches the pattern, or ask an admin whether this repository should be strict.
this workspace is authorised for …, not …
The repository is not granted to this workspace. An organisation admin grants it under Workspaces & repo access.
the App is not installed on acme/api (no installation id)
Install the GitHub App on that repository in GitHub, then load repositories again in the console.
authorship signed by key … which is not on this host
verify cannot check older signatures without their key. Restore the audit directory's authorship-keys from the same backup as the log.
What it does not do yet
- Third-party-verifiable signatures. The record is an HMAC with a key on your host: an auditor verifies it there, a third party cannot, and the host's root user could forge it.
- It cannot see a laptop. A commit made outside the console, then pushed from the workspace, reads
unattributed, neverhuman. - GitLab, Bitbucket, Azure DevOps. GitHub only.
- A live push on record. The authorship record is deployed; no live GitHub push has been recorded with it yet.
- A recorded live push through the gate. The gate is built and on for no real organisation yet; it has not been seen on a live GitHub push.
- A separate protected-branch list. The only rule is the ref pattern, and only under strict.
Related
Verified against the main branch, 28 Sep 2026.