Skip to content

Commit e6fbf02

Browse files
SecAI-Hubclaude
andcommitted
M53: Harder CI gates for production branches — release-gate job, CVE-ID govulncheck, docs consistency
Add release-branch-only hardened gate (zero-tolerance bandit, CVE-ID-level govulncheck waivers, M5 acceptance re-verification), release preflight check in release.yml, docs consistency checks (milestone count cross-check, m5-control-matrix test reference verification, test-counts staleness warning), container pin wiring, and branch protection documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b7931ea commit e6fbf02

9 files changed

Lines changed: 448 additions & 11 deletions

File tree

.github/branch-protection.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Branch Protection Rules
2+
3+
Required branch protection settings for SecAI OS release infrastructure.
4+
Configure these in GitHub Settings > Branches > Add branch protection rule,
5+
or use the setup script below.
6+
7+
Last updated: 2026-03-14
8+
9+
---
10+
11+
## `release/*` branches
12+
13+
| Setting | Value |
14+
|---------|-------|
15+
| Require pull request before merging | Yes |
16+
| Required approvals | 1 |
17+
| Dismiss stale reviews | Yes |
18+
| Require status checks to pass | Yes |
19+
| Required status checks | See list below |
20+
| Require branches to be up to date | Yes |
21+
| Require signed commits | Recommended |
22+
| Allow force pushes | No |
23+
| Allow deletions | No |
24+
25+
### Required status checks for `release/*`
26+
27+
All 7 of these must pass before a PR can merge into a release branch:
28+
29+
1. **Go Build & Test** (`go-build-and-test`) — Builds and tests all 9 Go services with race detector
30+
2. **Python Test & Lint** (`python-test`) — Ruff, bandit, mypy, unit/integration tests, adversarial + M5 acceptance
31+
3. **Security Regression Tests** (`security-regression`) — Adversarial tests (Python + Go MCP/policy/incident-recorder)
32+
4. **Dependency Vulnerability Audit** (`dependency-audit`) — govulncheck + pip-audit with waiver mechanism
33+
5. **Test Count Drift Check** (`test-count-check`) — Ensures test counts don't drop below documented floor
34+
6. **Documentation Validation** (`docs-validation`) — Broken links, required docs, milestone count consistency, test references
35+
7. **Release Branch Hardened Gate** (`release-gate`) — Zero-tolerance bandit, CVE-ID govulncheck waivers, M5 acceptance re-run
36+
37+
The `release-gate` job has `needs:` on all of the above, so configuring it as the sole required check is sufficient.
38+
However, listing all 7 makes failure diagnosis easier in the GitHub UI.
39+
40+
---
41+
42+
## `stable` branch
43+
44+
Same settings as `release/*`, plus:
45+
46+
| Setting | Value |
47+
|---------|-------|
48+
| Restrict who can push | Maintainers only |
49+
| Require conversation resolution | Yes |
50+
51+
---
52+
53+
## What the release-gate adds over dev CI
54+
55+
| Check | Dev CI (`main` / PRs) | Release branches |
56+
|-------|----------------------|------------------|
57+
| Bandit severity gate | HIGH severity + HIGH confidence | HIGH severity at **any** confidence |
58+
| Go vuln waiver matching | Count-based subtraction | CVE-ID matching (per-vulnerability) |
59+
| M5 acceptance suite | Runs in `python-test` | Re-runs in dedicated `release-gate` step |
60+
| Container pin check | Checked (since M53) | Same |
61+
| Docs consistency | Milestone counts + test refs (since M53) | Same |
62+
63+
---
64+
65+
## Setup Script
66+
67+
Run from a machine with the `gh` CLI authenticated as a repository admin.
68+
69+
**Note:** The GitHub API endpoint for branch protection rules with wildcard
70+
patterns (`release/*`) requires using rulesets. The script below uses the
71+
branch protection API for `stable` (exact name) and documents the UI steps
72+
for wildcard patterns.
73+
74+
### For `stable` branch (exact match — API supported)
75+
76+
```bash
77+
#!/usr/bin/env bash
78+
set -euo pipefail
79+
80+
OWNER="SecAI-Hub"
81+
REPO="SecAI_OS"
82+
83+
gh api -X PUT "repos/${OWNER}/${REPO}/branches/stable/protection" \
84+
--input - <<'EOF'
85+
{
86+
"required_status_checks": {
87+
"strict": true,
88+
"contexts": [],
89+
"checks": [
90+
{"context": "Go Build & Test"},
91+
{"context": "Python Test & Lint"},
92+
{"context": "Security Regression Tests"},
93+
{"context": "Dependency Vulnerability Audit"},
94+
{"context": "Test Count Drift Check"},
95+
{"context": "Documentation Validation"},
96+
{"context": "Release Branch Hardened Gate"}
97+
]
98+
},
99+
"enforce_admins": true,
100+
"required_pull_request_reviews": {
101+
"required_approving_review_count": 1,
102+
"dismiss_stale_reviews": true
103+
},
104+
"restrictions": null,
105+
"allow_force_pushes": false,
106+
"allow_deletions": false
107+
}
108+
EOF
109+
echo "OK: Branch protection set for stable"
110+
```
111+
112+
### For `release/*` branches (wildcard — use GitHub UI)
113+
114+
1. Go to **Settings > Branches > Add branch protection rule**
115+
2. Branch name pattern: `release/*`
116+
3. Enable all settings listed in the table above
117+
4. Under "Require status checks to pass", add all 7 check names listed above
118+
5. Save changes
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bash
2+
# govulncheck-strict.sh — Release-branch govulncheck with CVE-ID-level waivers.
3+
#
4+
# Unlike the regular dependency-audit job (which subtracts waiver counts),
5+
# this matches by specific CVE ID — same approach as the Python pip-audit gate.
6+
# Only used by the release-gate job on release/* and stable branches.
7+
#
8+
set -euo pipefail
9+
10+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
11+
WAIVERS="${REPO_ROOT}/.github/vuln-waivers.json"
12+
GO_SERVICES="airlock registry tool-firewall gpu-integrity-watch mcp-firewall policy-engine runtime-attestor integrity-monitor incident-recorder"
13+
14+
# Collect all vulnerability IDs across services
15+
FINDINGS_FILE=$(mktemp)
16+
trap 'rm -f "$FINDINGS_FILE"' EXIT
17+
18+
for svc in ${GO_SERVICES}; do
19+
echo "--- govulncheck: ${svc} ---"
20+
OUTPUT_FILE=$(mktemp)
21+
(cd "${REPO_ROOT}/services/${svc}" && govulncheck -json ./... > "$OUTPUT_FILE" 2>&1) || true
22+
23+
# Extract OSV/CVE IDs from govulncheck JSON stream
24+
python3 -c "
25+
import json, sys
26+
ids = set()
27+
for line in open('${OUTPUT_FILE}'):
28+
line = line.strip()
29+
if not line:
30+
continue
31+
try:
32+
obj = json.loads(line)
33+
except json.JSONDecodeError:
34+
continue
35+
osv = obj.get('osv', {})
36+
if osv:
37+
vid = osv.get('id', '')
38+
if vid:
39+
ids.add(vid)
40+
for a in osv.get('aliases', []):
41+
ids.add(a)
42+
finding = obj.get('finding', {})
43+
if finding and finding.get('osv'):
44+
ids.add(finding['osv'])
45+
for vid in sorted(ids):
46+
print('${svc}:' + vid)
47+
" >> "$FINDINGS_FILE" 2>/dev/null || true
48+
49+
rm -f "$OUTPUT_FILE"
50+
done
51+
52+
# Compare findings against waivers
53+
python3 -c "
54+
import json, sys, datetime
55+
56+
with open('${WAIVERS}') as f:
57+
waivers = json.load(f)
58+
today = datetime.date.today().isoformat()
59+
waived_ids = {w['id'] for w in waivers.get('go', []) if w.get('expires', '') >= today}
60+
61+
unwaived = []
62+
with open('${FINDINGS_FILE}') as f:
63+
for line in f:
64+
line = line.strip()
65+
if not line:
66+
continue
67+
svc, vid = line.split(':', 1)
68+
if vid in waived_ids:
69+
print(f'WAIVED: {svc} {vid}')
70+
else:
71+
print(f'::error::{svc}: {vid} — unwaived vulnerability')
72+
unwaived.append(f'{svc}:{vid}')
73+
74+
# Deduplicate (same CVE in multiple services)
75+
unique = set(v.split(':', 1)[1] for v in unwaived)
76+
if unique:
77+
print(f'RELEASE GATE FAIL: {len(unique)} unwaived Go CVE(s) across {len(unwaived)} service finding(s)')
78+
print('To waive, add CVE IDs to .github/vuln-waivers.json with reason + expiry.')
79+
sys.exit(1)
80+
print(f'OK: Go strict vuln check passed ({len(waived_ids)} waiver(s) active)')
81+
"

0 commit comments

Comments
 (0)