Skip to content

Commit 8b5d05f

Browse files
SecAI-Hubclaude
andcommitted
Epic 2: Unify image refs to ghcr.io/secai-hub/secai_os + CI enforcement
Fix 22 occurrences of wrong image reference (ghcr.io/sec_ai/secai_os) across 12 files — scripts, configs, docs, and VM build tooling. All container image references now use the canonical path matching the GitHub org (SecAI-Hub/SecAI_OS). Add CI enforcement: - image-ref-consistency job greps for known wrong patterns - docs-validation step verifies release-artifacts.json matches release.yml service matrix - Python test suite (test_image_ref_consistency.py) with 10 tests covering repo-wide scan, critical file assertions, and negative detection verification - Machine-readable release-artifacts.json as single source of truth for expected release artifact patterns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2276a73 commit 8b5d05f

13 files changed

Lines changed: 321 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,43 @@ jobs:
165165
sys.exit(errors)
166166
"
167167
168+
image-ref-consistency:
169+
name: Image Reference Consistency
170+
runs-on: ubuntu-latest
171+
permissions:
172+
contents: read
173+
steps:
174+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
175+
- name: Check for wrong image references
176+
run: |
177+
CANONICAL="ghcr.io/secai-hub/secai_os"
178+
179+
# Known wrong patterns (container image refs only)
180+
WRONG_PATTERNS=(
181+
"ghcr.io/sec_ai/secai_os"
182+
)
183+
184+
ERRORS=0
185+
for pattern in "${WRONG_PATTERNS[@]}"; do
186+
MATCHES=$(grep -rn \
187+
--include='*.sh' --include='*.py' --include='*.yaml' \
188+
--include='*.yml' --include='*.md' --include='*.json' \
189+
"$pattern" . 2>/dev/null | grep -v '.git/' | grep -v 'node_modules/' || true)
190+
if [ -n "$MATCHES" ]; then
191+
echo "::error::Found wrong image reference '$pattern':"
192+
echo "$MATCHES"
193+
ERRORS=$((ERRORS + $(echo "$MATCHES" | wc -l)))
194+
fi
195+
done
196+
197+
if [ "$ERRORS" -gt 0 ]; then
198+
echo ""
199+
echo "::error::Found $ERRORS wrong image reference(s)."
200+
echo "All container image references must use: $CANONICAL"
201+
exit 1
202+
fi
203+
echo "OK: All image references use canonical path: $CANONICAL"
204+
168205
check-pins:
169206
name: Verify action & container pins
170207
runs-on: ubuntu-latest
@@ -677,6 +714,44 @@ jobs:
677714
print(f'OK: test-counts.json is current (documented: {documented})')
678715
"
679716
717+
- name: Verify release-artifacts.json consistency
718+
run: |
719+
python3 -c "
720+
import json, re, sys
721+
722+
with open('docs/release-artifacts.json') as f:
723+
artifacts = json.load(f)
724+
725+
# Verify Go services in artifacts.json match release.yml matrix
726+
with open('.github/workflows/release.yml') as f:
727+
release = f.read()
728+
729+
# Extract matrix services from release.yml
730+
matrix_match = re.search(r'service: \[([^\]]+)\]', release)
731+
if not matrix_match:
732+
print('FAIL: cannot find service matrix in release.yml')
733+
sys.exit(1)
734+
release_services = sorted(s.strip() for s in matrix_match.group(1).split(','))
735+
artifact_services = sorted(artifacts['go_services'])
736+
737+
if release_services != artifact_services:
738+
print(f'FAIL: release.yml services {release_services} != release-artifacts.json {artifact_services}')
739+
sys.exit(1)
740+
741+
# Verify schema version
742+
if artifacts.get('schema_version') != 1:
743+
print('FAIL: release-artifacts.json schema_version must be 1')
744+
sys.exit(1)
745+
746+
# Verify canonical image ref
747+
canonical = artifacts.get('canonical_image_ref', '')
748+
if 'sec_ai' in canonical:
749+
print(f'FAIL: canonical_image_ref contains wrong namespace: {canonical}')
750+
sys.exit(1)
751+
752+
print(f'OK: release-artifacts.json consistent ({len(release_services)} Go services, {len(artifacts[\"python_services\"])} Python services)')
753+
"
754+
680755
release-gate:
681756
name: Release Branch Hardened Gate
682757
if: startsWith(github.ref, 'refs/heads/release/') || github.ref == 'refs/heads/stable'

