Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 147 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,28 @@ jobs:
print(egressweave.__version__)
PY
)
- name: Generate deterministic CycloneDX SBOMs
run: |
set -euo pipefail
wheel="$(find dist -maxdepth 1 -type f -name '*.whl' -print -quit)"
sdist="$(find dist -maxdepth 1 -type f -name '*.tar.gz' -print -quit)"
test -n "$wheel" && test -n "$sdist"
python scripts/ci/generate_release_sbom.py \
--artifact "$wheel" \
--manifest scripts/ci/release_runtime_dependencies.json \
--lock requirements-ci.txt \
--output "${wheel}.cdx.json"
python scripts/ci/generate_release_sbom.py \
--artifact "$sdist" \
--manifest scripts/ci/release_runtime_dependencies.json \
--lock requirements-ci.txt \
--output "${sdist}.cdx.json"
(
cd dist
sha256sum *.whl *.tar.gz *.whl.cdx.json *.tar.gz.cdx.json \
| LC_ALL=C sort -k2 > SHA256SUMS
sha256sum --check SHA256SUMS
)
- name: Prepare the canonical PyPI-only artifact set
run: |
mkdir publish-dist
Expand Down Expand Up @@ -171,11 +193,133 @@ jobs:
-f ref="refs/tags/${RELEASE_TAG}" \
-f sha="$RELEASE_SHA"

attest-release-evidence:
name: Sign and verify exact CycloneDX SBOM evidence
needs:
- build-distributions
- create-release-tag
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
actions: read
contents: read
id-token: write
attestations: write
artifact-metadata: write
steps:
- name: Download complete reviewed release evidence
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: release-evidence-${{ github.sha }}
path: release-evidence
- name: Verify checksums and identify canonical subjects
id: evidence
run: |
set -euo pipefail
(
cd release-evidence
sha256sum --check SHA256SUMS
)
mapfile -t wheels < <(find release-evidence -maxdepth 1 -type f -name '*.whl' -print)
mapfile -t sdists < <(find release-evidence -maxdepth 1 -type f -name '*.tar.gz' -print)
if [ "${#wheels[@]}" -ne 1 ] || [ "${#sdists[@]}" -ne 1 ]; then
echo "::error::Attestation requires exactly one wheel and one source distribution."
exit 1
fi
test -f "${wheels[0]}.cdx.json"
test -f "${sdists[0]}.cdx.json"
echo "wheel=${wheels[0]}" >>"$GITHUB_OUTPUT"
echo "wheel_sbom=${wheels[0]}.cdx.json" >>"$GITHUB_OUTPUT"
echo "sdist=${sdists[0]}" >>"$GITHUB_OUTPUT"
echo "sdist_sbom=${sdists[0]}.cdx.json" >>"$GITHUB_OUTPUT"
- name: Attest wheel SBOM
id: attest_wheel
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: ${{ steps.evidence.outputs.wheel }}
sbom-path: ${{ steps.evidence.outputs.wheel_sbom }}
- name: Attest source distribution SBOM
id: attest_sdist
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: ${{ steps.evidence.outputs.sdist }}
sbom-path: ${{ steps.evidence.outputs.sdist_sbom }}
- name: Verify signed SBOM attestations against repository and predicates
env:
GH_TOKEN: ${{ github.token }}
WHEEL: ${{ steps.evidence.outputs.wheel }}
WHEEL_SBOM: ${{ steps.evidence.outputs.wheel_sbom }}
WHEEL_BUNDLE: ${{ steps.attest_wheel.outputs.bundle-path }}
SDIST: ${{ steps.evidence.outputs.sdist }}
SDIST_SBOM: ${{ steps.evidence.outputs.sdist_sbom }}
SDIST_BUNDLE: ${{ steps.attest_sdist.outputs.bundle-path }}
run: |
set -euo pipefail
signer_workflow="${GITHUB_REPOSITORY}/.github/workflows/release.yml"
gh attestation verify "$WHEEL" \
--bundle "$WHEEL_BUNDLE" \
-R "$GITHUB_REPOSITORY" \
--predicate-type https://cyclonedx.org/bom \
--signer-workflow "$signer_workflow" \
--source-digest "$GITHUB_SHA" \
--source-ref "$GITHUB_REF" \
--deny-self-hosted-runners \
--format json \
--jq '.[0].verificationResult.statement.predicate' \
> wheel.observed.cdx.json
gh attestation verify "$SDIST" \
--bundle "$SDIST_BUNDLE" \
-R "$GITHUB_REPOSITORY" \
--predicate-type https://cyclonedx.org/bom \
--signer-workflow "$signer_workflow" \
--source-digest "$GITHUB_SHA" \
--source-ref "$GITHUB_REF" \
--deny-self-hosted-runners \
--format json \
--jq '.[0].verificationResult.statement.predicate' \
> sdist.observed.cdx.json
python - "$WHEEL_SBOM" wheel.observed.cdx.json \
"$SDIST_SBOM" sdist.observed.cdx.json <<'PY'
import json
import pathlib
import sys

