name: Replacement security proof
run-name: Replacement security proof ${{ inputs.source_sha || github.sha }}
on:
workflow_dispatch:
inputs:
source_sha:
description: Full Git commit to prove; blank selects the dispatch ref
required: false
type: string
permissions:
actions: read
contents: read
statuses: write
jobs:
prove:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Check out exact source
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 with:
ref: ${{ inputs.source_sha || github.sha }}
fetch-depth: 1
- name: Verify exact source identity
shell: bash
env:
REQUESTED_SHA: ${{ inputs.source_sha || github.sha }}
run: |
set -euo pipefail
[[ $REQUESTED_SHA =~ ^[0-9a-f]{40}$ ]]
test "$(git rev-parse HEAD)" = "$REQUESTED_SHA"
- name: Collect successful security evidence for this SHA
shell: bash
env:
GH_TOKEN: ${{ github.token }}
SOURCE_SHA: ${{ inputs.source_sha || github.sha }}
run: |
set -euo pipefail
python3 - <<'PY'
from __future__ import annotations
import json
import os
from pathlib import Path
import re
import urllib.parse
import urllib.request
repository = os.environ["GITHUB_REPOSITORY"]
sha = os.environ["SOURCE_SHA"]
token = os.environ["GH_TOKEN"]
api = os.environ["GITHUB_API_URL"]
server = os.environ["GITHUB_SERVER_URL"]
def request(path: str) -> dict:
req = urllib.request.Request(
api + path,
headers={
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"X-GitHub-Api-Version": "2022-11-28",
},
)
with urllib.request.urlopen(req, timeout=30) as response:
return json.load(response)
status_payload = request(
f"/repos/{repository}/commits/{sha}/status"
)
status_repository = status_payload.get("repository")
if (
status_payload.get("sha") != sha
or not isinstance(status_repository, dict)
or status_repository.get("full_name") != repository
):
raise SystemExit("replacement/security status source identity differs")
matching_security = [
item
for item in status_payload.get("statuses", [])
if item.get("context") == "replacement/security"
]
security_status = max(
matching_security,
key=lambda item: (item.get("created_at", ""), item.get("id", 0)),
default={},
)
if not isinstance(security_status, dict) or security_status.get("state") != "success":
raise SystemExit("replacement/security is not successful for this exact source SHA")
target_url = security_status.get("target_url")
if not isinstance(target_url, str):
raise SystemExit("replacement/security has no workflow target URL")
parsed_target = urllib.parse.urlsplit(target_url)
expected_prefix = f"/{repository}/actions/runs/"
if (
parsed_target.scheme != urllib.parse.urlsplit(server).scheme
or parsed_target.netloc != urllib.parse.urlsplit(server).netloc
or parsed_target.query
or parsed_target.fragment
or not parsed_target.path.startswith(expected_prefix)
or not re.fullmatch(
r"[1-9][0-9]*", parsed_target.path[len(expected_prefix):]
)
):
raise SystemExit("replacement/security target URL has no workflow run id")
run_id = int(parsed_target.path[len(expected_prefix):])
workflow = request(
f"/repos/{repository}/actions/workflows/replacement-security-gates.yml"
)
if (
not isinstance(workflow.get("id"), int)
or workflow["id"] <= 0
or workflow.get("name") != "Replacement security gates"
or workflow.get("path")
!= ".github/workflows/replacement-security-gates.yml"
or workflow.get("state") != "active"
):
raise SystemExit("replacement security workflow identity differs")
run = request(f"/repos/{repository}/actions/runs/{run_id}")
run_repository = run.get("repository")
head_repository = run.get("head_repository")
if (
run.get("id") != run_id
or run.get("workflow_id") != workflow["id"]
or run.get("event") != "workflow_dispatch"
or run.get("status") != "completed"
or run.get("conclusion") != "success"
or not isinstance(run.get("run_attempt"), int)
or run["run_attempt"] <= 0
or not isinstance(run_repository, dict)
or run_repository.get("full_name") != repository
or not isinstance(head_repository, dict)
or head_repository.get("full_name") != repository
or head_repository.get("id") != run_repository.get("id")
or run.get("head_sha") != sha
or run.get("head_branch") != "main"
or run.get("path")
!= ".github/workflows/replacement-security-gates.yml"
or run.get("name") != f"Replacement security gates {sha}"
or run.get("html_url") != target_url
or run.get("display_title") != f"Replacement security gates {sha}"
):
raise SystemExit("the source-bound replacement/security workflow did not pass")
jobs_payload = request(
f"/repos/{repository}/actions/runs/{run_id}"
f"/attempts/{run['run_attempt']}/jobs?per_page=100"
)
jobs = jobs_payload.get("jobs", [])
if (
not isinstance(jobs, list)
or jobs_payload.get("total_count") != len(jobs)
or any(not isinstance(job, dict) for job in jobs)
):
raise SystemExit("replacement security job listing is incomplete")
expected_jobs = {
"fuzz": "libFuzzer corpus and smoke",
"asan": "Address Sanitizer ASan",
"ubsan": "Undefined Behavior Sanitizer UBSan",
"miri": "Miri strict provenance",
"tsan": "Thread Sanitizer TSan",
"valgrind": "Valgrind NSS Varlink and DNS fallback",
}
evidence = []
for job in jobs:
if job.get("conclusion") != "success":
continue
successful_steps = [
step.get("name", "")
for step in job.get("steps", [])
if step.get("conclusion") == "success"
]
if job.get("name") not in expected_jobs.values():
continue
if (
job.get("run_id") != run_id
or job.get("run_attempt") != run["run_attempt"]
or job.get("head_sha") != sha
or job.get("head_branch") != "main"
or job.get("workflow_name") != f"Replacement security gates {sha}"
):
raise SystemExit(
f"security job run identity differs: {job.get('name')}"
)
if "Verify exact source identity" not in successful_steps:
raise SystemExit(
f"security job did not prove the requested checkout: {job.get('name')}"
)
evidence.append(
{
"repository": run_repository["full_name"],
"requested_source_commit": sha,
"workflow_id": workflow["id"],
"workflow_name": workflow["name"],
"workflow_path": workflow["path"],
"run_id": run["id"],
"run_attempt": run["run_attempt"],
"html_url": run["html_url"],
"event": run["event"],
"workflow_head_sha": run["head_sha"],
"workflow_head_branch": run["head_branch"],
"workflow_head_repository": head_repository["full_name"],
"workflow_display_title": run.get("display_title"),
"created_at": run["created_at"],
"updated_at": run["updated_at"],
"job_id": job["id"],
"job_name": job["name"],
"job_conclusion": job["conclusion"],
"job_run_id": job["run_id"],
"job_run_attempt": job["run_attempt"],
"job_head_sha": job["head_sha"],
"job_head_branch": job["head_branch"],
"job_workflow_name": job["workflow_name"],
"steps": successful_steps,
}
)
matched = {
category: [
item for item in evidence if item["job_name"] == expected_name
]
for category, expected_name in expected_jobs.items()
}
missing = [
name for name, values in matched.items() if len(values) != 1
]
payload = {
"schema": 2,
"repository": repository,
"source_commit": sha,
"workflow": {
"id": workflow["id"],
"name": workflow["name"],
"path": workflow["path"],
},
"run": {
"id": run["id"],
"attempt": run["run_attempt"],
"event": run["event"],
"conclusion": run["conclusion"],
"repository": run_repository["full_name"],
"head_repository": head_repository["full_name"],
"workflow_head_sha": run["head_sha"],
"head_branch": run["head_branch"],
"workflow_path": run["path"],
"workflow_name": run["name"],
"display_title": run.get("display_title"),
},
"required_categories": sorted(expected_jobs),
"matched": matched,
"all_successful_job_evidence": evidence,
"missing": missing,
}
Path("security-evidence.json").write_text(
json.dumps(payload, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
if missing:
raise SystemExit(
"missing successful security evidence for: " + ", ".join(missing)
)
PY
- name: Write bound proof
shell: bash
run: |
set -euo pipefail
mkdir -p proof
cp security-evidence.json proof/security-evidence.json
python3 scripts/write-replacement-proof.py \
--gate security-suite \
--result pass \
--output proof/security-suite.json \
--artifact proof/security-evidence.json \
--metadata profiles=fuzz,asan,ubsan,miri,tsan,valgrind \
--summary 'All required security profiles passed on this exact Git tree.'
- name: Upload security proof
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with:
name: replacement-security-proof-${{ inputs.source_sha || github.sha }}
path: proof
if-no-files-found: error
retention-days: 30
report:
name: Publish security proof status
if: ${{ always() }}
needs:
- prove
runs-on: ubuntu-24.04
steps:
- name: Publish exact-source status
env:
GH_TOKEN: ${{ github.token }}
SOURCE_SHA: ${{ inputs.source_sha || github.sha }}
RESULT: ${{ needs.prove.result }}
run: |
set -euo pipefail
state=failure
if [[ "$RESULT" == success ]]; then
state=success
fi
payload=$(printf '{"state":"%s","context":"replacement/security-proof","description":"Bound security proof %s","target_url":"%s"}' "$state" "$state" "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}")
curl --fail-with-body --silent --show-error -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/statuses/${SOURCE_SHA}" \
--data "$payload" >/dev/null