docs/audit-quick-path.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,14 @@ Verify the container image was signed by the expected identity:
171171
cosign verify \
172172
--certificate-identity-regexp=".*SecAI-Hub.*" \
173173
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
174-
ghcr.io/sec_ai/secai_os:latest
174+
ghcr.io/secai-hub/secai_os:latest
175175

176176
# Verify SLSA provenance attestation
177177
cosign verify-attestation \
178178
--type slsa \
179179
--certificate-identity-regexp=".*SecAI-Hub.*" \
180180
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
181-
ghcr.io/sec_ai/secai_os:latest
181+
ghcr.io/secai-hub/secai_os:latest
182182
```
183183

184184
Expected: verification succeeds with no errors. Output shows the signing certificate chain.

docs/install/recovery-bootstrap.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ curl -sSfL https://raw.githubusercontent.com/SecAI-Hub/SecAI_OS/main/cosign.pub
4040
-o /tmp/cosign.pub
4141

4242
# Verify the image signature — STOP if this fails
43-
cosign verify --key /tmp/cosign.pub ghcr.io/sec_ai/secai_os:latest
43+
cosign verify --key /tmp/cosign.pub ghcr.io/secai-hub/secai_os:latest
4444
```
4545

4646
You must see a successful verification result. **Do not proceed if verification fails.**
@@ -49,7 +49,7 @@ You must see a successful verification result. **Do not proceed if verification
4949

5050
```bash
5151
# One-time unverified pull (safe ONLY because you verified the signature above)
52-
sudo rpm-ostree rebase ostree-unverified-registry:ghcr.io/sec_ai/secai_os:latest
52+
sudo rpm-ostree rebase ostree-unverified-registry:ghcr.io/secai-hub/secai_os:latest
5353
sudo systemctl reboot
5454
```
5555

@@ -68,7 +68,7 @@ verified by rpm-ostree.
6868

6969
```bash
7070
# Lock to signed transport — all future updates verified automatically
71-
sudo rpm-ostree rebase ostree-image-signed:docker://ghcr.io/sec_ai/secai_os:latest
71+
sudo rpm-ostree rebase ostree-image-signed:docker://ghcr.io/secai-hub/secai_os:latest
7272
sudo systemctl reboot
7373
```
7474

@@ -91,7 +91,7 @@ If your system is currently on the unverified transport (check with
9191
`rpm-ostree status`), switch to signed transport:
9292

9393
```bash
94-
sudo rpm-ostree rebase ostree-image-signed:docker://ghcr.io/sec_ai/secai_os:latest
94+
sudo rpm-ostree rebase ostree-image-signed:docker://ghcr.io/secai-hub/secai_os:latest
9595
sudo systemctl reboot
9696
```
9797

@@ -110,7 +110,7 @@ If `rpm-ostree upgrade` fails with a signature verification error:
110110
1. Check that the signing policy is intact:
111111
```bash
112112
cat /etc/containers/policy.json | python3 -m json.tool
113-
# Should contain a "sigstoreSigned" entry for ghcr.io/sec_ai/secai_os
113+
# Should contain a "sigstoreSigned" entry for ghcr.io/secai-hub/secai_os
114114
```
115115

116116
2. Check the registries config:

docs/m5-control-matrix.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Last updated: 2026-03-14
3333
| 23 | Prompt injection detection | MCP Firewall global rules | Shell metacharacters and prompt patterns detected and denied | `TestAdversarial_MalformedMCPPayload` | Global rule match in audit log | `go test -run TestAdversarial_MalformedMCPPayload ./services/mcp-firewall/...` |
3434
| 24 | MCP taint tracking | MCP Firewall taint.go | Session-scoped taint propagation prevents data flow violations | `TestAdversarial_TaintBypassAttempt`, `TestTaint_*` | Taint entries per session ID | `go test -run "TestTaint\|TestAdversarial_Taint" ./services/mcp-firewall/...` |
3535
| 25 | SBOM generation verification | CI supply-chain-verify job | Syft generates SBOMs for all services | CI workflow step output | CycloneDX SBOM artifacts | `syft dir:services/registry -o cyclonedx-json` (repeat per service) |
36-
| 26 | Release provenance attestation | Release workflow (release.yml) | cosign attest with SLSA3 provenance | CI workflow attestation step | Signed provenance attestation | `cosign verify-attestation --type slsa ghcr.io/sec_ai/secai_os:latest` |
36+
| 26 | Release provenance attestation | Release workflow (release.yml) | cosign attest with SLSA3 provenance | CI workflow attestation step | Signed provenance attestation | `cosign verify-attestation --type slsa ghcr.io/secai-hub/secai_os:latest` |
3737