for expected_name, observed_name in zip(sys.argv[1::2], sys.argv[2::2]):
expected = json.loads(pathlib.Path(expected_name).read_text(encoding="utf-8"))
observed = json.loads(pathlib.Path(observed_name).read_text(encoding="utf-8"))
if observed != expected:
raise SystemExit(
f"verified SBOM predicate did not match {expected_name!r}"
)
PY
- name: Preserve Sigstore bundles and refresh evidence checksums
env:
WHEEL_BUNDLE: ${{ steps.attest_wheel.outputs.bundle-path }}
SDIST_BUNDLE: ${{ steps.attest_sdist.outputs.bundle-path }}
run: |
set -euo pipefail
cp "$WHEEL_BUNDLE" release-evidence/wheel.sbom.attestation.json
cp "$SDIST_BUNDLE" release-evidence/sdist.sbom.attestation.json
(
cd release-evidence
sha256sum *.whl *.tar.gz *.cdx.json *.attestation.json \
| LC_ALL=C sort -k2 > SHA256SUMS
sha256sum --check SHA256SUMS
)
- name: Upload attested release evidence
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: attested-release-evidence-${{ github.sha }}
path: release-evidence/
if-no-files-found: error
retention-days: 14

publish-to-pypi:
name: Publish to PyPI with OIDC
needs:
- build-distributions
- create-release-tag
- attest-release-evidence
runs-on: ubuntu-24.04
timeout-minutes: 15
environment:
Expand Down Expand Up @@ -203,17 +347,18 @@ jobs:
needs:
- build-distributions
- create-release-tag
- attest-release-evidence
- publish-to-pypi
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
actions: read
contents: write
steps:
- name: Download complete reviewed release evidence
- name: Download complete attested release evidence
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: release-evidence-${{ github.sha }}
name: attested-release-evidence-${{ github.sha }}
path: release-evidence
- name: Verify the immutable release tag
env:
Expand Down
13 changes: 8 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]

### Added
- Integrate deterministic CycloneDX 1.7 SBOMs into the protected release path,
sign each exact distribution with a credential-separated GitHub artifact
attestation, verify repository identity and predicate bytes before external
publication, and include the Sigstore bundles in checksummed release evidence.
- Add deterministic CycloneDX 1.7 SBOM generation that binds each canonical
wheel and source distribution to its exact SHA-256 and a reviewed, hash-pinned
runtime dependency graph. Protected attestation integration remains separate
Expand Down Expand Up @@ -40,12 +44,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
constructors accept positive integers or ASCII decimal strings and reject
ambiguous or non-positive configuration before network I/O.

### Fixed
- Correct the buyer-facing autonomous-maintainer identity from the retired Codex
wording to the pinned OpenCode execution path backed by `NVIDIA_NIM_API_KEY`,
without changing the centrally managed review-agent credential contract.

### Security
- Align the public autonomous-maintenance description with the executable
supply-chain boundary: the zero-PR product worker is pinned OpenCode backed by
`NVIDIA_NIM_API_KEY`, while the centrally managed review-agent identity and
inherited-secret contract remain unchanged.
- Reject PEP 508 extras in hash-locked runtime entries used for SBOM parity.
Extras can activate transitive packages outside the reviewed dependency graph,
so evidence generation now fails closed instead of understating executable
Expand Down
97 changes: 81 additions & 16 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,41 @@ a repository-secret fallback.
protected `main` head, reruns all quality gates, builds wheel and sdist with
hash-locked tooling, rejects any additional publishable archive, binds the
requested tag to package metadata and the dated changelog, smoke-tests the
installed wheel, and uploads two immutable artifact sets:
installed wheel, generates deterministic CycloneDX 1.7 SBOMs for both exact
distributions, and uploads two immutable artifact sets:
- canonical wheel and sdist only for PyPI;
- wheel, sdist, and `SHA256SUMS` as complete release evidence.
- wheel, sdist, both `.cdx.json` files, and `SHA256SUMS` as complete release
evidence.
4. A credential-separated tag job rechecks that the live protected `main` head
still equals the accepted workflow SHA, then creates the lightweight
`v<version>` tag at that exact reviewed commit. If `main` advanced after
acceptance, or the tag already exists at another commit, the run fails
rather than publishing stale evidence or moving the tag.
5. The `publish-to-pypi` job enters the protected `pypi` environment. Its only
steps download the canonical distribution artifact and invoke the pinned
PyPA Trusted Publishing action with attestations enabled. It receives no
repository-content write permission and no long-lived package-index token.
6. Only after PyPI succeeds, the final job rechecks that the tag still points to
the reviewed SHA, verifies `SHA256SUMS`, creates a draft GitHub Release with
all evidence attached, and then publishes that complete draft. It refuses to
overwrite an existing public release.
5. A credential-separated attestation job downloads only the exact checksummed
evidence, verifies it, and uses the immutable `actions/attest` v4 commit to
create one signed SBOM attestation for the wheel and one for the source
distribution. The job receives read-only repository access plus only
`id-token: write`, `attestations: write`, and the artifact-metadata permission
required by the reviewed action. It does not receive tag, release, package, or
pull-request write authority.
6. The same job immediately verifies the exact locally generated Sigstore
bundles with `gh attestation verify`. Verification requires repository
`ContextualWisdomLab/EgressWeave`, signer workflow
`.github/workflows/release.yml`, the exact protected-main source digest and
source ref, a GitHub-hosted runner, and predicate type
`https://cyclonedx.org/bom`. It then compares each verified predicate to the
generated CycloneDX 1.7 JSON, preserves both bundles, and refreshes
`SHA256SUMS` over the complete evidence set.
7. The `publish-to-pypi` job enters the protected `pypi` environment only after
signed SBOM verification. Its only steps download the canonical distribution
artifact and invoke the pinned PyPA Trusted Publishing action with
attestations enabled. It receives no repository-content write permission and
no long-lived package-index token.
8. Only after PyPI succeeds, the final job downloads the attested evidence,
rechecks that the tag still points to the reviewed SHA, verifies
`SHA256SUMS`, creates a draft GitHub Release with all evidence attached, and
then publishes that complete draft. It refuses to overwrite an existing
public release.

