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@v4
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.request
repository = os.environ["GITHUB_REPOSITORY"]
sha = os.environ["SOURCE_SHA"]
token = os.environ["GH_TOKEN"]
api = os.environ["GITHUB_API_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"
)
security_status = next(
(
item
for item in status_payload.get("statuses", [])
if item.get("context") == "replacement/security"
),
None,
)
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")
match = re.search(r"/actions/runs/(\d+)$", target_url)
if not match:
raise SystemExit("replacement/security target URL has no workflow run id")
run_id = int(match.group(1))
run = request(f"/repos/{repository}/actions/runs/{run_id}")
if run.get("conclusion") != "success":
raise SystemExit("the source-bound replacement/security workflow did not pass")
jobs = request(
f"/repos/{repository}/actions/runs/{run_id}/jobs?filter=latest&per_page=100"
).get("jobs", [])
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"
]
evidence.append(
{
"workflow_id": run["workflow_id"],
"workflow_name": "Replacement security gates",
"workflow_display_name": run["name"],
"run_id": run["id"],
"run_attempt": run.get("run_attempt"),
"html_url": run["html_url"],
"event": run["event"],
"head_sha": sha,
"workflow_head_sha": run["head_sha"],
"created_at": run["created_at"],
"updated_at": run["updated_at"],
"job_id": job["id"],
"job_name": job["name"],
"steps": successful_steps,
}
)
categories = {
"fuzz": re.compile(r"fuzz|libfuzzer", re.I),
"asan": re.compile(r"address sanitizer|asan", re.I),
"ubsan": re.compile(r"undefined behavior sanitizer|ubsan", re.I),
"miri": re.compile(r"miri", re.I),
"tsan": re.compile(r"thread sanitizer|tsan", re.I),
"valgrind": re.compile(r"valgrind", re.I),
}
matched: dict[str, list[dict]] = {name: [] for name in categories}
for item in evidence:
searchable = "\n".join(
[item["workflow_name"], item["job_name"], *item["steps"]]
)
for name, pattern in categories.items():
if pattern.search(searchable):
matched[name].append(item)
missing = [name for name, values in matched.items() if not values]
payload = {
"schema": 1,
"repository": repository,
"source_commit": sha,
"required_categories": sorted(categories),
"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@v4
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