feat(github): record reviewed commits and signers, and check them in the four-eyes policy - #1244
AlexKantor87 wants to merge 3 commits into
Conversation
…was given on A policy can tell whether an approval still covers a pull request only by knowing which commit it was given on. Commit dates can't answer that: they come from the commit itself and can be set to anything. GitHub records the reviewed commit on each review and the PR's final commit as headRefOid, so the pull request attestation now carries them as approvers[].commit_sha and head_sha. Both fields are omitted when unknown: head_sha for providers other than GitHub, and commit_sha when GitHub no longer has the reviewed commit. A policy can then treat a missing value as "not shown", rather than compare two empty strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cess, and who signed each commit The pull request attestation listed every review GitHub had marked APPROVED. That included approvals from accounts without write access, which anyone can leave on a public repository, approvals by bots, and approvals the same reviewer later replaced by requesting changes. Approvers are now taken from each reviewer's latest approving or change-requesting review, from people with write access only (latestOpinionatedReviews with writersOnly), keeping approvals by user accounts. GitHub pull request attestations therefore list fewer approvers than before when any of those were present. Each commit now also records signer_username and signed_by_github. A commit's author name and email are whatever its writer put there; a verified signature names the account that holds the signing key, so a policy can tell who actually made the commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…identify committers by signature The four-eyes policy decided whether an approval still covered a pull request by comparing it with the newest commit's date, and decided who wrote each commit from the commit's author fields. Both come from the commit itself, so they can't show which code a reviewer saw or who made the commit. The policy now: - counts an approval only when the commit it was given on (approvers[].commit_sha) is the PR's head commit (head_sha); - treats a commit as identified only if it has a verified signature by a known account (signer_username) or by GitHub (signed_by_github), and requires an independent approval for the signer as well as the named author; - counts only pull requests in the evaluated repository, passed as the new required "repository" param, because an associated PR elsewhere, such as in a fork, has approvers its author may choose. An attestation without the new fields, from an older CLI, gets no approval. The workflow passes github.repository. The policy had no tests. The new Rego tests run under go test through internal/evaluate, so CI runs them with the rest of the suite. Needs a CLI release that sends head_sha, commit_sha, signer_username and signed_by_github; until CI installs it, the four-eyes result on build trails is red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Behaviour changes
Merge order
Checked against GitHubRan the new query shapes on #1239 (a dependabot PR): the human approval comes back from |
|
Claude finished @AlexKantor87's task in 2m 24s —— View job Review of PR #1244
Static checks: Issues
Notes (no change needed)
Good work
|
| approved_approvers_after_cutoff(pr, cutoff) := {a.username | | ||
| # Approver usernames whose approval was given on the PR's final commit. Commit | ||
| # dates are not used: whoever writes a commit sets them. | ||
| approvers_on_head(pr) := {a.username | |
There was a problem hiding this comment.
Rollout order: init_kosli.yml records the pr attestation with the released CLI (setup-cli-action with vars.KOSLI_CLI_VERSION, line 53–56), but it evaluates this policy from the checked-out branch. Until a release that includes this PR is cut and KOSLI_CLI_VERSION is bumped, pr attestations won't have head_sha, commit_sha, signer_username or signed_by_github. Every trail will then fail, starting with this PR's own merge commit. --no-assert keeps the build green, but a non-compliant four-eyes-result gets recorded for each of those trails.
Two ways to handle it:
- merge the Go changes first, release, bump the var, then merge the policy change; or
- in
init_kosli.yml, install the CLI from the branch build for theprattestation step.
Related, and worth stating in the PR description: approvals given before an "Update branch" merge commit stop counting, because the head moves. That is correct for four-eyes, but it is stricter than branch protection when "dismiss stale approvals" is off, so people may see unexpected failures.
| is_resolved_username(object.get(c, "signer_username", null)) | ||
| } | ||
|
|
||
| signed_by_known_identity(c) if { |
There was a problem hiding this comment.
Minor/question: for a GitHub-signed commit, signer_username is deliberately left empty (github.go, !g), so author_username is the only identity for it. That holds for web-UI and "Update branch" commits, where GitHub sets the author to the acting user. A GitHub App or Action can also create commits through the API (for example createCommitOnBranch or the REST git-data endpoints), and GitHub signs those too. Do we know whether the author on those can differ from the token's identity? If it can, we'd be trusting a field the writer controls again. Even if it can't, a short comment here saying why the GitHub-signed case is safe would help.
Record, on GitHub pull request attestations, the commit each approval was given on, the PR's final commit and who signed each commit, and use them in the four-eyes policy. The policy compared approvals with commit dates and trusted commit author fields, both of which come from the commit itself.
🤖 Generated with Claude Code