## Failure and retry semantics

Expand All @@ -111,16 +130,62 @@ a repository-secret fallback.

- Confirm PyPI shows both wheel and source distribution for the exact version.
- Inspect PyPI provenance and publish-attestation evidence.
- Download both artifacts and verify them against the attached `SHA256SUMS`.
- Download both distributions, both CycloneDX 1.7 SBOMs, the two signed SBOM
attestation bundles, and `SHA256SUMS`; verify the complete checksum set.
- Verify each distribution against the repository SBOM attestation, for example:

```bash
gh attestation verify egressweave-<version>-py3-none-any.whl \
--bundle wheel.sbom.attestation.json \
-R ContextualWisdomLab/EgressWeave \
--predicate-type https://cyclonedx.org/bom \
--signer-workflow \
ContextualWisdomLab/EgressWeave/.github/workflows/release.yml
```

Repeat for the source distribution and compare the verified predicate JSON to
the attached `.cdx.json` document. For an air-gapped verifier, obtain a trusted
root on an authenticated online system with `gh attestation trusted-root`, move
that root with the checksummed evidence, and add
`--custom-trusted-root trusted_root.jsonl` to the bundle verification command.
- Install the wheel in clean Python 3.10 and Python 3.13 environments and run a
minimal import/version check outside the source tree.
- Confirm the GitHub Release tag resolves to the exact workflow and protected
`main` commit.
- Restore an empty `[Unreleased]` section only in the next normal development PR.

## Authoritative references
A signed SBOM attestation is evidence binding an exact artifact digest to the
reviewed CycloneDX predicate and workflow identity. No SLSA Build level is
claimed merely because SBOMs, Sigstore bundles, PyPI attestations, or GitHub
artifact attestations exist; any future SLSA claim requires a separate mapping
to every normative requirement of the claimed level.

- [GitHub Docs: Manually running a workflow](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow)
- [GitHub Docs: Events that trigger workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows)
- [PyPI Docs: Publishing with a Trusted Publisher](https://docs.pypi.org/trusted-publishers/using-a-publisher/)
- [PyPI Docs: Trusted Publishing security model](https://docs.pypi.org/trusted-publishers/security-model/)
## Authoritative references — APA 7th

Ecma International, & OWASP Foundation. (2025). *CycloneDX specification 1.7
(ECMA-424).* https://cyclonedx.org/specification/overview/

GitHub. (n.d.). *Manually running a workflow.* GitHub Docs. Retrieved August 5,
2026, from https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow

GitHub. (n.d.). *Using artifact attestations to establish provenance for
builds.* GitHub Docs. Retrieved August 5, 2026, from
https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations

GitHub. (n.d.). *Verifying attestations offline.* GitHub Docs. Retrieved August
5, 2026, from
https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/verify-attestations-offline

GitHub. (2026). *actions/attest* (Version 4) [Computer software].
https://github.com/actions/attest

in-toto Project. (n.d.). *Predicate type: CycloneDX.* Retrieved August 5, 2026,
from https://github.com/in-toto/attestation/blob/main/spec/predicates/cyclonedx.md

Python Packaging Authority. (n.d.). *Publishing with a Trusted Publisher.* PyPI
Docs. Retrieved August 5, 2026, from
https://docs.pypi.org/trusted-publishers/using-a-publisher/

Python Packaging Authority. (n.d.). *Trusted Publishing security model.* PyPI
Docs. Retrieved August 5, 2026, from
https://docs.pypi.org/trusted-publishers/security-model/
Loading
Loading