-
Notifications
You must be signed in to change notification settings - Fork 0
feat(release): tagged GitHub Release with SHA-256 and immutable GHCR #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
seonghobae
merged 10 commits into
feat/issue-80-optimistic-concurrency
from
feat/issue-84-signed-release
Aug 26, 2026
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
40cee1a
feat(release): tagged GitHub Release with SHA-256 and immutable GHCR
seonghobae 3e501fa
docs: record PR #107 in the product-technical gap baseline
seonghobae d810fc4
fix(release): basename checksums and serialize postgres GRANTs
seonghobae ef89a7e
feat(release): keyless cosign, SPDX SBOM, and SLSA on the same tag
seonghobae 550c237
feat(release): refuse lightweight tags and pin k8s by digest
seonghobae 2119788
docs: record PR #109 in the product-technical gap baseline
seonghobae d00c78f
feat(waf): detect OWASP CRS attack battery on the live binary
seonghobae f62e7ab
Merge pull request #110 from ContextualWisdomLab/feat/issue-11-attack…
seonghobae b8094c4
Merge pull request #109 from ContextualWisdomLab/feat/issue-84-unsign…
seonghobae ef6f24d
Merge pull request #108 from ContextualWisdomLab/feat/issue-84-cosign…
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| name: Release | ||
|
|
||
| on: | ||
| push: | ||
| tags: | ||
| - "v*.*.*" | ||
|
|
||
| permissions: | ||
| contents: write | ||
| packages: write | ||
| id-token: write | ||
| attestations: write | ||
|
|
||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Admit annotated vX.Y.Z tag only | ||
| run: scripts/admit-release-tag.sh "$GITHUB_REF_NAME" | ||
| - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable | ||
| with: | ||
| toolchain: stable | ||
| - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 | ||
| - uses: anchore/sbom-action/download-syft@a930d0ac434e3182448fe678398ba5713717112a # v0.21.0 | ||
| - name: Build release binary | ||
| run: cargo build --locked --release | ||
| - name: Stage binary and binary SBOM | ||
| run: | | ||
| mkdir -p dist | ||
| cp target/release/waf-ids-ai-soc dist/waf-ids-ai-soc-linux-x86_64 | ||
| scripts/release-sbom.sh --output dist/sbom.spdx.json dist/waf-ids-ai-soc-linux-x86_64 | ||
| - name: Publish GHCR image by digest | ||
| id: image | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -euo pipefail | ||
| echo "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin | ||
| image="ghcr.io/contextualwisdomlab/waf-ids-ai-soc" | ||
| tag="${GITHUB_REF_NAME}" | ||
| docker build -t "${image}:${tag}" . | ||
| push_out="$(docker push "${image}:${tag}")" | ||
| digest="$(printf '%s\n' "$push_out" | awk '/digest:/{d=$NF} END{print d}')" | ||
| test -n "$digest" | ||
| ref="${image}@${digest}" | ||
| printf '%s\n' "$ref" > dist/IMAGE-DIGEST.txt | ||
| scripts/release-sbom.sh --output dist/image.sbom.spdx.json "$ref" | ||
| echo "ref=${ref}" >> "$GITHUB_OUTPUT" | ||
| echo "digest=${digest}" >> "$GITHUB_OUTPUT" | ||
| echo "image=${image}" >> "$GITHUB_OUTPUT" | ||
| - name: Checksums and keyless blob signatures | ||
| run: | | ||
| set -euo pipefail | ||
| (cd dist && ../scripts/release-checksums.sh \ | ||
| waf-ids-ai-soc-linux-x86_64 \ | ||
| sbom.spdx.json \ | ||
| image.sbom.spdx.json \ | ||
| IMAGE-DIGEST.txt > SHA256SUMS) | ||
| cat dist/SHA256SUMS | ||
|
seonghobae marked this conversation as resolved.
|
||
| cosign sign-blob --yes \ | ||
| --bundle dist/waf-ids-ai-soc-linux-x86_64.sigstore.json \ | ||
| dist/waf-ids-ai-soc-linux-x86_64 | ||
| cosign sign-blob --yes \ | ||
| --bundle dist/SHA256SUMS.sigstore.json \ | ||
| dist/SHA256SUMS | ||
| cosign sign-blob --yes \ | ||
| --bundle dist/sbom.spdx.json.sigstore.json \ | ||
| dist/sbom.spdx.json | ||
| cosign sign-blob --yes \ | ||
| --bundle dist/image.sbom.spdx.json.sigstore.json \ | ||
| dist/image.sbom.spdx.json | ||
| cosign sign-blob --yes \ | ||
| --bundle dist/IMAGE-DIGEST.txt.sigstore.json \ | ||
| dist/IMAGE-DIGEST.txt | ||
| - name: Sign image and attest image SBOM (keyless) | ||
| run: | | ||
| set -euo pipefail | ||
| ref="${{ steps.image.outputs.ref }}" | ||
| cosign sign --yes "$ref" | ||
| cosign attest --yes --predicate dist/image.sbom.spdx.json --type spdxjson "$ref" | ||
| - name: SLSA provenance for binary | ||
| uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0 | ||
| with: | ||
| subject-path: dist/waf-ids-ai-soc-linux-x86_64 | ||
| - name: SLSA provenance for image | ||
| uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0 | ||
| with: | ||
| subject-name: ghcr.io/contextualwisdomlab/waf-ids-ai-soc | ||
| subject-digest: ${{ steps.image.outputs.digest }} | ||
| push-to-registry: true | ||
| - name: Attest binary SBOM | ||
| uses: actions/attest-sbom@115c3be05ff3974bcbd596578934b3f9ce39bf68 # v2.2.0 | ||
| with: | ||
| subject-path: dist/waf-ids-ai-soc-linux-x86_64 | ||
| sbom-path: dist/sbom.spdx.json | ||
| - name: Attest image SBOM | ||
| uses: actions/attest-sbom@115c3be05ff3974bcbd596578934b3f9ce39bf68 # v2.2.0 | ||
| with: | ||
| subject-name: ghcr.io/contextualwisdomlab/waf-ids-ai-soc | ||
| subject-digest: ${{ steps.image.outputs.digest }} | ||
| sbom-path: dist/image.sbom.spdx.json | ||
| push-to-registry: true | ||
| - name: GitHub Release | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -euo pipefail | ||
| notes="$(mktemp)" | ||
| { | ||
| echo "Promotion authority is the image digest and Sigstore signatures, not the tag." | ||
| echo | ||
| echo "Image: \`${{ steps.image.outputs.ref }}\`" | ||
| echo "Tag alias: \`ghcr.io/contextualwisdomlab/waf-ids-ai-soc:${GITHUB_REF_NAME}\`" | ||
| echo | ||
| echo "Verify: \`docs/runbooks/release.md\`" | ||
| } > "$notes" | ||
| gh release create "$GITHUB_REF_NAME" \ | ||
| dist/waf-ids-ai-soc-linux-x86_64 \ | ||
| dist/SHA256SUMS \ | ||
| dist/sbom.spdx.json \ | ||
| dist/image.sbom.spdx.json \ | ||
| dist/IMAGE-DIGEST.txt \ | ||
| dist/waf-ids-ai-soc-linux-x86_64.sigstore.json \ | ||
| dist/SHA256SUMS.sigstore.json \ | ||
| dist/sbom.spdx.json.sigstore.json \ | ||
| dist/image.sbom.spdx.json.sigstore.json \ | ||
| dist/IMAGE-DIGEST.txt.sigstore.json \ | ||
| --notes-file "$notes" \ | ||
| --verify-tag | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| # Doctoring — CI attack-evidence battery (issue #11) | ||
|
|
||
| This note grounds the issue #11 slice: the compiled gateway binary is started | ||
| in CI with a hermetic libcoraza engine, a deterministic OWASP CRS attack | ||
| battery is fired over real HTTP, and every attempt must be blocked with the | ||
| cited CRS rule id and recorded as a security event that keeps the forwarded | ||
| client IP unmasked. | ||
|
|
||
| ## What is proven (and what is not) | ||
|
|
||
| Proven end to end on the real binary: operator-supplied `CORAZA_LIB_PATH` | ||
| loading, rules-file admission, per-transaction evaluation of method/URI/body, | ||
| block responses citing `coraza/crs: rule <id>`, benign traffic still | ||
| forwarding, and unmasked client attribution in `/api/events`. | ||
|
|
||
| Not proven: detection *quality* against arbitrary live traffic. The CI engine | ||
| is the build-script ABI stub (`src/coraza_abi_stub.rs`), a fixture that | ||
| mirrors the libcoraza C ABI, not Coraza itself. Quality evidence stays with an | ||
| operator deployment using a real libcoraza plus the OWASP Core Rule Set; this | ||
| slice only removes "the path was never exercised in CI" from the gap list. | ||
|
|
||
| ## Adopted standards and literature | ||
|
|
||
| OWASP Foundation. (n.d.). *OWASP Core Rule Set documentation*. | ||
| https://coreruleset.org/docs/ | ||
|
|
||
| - **Design impact:** Battery entries map to canonical CRS rule families — | ||
| 942100 SQLi (libinjection), 941100 XSS (libinjection), 930100 path | ||
| traversal, 932100 Unix command injection, 944120 Log4j JNDI. Rule ids in | ||
| block reasons and events stay CRS ids so operator dashboards read the same | ||
| vocabulary in CI evidence and production. | ||
|
|
||
| Scarfone, K., & Mell, P. (2007). *Guide to intrusion detection and prevention | ||
| systems (IDPS)* (NIST Special Publication 800-94). National Institute of | ||
| Standards and Technology. https://doi.org/NIST.SP.800-94 | ||
|
|
||
| - **Design impact:** IDPS evaluation distinguishes the detection *path* from | ||
| detection *efficacy*. SP 800-94's testing guidance motivates keeping the two | ||
| claims separate: CI asserts the prevention path (signature → interrupt → | ||
| block → record), while efficacy against evasive payloads requires curated | ||
| corpora and is explicitly out of scope for this fixture. | ||
|
|
||
| Saltzer, J. H., & Schroeder, M. D. (1975). The protection of information in | ||
| computer systems. *Proceedings of the IEEE*, *63*(9), 1278–1308. | ||
| https://doi.org/10.1109/PROC.1975.9939 | ||
|
|
||
| - **Design impact:** Complete mediation and fail-safe defaults. The battery | ||
| runs through the same route pipeline (`mode: block`) as production traffic, | ||
| so no test-only bypass exists; an engine that fails to load refuses startup | ||
| before bind instead of degrading silently. | ||
|
|
||
| MITRE. (n.d.). *CWE-20: Improper input validation*. MITRE Corporation. | ||
| https://cwe.mitre.org/data/definitions/20.html | ||
|
|
||
| - **Design impact:** The battery covers encoded variants (`%3Cscript`, | ||
| `%24%7BJNDI`, `..%2F`) because input-validation defects classically live at | ||
| decoding boundaries; the gateway evaluates the raw request line exactly as | ||
| received, so fixtures pin that behavior rather than a decoded copy. | ||
|
|
||
| ## Verification posture | ||
|
|
||
| - `tests/binary.rs::live_gateway_detects_owasp_attack_battery_end_to_end` | ||
| spawns the binary, creates the block route over the admin API, fires nine | ||
| battery cases (GET query attacks across five rule families plus a POST-body | ||
| XSS), asserts HTTP 403 + `engine=coraza` + cited rule id per case, asserts a | ||
| benign request forwards, and asserts `/api/events` records one event per | ||
| attempt with `X-Forwarded-For` preserved verbatim. | ||
| - `src/coraza_inprocess.rs::stub_engine_battery_matches_each_owasp_family` | ||
| pins the fixture contract itself, including first-match ordering so the | ||
| overlapping `; cat /etc/passwd` payload attributes to RCE (932100), not | ||
| traversal. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Doctoring — signed release, SBOM, and provenance | ||
|
|
||
| This note grounds issue #84 remainder (keyless Sigstore signatures and | ||
| SBOM/SLSA attestations on the same `vX.Y.Z` tag as checksums/GHCR). | ||
| IEEE/ACM PDFs are not redistributed. NIST SP 800-218 is a U.S. government | ||
| work and is committed at `docs/papers/nist-sp-800-218-ssdf.pdf`. | ||
|
|
||
| ## Adopted standards and literature | ||
|
|
||
| Sigstore. (n.d.). *Cosign documentation*. https://docs.sigstore.dev/cosign/ | ||
|
|
||
| - **Design impact:** The release workflow uses GitHub OIDC (`id-token: write`) | ||
| for keyless signing. No long-lived Cosign key is stored. Blobs (binary, | ||
| `SHA256SUMS`, SBOMs, image digest file) get `cosign sign-blob` bundles. | ||
| The GHCR image is signed by digest (`image@sha256:…`), never by a moving | ||
| tag. | ||
|
|
||
| SLSA Project. (2025). *SLSA specification version 1.2*. | ||
| https://slsa.dev/spec/v1.2/ | ||
|
|
||
| - **Design impact:** `actions/attest-build-provenance` binds the binary and | ||
| the image digest to in-toto SLSA provenance. `actions/attest-sbom` binds | ||
| SPDX SBOMs to the same subjects. GitHub Release is created only after | ||
| signatures and attestations succeed. | ||
|
|
||
| National Institute of Standards and Technology. (2022). *Secure Software | ||
| Development Framework (SSDF) version 1.1* (NIST SP 800-218). | ||
| https://doi.org/10.6028/NIST.SP.800-218 | ||
| `docs/papers/nist-sp-800-218-ssdf.pdf` | ||
|
|
||
| - **Design impact:** PS.3 / PW.4 — produce integrity evidence (checksums, | ||
| SBOM, signatures, provenance) for the shipped artifact. A tag alias is | ||
| not promotion authority; operators verify the digest and signatures | ||
| (`docs/runbooks/release.md`). | ||
|
|
||
| Anchore. (n.d.). *Syft*. https://github.com/anchore/syft | ||
|
|
||
| - **Design impact:** `scripts/release-sbom.sh` fails closed without Syft and | ||
| rejects non-SPDX JSON. Binary and container filesystem SBOMs are both | ||
| attached to the GitHub Release. | ||
|
|
||
| ## Operator next action | ||
|
|
||
| Tag `vX.Y.Z` from the reviewed merge commit on `main`. After the Release | ||
| workflow finishes, verify with the commands in `docs/runbooks/release.md`. | ||
| Point Kubernetes at the digest in `IMAGE-DIGEST.txt`, not at `latest`. |
Binary file not shown.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Release digest parse grabs image size, not sha256
awk '/digest:/{d=$NF}'takes the last field of thedocker pushline<tag>: digest: sha256:<hex> size: <number>, which is the size number, not the digest.refbecomesghcr.io/...@<size>, so the SBOM, cosign signing by digest, the provenance/SBOM attestations, andIMAGE-DIGEST.txtall get an invalid reference.Was this helpful? React with 👍 or 👎 to provide feedback.