3838
## End-to-End Enforcement Paths
3939

docs/release-artifacts.json

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"schema_version": 1,
3+
"description": "Machine-readable source of truth for expected release artifacts. Used by CI docs-validation and test_release_artifacts.py.",
4+
"canonical_image_ref": "ghcr.io/secai-hub/secai_os",
5+
"go_services": [
6+
"airlock",
7+
"registry",
8+
"tool-firewall",
9+
"gpu-integrity-watch",
10+
"mcp-firewall",
11+
"policy-engine",
12+
"runtime-attestor",
13+
"integrity-monitor",
14+
"incident-recorder"
15+
],
16+
"architectures": ["linux-amd64", "linux-arm64"],
17+
"python_services": [
18+
"agent",
19+
"ui",
20+
"quarantine",
21+
"common",
22+
"diffusion-worker",
23+
"search-mediator"
24+
],
25+
"artifacts": {
26+
"required": {
27+
"go_binaries": {
28+
"pattern": "{service}-{arch}",
29+
"description": "Static Go binaries for each service and architecture"
30+
},
31+
"go_sboms": {
32+
"pattern": "{service}-sbom.cdx.json",
33+
"description": "CycloneDX SBOM per Go service"
34+
},
35+
"python_sboms": {
36+
"pattern": "{service}-sbom.cdx.json",
37+
"description": "CycloneDX SBOM per Python service"
38+
},
39+
"checksums": {
40+
"files": ["SHA256SUMS", "SHA256SUMS.sig"],
41+
"description": "Checksums and cosign detached signature"
42+
},
43+
"manifest": {
44+
"files": ["RELEASE_MANIFEST.json"],
45+
"description": "Machine-readable release manifest"
46+
},
47+
"image_digest": {
48+
"files": ["IMAGE_DIGEST"],
49+
"description": "OCI image digest for this release"
50+
}
51+
},
52+
"optional": {
53+
"iso": {
54+
"pattern": "secai-os-{version}-x86_64.iso",
55+
"signature": "secai-os-{version}-x86_64.iso.sig",
56+
"description": "Bootable ISO (requires isogenerator)",
57+
"required_when": "always (standard runner)"
58+
},
59+
"qcow2": {
60+
"pattern": "secai-os-{version}.qcow2",
61+
"signature": "secai-os-{version}.qcow2.sig",
62+
"description": "QCOW2 disk image for KVM/QEMU/Proxmox",
63+
"required_when": "vars.HAS_KVM_RUNNER == 'true'"
64+
},
65+
"ova": {
66+
"pattern": "secai-os-{version}.ova",
67+
"signature": "secai-os-{version}.ova.sig",
68+
"description": "OVA appliance for VirtualBox/VMware",
69+
"required_when": "vars.HAS_KVM_RUNNER == 'true'"
70+
},
71+
"bootc_iso": {
72+
"pattern": "secai-os-{version}-x86_64-bootc.iso",
73+
"signature": "secai-os-{version}-x86_64-bootc.iso.sig",
74+
"description": "Experimental bootc-image-builder ISO",
75+
"required_when": "vars.ENABLE_BOOTC_ISO == 'true'"
76+
}
77+
}
78+
}
79+
}

docs/supply-chain-provenance.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ build → attest → sign → verify → promote
2525

2626
### 1. Build (build.yml)
2727
- BlueBuild action builds the OS image from `recipes/recipe.yml`
28-
- Image published to `ghcr.io/sec_ai/secai_os`
28+
- Image published to `ghcr.io/secai-hub/secai_os`
2929
- cosign signs the image using `SIGNING_SECRET`
3030

3131
### 2. Attest (build.yml + release.yml)

