getRemoteURL, getEphemeralRemoteURL, getImportRemoteURL) and by every
per-operation REST call (createBranch, merge, createCommit, notes, tags, and so on).
Available policies
Per-ref rules
TherefPolicies option is an ordered list of (pattern, ops) entries, and the first match wins.
Pattern syntax
A pattern is either an exact ref name or a prefix glob: a single trailing* that follows a /, or
the standalone *. There is no segment-by-segment matching. refs/heads/feature/* matches
everything under refs/heads/feature/, including nested paths like refs/heads/feature/a/b.
Branch shorthand is supported: a pattern that does not start with refs/ (and is not exactly * or
HEAD) is normalized to refs/heads/<pattern> before matching. So main becomes
refs/heads/main, and agents/* becomes refs/heads/agents/*. Use the fully qualified form when
you want to match tags (refs/tags/*) or any other non-branch ref.
An entry with no
ops (or an empty ops list) is an explicit passthrough: matching refs are
permitted with no policies enforced. An entry with ops: ["no-push"] is a deny for matching
refs.
Put the * catch-all last
refPolicies is first-match-wins, so a rule with pattern * (or any other broad glob) must be
the last entry for the more specific rules above it to matter. If you put * first, every other
rule below it is dead code.
Examples
Disable force pushes everywhere
Applyno-force-push to every branch with a single catch-all rule.
Allow agents to push only to agents/*
Hand an autonomous agent a token that can push to agents/* (branch shorthand for
refs/heads/agents/*) and nothing else.
Lock down main, allow everything else
A more permissive policy: main is read-only, but any other branch or tag is writable. Branch
shorthand expands main to refs/heads/main.
Apply the same policy to a REST call
refPolicies is accepted by every ref-mutating REST method (createBranch, deleteBranch,
createTag, deleteTag, merge, createCommit, createCommitFromDiff, restoreCommit,
pullUpstream, createNote / appendNote / deleteNote). For note writes, the policy is
evaluated against the selected notes ref. Define the policy once and reuse it:
Commit signing verifications
Theverify-sig op rejects a push unless every commit it introduces is signed by a key you have
registered ahead of time. “Introduced” means commits reachable from the new ref tip but not from
that protected ref’s own pre-push tip, so a fast-forward that adds three commits has all three
checked, while re-advancing the ref to commits it already contained verifies nothing.
The comparison is against the protected ref’s prior tip, not against other refs in the repo. A
commit that already exists elsewhere is still checked the first time you bring it onto the protected
ref, so you cannot bypass the policy by pushing an unsigned commit to an unprotected ref and then
advancing the protected ref to that same commit.
Both OpenPGP and SSH signing formats Git supports are accepted matching git config gpg.format.
To inspect a signature yourself, getCommit() returns the commit’s
armored signature along with the exact payload the signature was computed over, so you can
verify it independently of the push-time check.
Register signing keys
verify-sig checks each signature against the set of public keys you have registered. Manage them
in the dashboard under Signing Keys. The keyring is read fresh on every push, so deleting a key
takes effect immediately on the next push.
What gets rejected
When averify-sig-covered push fails, Git reports signature verification failed and the storage
logs name the offending commit and ref (verify-sig denied refs/heads/main at <sha>: <reason>). A
commit is rejected when:
- the commit is not signed,
- it is signed but no registered key matches the signature, or
- there are no registered signing keys at all.
Example: Require signed commits on main
ops: ["verify-sig", "no-force-push"].
Where policies apply
Policies are evaluated on all ref-updating paths, including:git pushover HTTPS- Ref-mutating REST methods
Ephemeral namespace quirk
Writes that go through the ephemeral namespace reach storage under a rewritten ref name. This applies to pushes togetEphemeralRemoteURL() and to REST calls with ephemeral: true /
targetIsEphemeral: true. refs/heads/main becomes refs/namespaces/ephemeral/refs/heads/main.
Patterns are matched against that rewritten name, so pattern: "main" or pattern: "refs/heads/*"
does not cover the ephemeral copy of a branch.
To lock down a specific ephemeral ref, spell the rewritten path:
* (which matches refs in any namespace) when you want one rule to cover both:
Legacy: ops on URL methods
The URL-generating methods (getRemoteURL, getEphemeralRemoteURL, getImportRemoteURL) also
accept a top-level ops array. It predates refPolicies and is preserved for compatibility. On
verify, the gateway folds it into the catch-all * rule, merging into an existing * entry in
refPolicies if one is present, or appending a new trailing * rule otherwise. A more specific
refPolicies match still wins.
Prefer refPolicies for new code. It covers every method that takes a policy and makes the
catch-all behavior explicit. If a token carries both, only the first match counts on a given ref.
The two are not additive.