git attestor
Captures the state of the git repository at the working directory — HEAD commit, author/committer identity, refs, remotes, tags pointing at HEAD, parent commits, tree hash, and worktree/staging dirtiness.
| Name | git |
|---|---|
| Predicate type | https://aflock.ai/attestations/git/v0.1 |
| Lifecycle | prematerial |
| Default binary? | Yes |
| Category | source-checkout (primary) |
| Recommended trace | off — no syscall tracing needed |
| Auto-attaches when |
|
The facts in this box are generated from the CI/lock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.
What it captures
The attestor opens the repo with go-git (using DetectDotGit, so it walks up from --workingdir to find .git) and records:
commithash— SHA-1 of HEAD commit. Also emitted as acommitdigestDigestSet(sha1).author/authoremail—commit.Author.Name/.Email.committername/committeremail—commit.Committer.Name/.Email.commitdate— author timestamp (time.Time.String()format).commitmessage— full commit message.signature— PGP signature on the commit, if present.parenthashes— SHA-1s of parent commits (multiple for merges).treehash— SHA-1 of the root tree.refs— every ref (branches, remote-tracking, tags) whose target equals HEAD.branch— short ref name of HEAD (json key isbranch, struct fieldRefNameShort).remotes— configured remote URLs, with embedded user-info (tokens) stripped.tags— annotated tag objects pointing at HEAD:name,taggername,taggeremail,when(RFC3339),pgpsignature,message.status— map of dirty files to{staging, worktree}codes:unmodified,untracked,modified,added,deleted,renamed,copied,updated.gittool—"go-git+git-bin"when thegitbinary is onPATH, else"go-git".gitbinpath/gitbinhash— path and digest of thegitbinary (only when present). Hash uses the attestation context's configured hashes.
If HEAD cannot be resolved (reference not found — e.g. a brand-new repo with no commits), the attestor returns no error and emits an empty predicate rather than failing the run.
When to use
Almost every CI step that runs inside a git checkout. The captured commithash is the canonical SLSA source identifier most release policies pin against, and the parenthash subjects make the resulting collection traversable along the commit-graph during policy evaluation.
Keyless commit and tag signing
The same cilock binary implements Git's standard X.509 signing protocol. Configure the current repository once:
cilock git configure
git commit -S -m "feat: signed by CI/lock"
git tag -s v1.2.3 -m "v1.2.3"
git verify-commit HEAD
git verify-tag v1.2.3
CI/lock defaults to the hosted TestifySec platform. It exchanges the current stored session or registered workflow identity for a short-lived Fulcio certificate, signs with an ephemeral in-memory key, and requires an RFC 3161 timestamp from the platform TSA. Private installations use the same protocol with the organization's TestifySec appliance. No signing flag, platform flag, separate gitsign executable, or credential-cache daemon is required.
This covers the everyday gitsign-compatible Git surface: signed commits, signed annotated tags, git log --show-signature, git verify-commit, and git verify-tag. CI/lock is designed for an organization's private trust infrastructure, so it uses platform discovery and pinned private trust instead of gitsign's public-community TUF/Rekor configuration. CI/lock's existing run, attest, and verify commands remain the attestation and policy interface; it does not duplicate gitsign's source-predicate subcommands.
Flags
None. The attestor is configured entirely by the working directory; there are no cobra flag registrations.
Output shape
{
"gittool": "go-git+git-bin",
"gitbinpath": "/usr/bin/git",
"gitbinhash": { "sha256": "..." },
"commithash": "9f1c...",
"author": "Jane Dev",
"committername": "Jane Dev",
"commitdate": "2026-05-20 12:34:56 +0000 UTC",
"commitmessage": "fix: ...",
"commitdigest": { "sha1": "9f1c..." },
"signature": "-----BEGIN PGP SIGNATURE-----\n...",
"parenthashes": ["abcd..."],
"treehash": "1234...",
"refs": ["refs/heads/main", "refs/remotes/origin/main"],
"remotes": ["https://github.com/org/repo"],
"tags": [{"name": "v1.2.3", "taggername": "...", "taggeremail": "...", "when": "2026-05-20T12:34:56Z", "pgpsignature": "...", "message": "..."}],
"branch": "main",
"status": {"path/to/file": {"staging": "modified", "worktree": "unmodified"}}
}
Emitted subjects: commithash:<sha1>, authoremail:<email>, committeremail:<email>, parenthash:<sha1> (one per parent), refnameshort:<branch>, remote:<url>. BackRefs() exposes commithash: and parenthash: with sha1 digests.
Gotchas
- Parent-hash chain semantics.
parenthash:<sha>subjects use the raw SHA-1 of the parent commit — the same encoding used forcommithash:<sha>. This is deliberate (issue #34, PR #37): a downstream collection'sparenthashsubject digest now matches the upstream collection'scommithashsubject digest for the same commit, so policy can traverse the parent chain across collections via subject-graph matching. Earlier builds hashed the string with sha256, which broke the join. - Status detection has two modes. If the
gitbinary is onPATH, status comes fromgit status --porcelain; otherwise it uses go-git's worktree status. The two implementations are not always byte-identical for edge cases (submodules, ignored files). - Shallow clones / missing refs.
refsonly contains refs the local repo knows about. CI checkouts that fetch a single SHA may produce an emptybranchand minimalrefs. Tags must be fetched explicitly (fetch-tags) to appear intags. - Submodules are not traversed — only the outer repo's HEAD is recorded.
- Remote URL scrubbing. URLs are parsed and
Useris cleared before recording, sohttps://x-access-token:[email protected]/...becomeshttps://github.com/.... If parsing fails the raw URL is recorded — review remotes if you've put credentials in non-URL syntax. - Dirty trees still attest. A non-empty
statusmap does not fail the attestor; downstream policy must reject dirty builds explicitly if that is the requirement. - Signing and attesting are distinct. The Git signature authenticates the proposed commit or tag. The
gitattestor records repository state inside signed in-toto evidence. Pushgate policy can require both without treating either as a substitute for the other.
CLI example
Real .git/ discovery. Reads HEAD commit, branch, tag, parent SHA, author, signature.
cilock run --step git-context \
--signer-file-key-path key.pem --outfile attestation.json --workingdir . \
--attestations git \
-- echo "captured git context"
Validated against a real git checkout (rookery). Captures real commit metadata. See the full real-data example at https://github.com/aflock-ai/attestor-compliance-examples/tree/main/05-git.
See also
- Catalog row
- Upstream: witness/git.md