Skip to content

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.

What the broker says to one pushsynthetic
Resolved profile
Branch
Push
Validation gate for acme
Verdict for 7be0d13a2c
Validation gate refused itREFUSED
$ 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Each push is labelled. After GitHub accepts it, the host lists the pushed commits, classifies each as agent, human-console or unattributed, adds the AI activity seen in the workspace since its last push, and appends a signed push.authorship record to the git audit stream.

Defaults

Settings for git and provenance
SettingScopeDefaultAllowed
git_credential in the profileper profilebroker under guarded and strict; container under open and meteredcontainer, broker
git_broker_enabledplatformtruetrue, false; restart the git broker
git_validation_gate_enabledplatform, organisationfalse: off until an admin turns it ontrue, false
agent_check_allowlistplatform, organisationempty: the gate has no approved checks to runcheck names, e.g. test,lint
git_branch_patternplatformfeature/{user}/*, enforced under strict onlya ref glob
git_token_ttlplatform3600 seconds300 to 3600
git_ticket_ttlplatform300 seconds30 to 3600
Authorship recordplatformon for every push through the broker, while audit_enabled is onno setting of its own
agent_vcs_enabledplatform, organisationfalse: off until an admin turns it ontrue, false
vcs_push_min_profile, vcs_pr_min_profileplatform, organisationoff: no agent pushes or opens a pull requestoff, supervised, assisted, autonomous
vcs_max_pushes_per_turnplatform, organisation (lower only)10 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.

on the host
# 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/web

You 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.

on the host
$ 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_enabled

Hold 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.

on the host
$ 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/main

Tasks

Validate a commit and push it

  1. Commit. In the workspace's Git panel, or let the agent commit. Result: the host records the commit's origin.
  2. 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.
  3. Push. Result: the broker finds the passing verdict and forwards the push; the allow record in the git stream carries the verdict's id.
  4. 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

  1. Query the git stream for authorship records.
    on the host
    $ bin/audit-log query --stream git --action push.authorship --workspace acme/dev01 --json
  2. Read the classes. classes counts the pushed commits by kind, and mixed is true when they differ. Each commit under commits carries its own kind, and for an agent the run, the approver and the model the run reported.
  3. Read the AI activity beside it. ai_activity lists 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.
  4. Prove it was not edited. bin/audit-log verify checks the hash chain and every authorship signature on this host.
push.authorship, trimmedsynthetic
{
  "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

  1. 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.
  2. 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.
  3. Choose who may open a pull request. Set vcs_pr_min_profile the same way. The request is made by the broker, and the App needs Pull requests access.
  4. 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

  1. Read the prefix. Git prints refused by branch policy: or refused by validation gate: followed by the reason. The console's Git panel shows the same sentence with its remedy.
  2. Read the audit record. bin/audit-log query --stream git --action push --decision deny --workspace acme/dev01. Result: the reason code, such as validation-missing, validation-failed or validation-other-repo.

Verify it works

on the host
$ 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, never human.
  • 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.

Verified against the main branch, 28 Sep 2026.