files/scripts/build-services.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ with open('${POLICY_JSON}') as f:
552552
policy.setdefault('transports', {})
553553
policy['transports'].setdefault('docker', {})
554554
555-
policy['transports']['docker']['ghcr.io/sec_ai/secai_os'] = [{
555+
policy['transports']['docker']['ghcr.io/secai-hub/secai_os'] = [{
556556
'type': 'sigstoreSigned',
557557
'keyPath': '${COSIGN_PUB}',
558558
'signedIdentity': {'type': 'matchRepository'}
@@ -562,7 +562,7 @@ with open('${POLICY_JSON}', 'w') as f:
562562
json.dump(policy, f, indent=2)
563563
f.write('\n')
564564
565-
print(' -> policy.json updated: sigstoreSigned entry for ghcr.io/sec_ai/secai_os')
565+
print(' -> policy.json updated: sigstoreSigned entry for ghcr.io/secai-hub/secai_os')
566566
" || fail_build "Failed to update container signing policy"
567567
else
568568
echo "WARNING: ${POLICY_JSON} not found — signing policy not configured"

files/scripts/secai-bootstrap.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ set -euo pipefail
2727
# ---------------------------------------------------------------------------
2828
# Configuration
2929
# ---------------------------------------------------------------------------
30-
REGISTRY="ghcr.io/sec_ai/secai_os"
30+
REGISTRY="ghcr.io/secai-hub/secai_os"
3131
COSIGN_PUB_URL="https://raw.githubusercontent.com/SecAI-Hub/SecAI_OS/main/cosign.pub"
3232
# SHA256 fingerprint of the expected cosign.pub — update after key rotation
3333
COSIGN_PUB_SHA256="de6a17ed1cd444a2671798f14d6bf98c1658259dc443a130eba9f40855a7d310"
@@ -197,7 +197,7 @@ mkdir -p "$(dirname "$REGISTRIES_YAML")"
197197
cat > "$REGISTRIES_YAML" <<'YAML'
198198
## SecAI OS — enable sigstore signature attachments for cosign-signed images.
199199
docker:
200-
ghcr.io/sec_ai/secai_os:
200+
ghcr.io/secai-hub/secai_os:
201201
use-sigstore-attachments: true
202202
YAML
203203
chmod 0644 "$REGISTRIES_YAML"
@@ -218,7 +218,7 @@ with open('${POLICY_JSON}') as f:
218218
policy.setdefault('transports', {})
219219
policy['transports'].setdefault('docker', {})
220220
221-
policy['transports']['docker']['ghcr.io/sec_ai/secai_os'] = [{
221+
policy['transports']['docker']['ghcr.io/secai-hub/secai_os'] = [{
222222
'type': 'sigstoreSigned',
223223
'keyPath': '${COSIGN_PUB_DEST}',
224224
'signedIdentity': {'type': 'matchRepository'}
@@ -238,7 +238,7 @@ policy = {
238238
'default': [{'type': 'insecureAcceptAnything'}],
239239
'transports': {
240240
'docker': {
241-
'ghcr.io/sec_ai/secai_os': [{
241+
'ghcr.io/secai-hub/secai_os': [{
242242
'type': 'sigstoreSigned',
243243
'keyPath': '${COSIGN_PUB_DEST}',
244244
'signedIdentity': {'type': 'matchRepository'}

files/scripts/secai-setup-wizard.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ set -euo pipefail
2121
SECURE_AI_ROOT="/var/lib/secure-ai"
2222
COSIGN_PUB="/etc/pki/containers/secai-cosign.pub"
2323
WIZARD_MARKER="${SECURE_AI_ROOT}/.wizard-complete"
24-
REGISTRY="ghcr.io/sec_ai/secai_os"
24+
REGISTRY="ghcr.io/secai-hub/secai_os"
2525
HEALTH_CHECK="/usr/libexec/secure-ai/first-boot-check.sh"
2626
SETUP_VAULT="/usr/libexec/secure-ai/setup-vault.sh"
2727
TPM2_SEAL="/usr/libexec/secure-ai/tpm2-seal-vault.sh"

files/system/etc/containers/registries.d/secai-os.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
## This allows rpm-ostree to verify cosign signatures during rebase/upgrade.
33

44
docker:
5-
ghcr.io/sec_ai/secai_os:
5+
ghcr.io/secai-hub/secai_os:
66
use-sigstore-attachments: true

0 commit comments

Comments
 (0)