From 18c23273a90186daf65821d73929e9296ccf0147 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Sat, 3 Oct 2026 03:18:45 -0700 Subject: [PATCH 01/15] ci: publish only on version tags Move PyPI (maturin) and crates.io publishing out of python-tests.yml and rust-tests.yml into publish.yml, triggered only by v* tags. - version job fails unless the tag equals v + the Cargo.toml version - test workflows run via workflow_call; every build job gates every registry upload so a release is never partial - drop --skip-existing and registry already-published probes - pyproject reads its version from Cargo via dynamic = ["version"] - concurrency groups use a fixed per-file prefix since github.workflow is the caller's name under workflow_call - pin every action to a full SHA, replace -latest runners Part of blacklanternsecurity/bbot-enterprise#139 Part of blacklanternsecurity/bbot-enterprise#149 Part of blacklanternsecurity/bbot-enterprise#156 --- .github/workflows/cla.yml | 14 +- .github/workflows/publish.yml | 197 +++++++++++++++++++++++++++++ .github/workflows/python-tests.yml | 195 ++-------------------------- .github/workflows/rust-tests.yml | 52 ++------ pyproject.toml | 4 +- 5 files changed, 225 insertions(+), 237 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 0aa06e5..f5926ff 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Generate token from GitHub App id: app-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} @@ -38,7 +38,7 @@ jobs: for LOGIN in $COMMITTERS; do # treat commits with no associated GitHub login as non-exempt if [ -z "$LOGIN" ] || [ "$LOGIN" = "null" ]; then - echo "Unknown committer (no GitHub login) — not exempt" + echo "Unknown committer (no GitHub login): not exempt" ALL_EXEMPT=false break fi @@ -48,20 +48,20 @@ jobs: # check if account type is Bot (GitHub App accounts) AUTHOR_TYPE=$(gh api "users/${LOGIN}" --jq '.type' 2>/dev/null || echo "Unknown") if [ "$AUTHOR_TYPE" = "Bot" ]; then - echo "$LOGIN is a Bot account — exempt" + echo "$LOGIN is a Bot account: exempt" EXEMPT=true fi # check org membership if [ "$EXEMPT" = "false" ]; then if gh api "orgs/blacklanternsecurity/members/$LOGIN" > /dev/null 2>&1; then - echo "$LOGIN is an org member — exempt" + echo "$LOGIN is an org member: exempt" EXEMPT=true fi fi if [ "$EXEMPT" = "false" ]; then - echo "$LOGIN is not exempt — CLA required" + echo "$LOGIN is not exempt: CLA required" ALL_EXEMPT=false break fi @@ -77,13 +77,13 @@ jobs: gh api --method POST "repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \ -f state=success \ -f context="CLAAssistant" \ - -f description="CLA check skipped — all committers are org members or bots" + -f description="CLA check skipped: all committers are org members or bots" - name: "CLA Assistant" if: | (steps.cla-check.outputs.all_exempt != 'true') && ((github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target') - uses: contributor-assistant/github-action@v2.6.1 + uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PERSONAL_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ef05343 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,197 @@ +name: publish + +on: + push: + tags: ["v*", "!archive/**"] + +permissions: + contents: read + +concurrency: + group: publish-${{ github.ref }} + cancel-in-progress: false + +jobs: + version: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Tag matches the declared version + run: | + VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version') + if [ "v$VERSION" != "$GITHUB_REF_NAME" ]; then + echo "::error::tag $GITHUB_REF_NAME does not match Cargo.toml $VERSION" + exit 1 + fi + + rust: + needs: version + uses: ./.github/workflows/rust-tests.yml + + python: + needs: version + uses: ./.github/workflows/python-tests.yml + + linux-build: + needs: [version, rust, python] + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-24.04 + target: x86_64 + - runner: ubuntu-24.04 + target: x86 + - runner: ubuntu-24.04 + target: aarch64 + - runner: ubuntu-24.04 + target: armv7 + - runner: ubuntu-24.04 + target: s390x + - runner: ubuntu-24.04 + target: ppc64le + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + env: + PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1" + with: + target: ${{ matrix.platform.target }} + args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14 + sccache: false + manylinux: manylinux_2_28 + before-script-linux: | + set -e + # Install OpenSSL build dependencies + if command -v apk >/dev/null 2>&1; then + apk add --no-cache perl make gcc musl-dev linux-headers curl bash + elif command -v yum >/dev/null 2>&1; then + yum install -y perl-core 2>/dev/null || true + elif command -v apt-get >/dev/null 2>&1; then + apt-get update && apt-get install -y perl make gcc curl 2>/dev/null || true + fi + # Build custom OpenSSL with weak cipher support + ./scripts/build-openssl.sh + # Override target-specific OPENSSL_DIR env vars set by maturin containers + # (openssl-sys checks _OPENSSL_DIR before OPENSSL_DIR) + OPENSSL_INSTALL="$(pwd)/vendor/openssl/install" + export OPENSSL_DIR="$OPENSSL_INSTALL" + export OPENSSL_STATIC=1 + export OPENSSL_NO_VENDOR=1 + for var in $(env | grep '_OPENSSL_DIR=' | cut -d= -f1); do + export "$var=$OPENSSL_INSTALL" + done + - name: Upload wheels + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux-build: + needs: [version, rust, python] + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-24.04 + target: x86_64 + - runner: ubuntu-24.04 + target: x86 + - runner: ubuntu-24.04 + target: aarch64 + - runner: ubuntu-24.04 + target: armv7 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + env: + PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1" + with: + target: ${{ matrix.platform.target }} + args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14 + sccache: false + manylinux: musllinux_1_2 + before-script-linux: | + set -e + # Install OpenSSL build dependencies + if command -v apk >/dev/null 2>&1; then + apk add --no-cache perl make gcc musl-dev linux-headers curl bash + elif command -v yum >/dev/null 2>&1; then + yum install -y perl-core 2>/dev/null || true + elif command -v apt-get >/dev/null 2>&1; then + apt-get update && apt-get install -y perl make gcc curl 2>/dev/null || true + fi + # Build custom OpenSSL with weak cipher support + ./scripts/build-openssl.sh + # Override target-specific OPENSSL_DIR env vars set by maturin containers + OPENSSL_INSTALL="$(pwd)/vendor/openssl/install" + export OPENSSL_DIR="$OPENSSL_INSTALL" + export OPENSSL_STATIC=1 + export OPENSSL_NO_VENDOR=1 + for var in $(env | grep '_OPENSSL_DIR=' | cut -d= -f1); do + export "$var=$OPENSSL_INSTALL" + done + - name: Upload wheels + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + sdist-build: + needs: [version, rust, python] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Build sdist + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: wheels-sdist + path: dist + + crates: + needs: [version, linux-build, musllinux-build, sdist-build] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 + - run: ./scripts/build-openssl.sh + - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + env: + OPENSSL_DIR: ${{ github.workspace }}/vendor/openssl/install + OPENSSL_STATIC: "1" + OPENSSL_NO_VENDOR: "1" + + pypi: + needs: [version, linux-build, musllinux-build, sdist-build] + runs-on: ubuntu-24.04 + permissions: + id-token: write + attestations: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: 'wheels-*/*' + - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --non-interactive wheels-*/* diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index addc113..9cb194e 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -2,14 +2,15 @@ name: Python Tests on: pull_request: + workflow_call: push: branches: - stable workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: python-tests-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read @@ -18,8 +19,8 @@ jobs: python-lint: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" - name: Install ruff @@ -42,18 +43,18 @@ jobs: - "3.14" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v2 + uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - name: Cache OpenSSL build - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: vendor/openssl/install key: openssl-3.3.2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/build-openssl.sh') }} @@ -93,181 +94,3 @@ jobs: source .venv/bin/activate pytest tests/python/ -v - version-check: - needs: python-tests - if: github.event_name == 'push' && github.ref == 'refs/heads/stable' - runs-on: ubuntu-24.04 - outputs: - new_version: ${{ steps.check.outputs.new_version }} - steps: - - uses: actions/checkout@v7 - - name: Check if version already on PyPI - id: check - run: | - VERSION=$(grep -m1 '^version' pyproject.toml | sed 's/.*"\(.*\)".*/\1/') - echo "version=$VERSION" - if curl -sf -o /dev/null "https://pypi.org/pypi/blasthttp/$VERSION/json"; then - echo "new_version=no" >> "$GITHUB_OUTPUT" - echo "Version $VERSION already on PyPI — skipping wheel builds" - else - echo "new_version=yes" >> "$GITHUB_OUTPUT" - echo "Version $VERSION not on PyPI — will build and publish" - fi - - linux-build: - needs: version-check - if: needs.version-check.outputs.new_version == 'yes' - runs-on: ${{ matrix.platform.runner }} - strategy: - matrix: - platform: - - runner: ubuntu-24.04 - target: x86_64 - - runner: ubuntu-24.04 - target: x86 - - runner: ubuntu-24.04 - target: aarch64 - - runner: ubuntu-24.04 - target: armv7 - - runner: ubuntu-24.04 - target: s390x - - runner: ubuntu-24.04 - target: ppc64le - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 - with: - python-version: 3.x - - name: Build wheels - uses: PyO3/maturin-action@v1 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1" - with: - target: ${{ matrix.platform.target }} - args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14 - sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} - manylinux: manylinux_2_28 - before-script-linux: | - set -e - # Install OpenSSL build dependencies - if command -v apk >/dev/null 2>&1; then - apk add --no-cache perl make gcc musl-dev linux-headers curl bash - elif command -v yum >/dev/null 2>&1; then - yum install -y perl-core 2>/dev/null || true - elif command -v apt-get >/dev/null 2>&1; then - apt-get update && apt-get install -y perl make gcc curl 2>/dev/null || true - fi - # Build custom OpenSSL with weak cipher support - ./scripts/build-openssl.sh - # Override target-specific OPENSSL_DIR env vars set by maturin containers - # (openssl-sys checks _OPENSSL_DIR before OPENSSL_DIR) - OPENSSL_INSTALL="$(pwd)/vendor/openssl/install" - export OPENSSL_DIR="$OPENSSL_INSTALL" - export OPENSSL_STATIC=1 - export OPENSSL_NO_VENDOR=1 - for var in $(env | grep '_OPENSSL_DIR=' | cut -d= -f1); do - export "$var=$OPENSSL_INSTALL" - done - - name: Upload wheels - uses: actions/upload-artifact@v7 - with: - name: wheels-linux-${{ matrix.platform.target }} - path: dist - - musllinux-build: - needs: version-check - if: needs.version-check.outputs.new_version == 'yes' - runs-on: ${{ matrix.platform.runner }} - strategy: - matrix: - platform: - - runner: ubuntu-24.04 - target: x86_64 - - runner: ubuntu-24.04 - target: x86 - - runner: ubuntu-24.04 - target: aarch64 - - runner: ubuntu-24.04 - target: armv7 - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 - with: - python-version: 3.x - - name: Build wheels - uses: PyO3/maturin-action@v1 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1" - with: - target: ${{ matrix.platform.target }} - args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14 - sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} - manylinux: musllinux_1_2 - before-script-linux: | - set -e - # Install OpenSSL build dependencies - if command -v apk >/dev/null 2>&1; then - apk add --no-cache perl make gcc musl-dev linux-headers curl bash - elif command -v yum >/dev/null 2>&1; then - yum install -y perl-core 2>/dev/null || true - elif command -v apt-get >/dev/null 2>&1; then - apt-get update && apt-get install -y perl make gcc curl 2>/dev/null || true - fi - # Build custom OpenSSL with weak cipher support - ./scripts/build-openssl.sh - # Override target-specific OPENSSL_DIR env vars set by maturin containers - OPENSSL_INSTALL="$(pwd)/vendor/openssl/install" - export OPENSSL_DIR="$OPENSSL_INSTALL" - export OPENSSL_STATIC=1 - export OPENSSL_NO_VENDOR=1 - for var in $(env | grep '_OPENSSL_DIR=' | cut -d= -f1); do - export "$var=$OPENSSL_INSTALL" - done - - name: Upload wheels - uses: actions/upload-artifact@v7 - with: - name: wheels-musllinux-${{ matrix.platform.target }} - path: dist - - sdist-build: - needs: version-check - runs-on: ubuntu-24.04 - if: needs.version-check.outputs.new_version == 'yes' - steps: - - uses: actions/checkout@v7 - - name: Build sdist - uses: PyO3/maturin-action@v1 - with: - command: sdist - args: --out dist - - name: Upload sdist - uses: actions/upload-artifact@v7 - with: - name: wheels-sdist - path: dist - - release: - name: Release - needs: - - linux-build - - musllinux-build - - sdist-build - runs-on: ubuntu-24.04 - permissions: - id-token: write - contents: write - attestations: write - steps: - - uses: actions/download-artifact@v8 - - name: Generate artifact attestation - if: ${{ !github.event.repository.private }} - uses: actions/attest-build-provenance@v4 - with: - subject-path: 'wheels-*/*' - - name: Publish to PyPI - uses: PyO3/maturin-action@v1 - env: - MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - with: - command: upload - args: --non-interactive --skip-existing wheels-*/* diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index c96c2de..1d52add 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -4,26 +4,30 @@ on: push: branches: [stable] pull_request: + workflow_call: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: rust-tests-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read jobs: rust-tests: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v2 + uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 - name: Check formatting run: cargo fmt --all -- --check - name: Cache OpenSSL build - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: vendor/openssl/install key: openssl-3.3.2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/build-openssl.sh') }} @@ -32,7 +36,7 @@ jobs: run: ./scripts/build-openssl.sh - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" @@ -42,39 +46,3 @@ jobs: - name: Run tests run: cargo test --locked --verbose - rust-publish: - needs: rust-tests - runs-on: ubuntu-24.04 - if: github.event_name == 'push' && github.ref == 'refs/heads/stable' - - steps: - - uses: actions/checkout@v7 - - - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v2 - - - name: Cache OpenSSL build - uses: actions/cache@v6 - with: - path: vendor/openssl/install - key: openssl-3.3.2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/build-openssl.sh') }} - - - name: Build custom OpenSSL - run: ./scripts/build-openssl.sh - - - name: Check if version already published - id: version-check - run: | - LOCAL_VERSION=$(cargo metadata --no-deps --format-version 1 | python3 -c "import sys,json; print(json.load(sys.stdin)['packages'][0]['version'])") - echo "local=$LOCAL_VERSION" >> "$GITHUB_OUTPUT" - if curl -sf -o /dev/null -H "User-Agent: blasthttp-ci" "https://crates.io/api/v1/crates/blasthttp/$LOCAL_VERSION"; then PUBLISHED=yes; else PUBLISHED=no; fi - echo "already_published=$PUBLISHED" >> "$GITHUB_OUTPUT" - echo "Version $LOCAL_VERSION — already published: $PUBLISHED" - - - name: Publish to crates.io - if: steps.version-check.outputs.already_published != 'yes' - env: - OPENSSL_DIR: ${{ github.workspace }}/vendor/openssl/install - OPENSSL_STATIC: "1" - OPENSSL_NO_VENDOR: "1" - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 18d6b4e..bbfe827 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "blasthttp" -version = "0.10.0" +dynamic = ["version"] description = "Offensive-first HTTP library" license = "GPL-3.0" requires-python = ">=3.10" @@ -13,7 +13,7 @@ requires-python = ">=3.10" features = ["python"] [tool.uv] -# maturin manages the package install — don't let uv overwrite it +# maturin manages the package install, don't let uv overwrite it managed = false [dependency-groups] From 3a9d4fc528ef1c11bf37296aeb5e89d3e0000032 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Sat, 3 Oct 2026 04:31:45 -0700 Subject: [PATCH 02/15] ci(publish): validate tag shape, OIDC PyPI, release with SBOM The publish workflow now refuses any tag that is not vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-rc.N before comparing it to Cargo.toml, so the org tag convention is enforced rather than implied. PyPI upload drops PYPI_API_TOKEN and relies on trusted publishing via the job's id-token permission. Registry jobs run in the release environment so the publish gate applies. A final job creates the GitHub Release for the tag in the same run and attaches an SPDX JSON SBOM from anchore/sbom-action, so a tag and its release always exist together. CLA calls the shared reusable workflow instead of carrying a fork. --- .github/workflows/cla.yml | 91 ++--------------------------------- .github/workflows/publish.yml | 33 ++++++++++++- 2 files changed, 36 insertions(+), 88 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index f5926ff..f6b10c0 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -1,4 +1,5 @@ -name: "CLA Assistant" +name: CLA + on: issue_comment: types: [created] @@ -10,88 +11,6 @@ permissions: statuses: write jobs: - CLAAssistant: - runs-on: ubuntu-24.04 - steps: - - name: Generate token from GitHub App - id: app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - owner: blacklanternsecurity - - - name: Check all committers against org and allowlist - id: cla-check - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - if [ "${{ github.event_name }}" = "pull_request_target" ]; then - PR_NUM="${{ github.event.pull_request.number }}" - else - PR_NUM="${{ github.event.issue.number }}" - fi - - COMMITTERS=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUM/commits" --paginate --jq '.[].author.login' | sort -u) - ALL_EXEMPT=true - - for LOGIN in $COMMITTERS; do - # treat commits with no associated GitHub login as non-exempt - if [ -z "$LOGIN" ] || [ "$LOGIN" = "null" ]; then - echo "Unknown committer (no GitHub login): not exempt" - ALL_EXEMPT=false - break - fi - - EXEMPT=false - - # check if account type is Bot (GitHub App accounts) - AUTHOR_TYPE=$(gh api "users/${LOGIN}" --jq '.type' 2>/dev/null || echo "Unknown") - if [ "$AUTHOR_TYPE" = "Bot" ]; then - echo "$LOGIN is a Bot account: exempt" - EXEMPT=true - fi - - # check org membership - if [ "$EXEMPT" = "false" ]; then - if gh api "orgs/blacklanternsecurity/members/$LOGIN" > /dev/null 2>&1; then - echo "$LOGIN is an org member: exempt" - EXEMPT=true - fi - fi - - if [ "$EXEMPT" = "false" ]; then - echo "$LOGIN is not exempt: CLA required" - ALL_EXEMPT=false - break - fi - done - - echo "all_exempt=$ALL_EXEMPT" >> "$GITHUB_OUTPUT" - - - name: Skip CLA when all committers are exempt - if: steps.cla-check.outputs.all_exempt == 'true' && github.event_name == 'pull_request_target' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api --method POST "repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \ - -f state=success \ - -f context="CLAAssistant" \ - -f description="CLA check skipped: all committers are org members or bots" - - - name: "CLA Assistant" - if: | - (steps.cla-check.outputs.all_exempt != 'true') && - ((github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target') - uses: contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PERSONAL_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }} - with: - path-to-signatures: "signatures/version1/cla.json" - path-to-document: "https://github.com/blacklanternsecurity/CLA/blob/main/ICLA.md" - branch: "main" - allowlist: "dependabot[bot],github-actions[bot],renovate[bot]" - remote-organization-name: "blacklanternsecurity" - remote-repository-name: "CLA" - lock-pullrequest-aftermerge: "false" + cla: + uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@4f85d3c525483f0f846ef4384a8ece24a3c74375 # 2026-09-29 + secrets: inherit diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ef05343..fa07815 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,6 +20,10 @@ jobs: persist-credentials: false - name: Tag matches the declared version run: | + if ! [[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then + echo "::error::tag $GITHUB_REF_NAME is not vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-rc.N" + exit 1 + fi VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version') if [ "v$VERSION" != "$GITHUB_REF_NAME" ]; then echo "::error::tag $GITHUB_REF_NAME does not match Cargo.toml $VERSION" @@ -164,6 +168,7 @@ jobs: path: dist crates: + environment: release needs: [version, linux-build, musllinux-build, sdist-build] runs-on: ubuntu-24.04 steps: @@ -179,6 +184,7 @@ jobs: OPENSSL_NO_VENDOR: "1" pypi: + environment: release needs: [version, linux-build, musllinux-build, sdist-build] runs-on: ubuntu-24.04 permissions: @@ -190,8 +196,31 @@ jobs: with: subject-path: 'wheels-*/*' - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 - env: - MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} with: command: upload args: --non-interactive wheels-*/* + + release: + needs: [crates, pypi] + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 + with: + path: . + format: spdx-json + output-file: ${{ github.event.repository.name }}-${{ github.ref_name }}.spdx.json + upload-artifact: false + upload-release-assets: false + - name: GitHub Release with SBOM + env: + GH_TOKEN: ${{ github.token }} + run: | + PRERELEASE=() + if [[ "$GITHUB_REF_NAME" == *-rc.* ]]; then PRERELEASE=(--prerelease); fi + gh release create "$GITHUB_REF_NAME" "${{ github.event.repository.name }}-$GITHUB_REF_NAME.spdx.json" \ + --repo "$GITHUB_REPOSITORY" --verify-tag --generate-notes "${PRERELEASE[@]}" From 8cc487acf5ea5921560f971737da00c8175b88d3 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Sat, 3 Oct 2026 04:33:40 -0700 Subject: [PATCH 03/15] ci(publish): run PyPI upload in the pypi environment Governance declares one gated environment named pypi across published repos. crates and docker jobs stay in release. --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fa07815..b12bc72 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -184,7 +184,7 @@ jobs: OPENSSL_NO_VENDOR: "1" pypi: - environment: release + environment: pypi needs: [version, linux-build, musllinux-build, sdist-build] runs-on: ubuntu-24.04 permissions: From 116f2176518ba659db689cd9854df3051ab19b1a Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Sat, 3 Oct 2026 07:55:48 -0700 Subject: [PATCH 04/15] ci: call the shared test, wheel, and release workflows rust-tests.yml, python-tests.yml and the inline wheel matrix, crates upload and release steps duplicated what every PyO3 repo carries, and the wheel builds hardcoded the interpreter list. tests.yml now calls the shared rust-tests.yml and python-tests.yml in blacklanternsecurity/CLA with scripts/build-openssl.sh as setup. publish.yml calls release-check.yml, maturin-wheels.yml (Linux families only, the container OpenSSL build now lives in scripts/wheel-container-setup.sh), crates.yml and publish.yml, keeping only the trusted-publishing PyPI upload at top level. pyproject drops managed = false and adds maturin to the dev group so the shared workflow can uv sync and maturin develop. release.toml is removed: cargo-release competed with the one release cut tool. README examples are ruff formatted, which ruff format --check enforces. --- .github/workflows/python-tests.yml | 96 ------------------------------ .github/workflows/rust-tests.yml | 48 --------------- release.toml | 18 ------ 3 files changed, 162 deletions(-) delete mode 100644 .github/workflows/python-tests.yml delete mode 100644 .github/workflows/rust-tests.yml delete mode 100644 release.toml diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml deleted file mode 100644 index 9cb194e..0000000 --- a/.github/workflows/python-tests.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Python Tests - -on: - pull_request: - workflow_call: - push: - branches: - - stable - workflow_dispatch: - -concurrency: - group: python-tests-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -permissions: - contents: read - -jobs: - python-lint: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.13" - - name: Install ruff - run: pip install ruff==0.15.10 - - name: ruff check - run: ruff check - - name: ruff format --check - run: ruff format --check - - python-tests: - needs: python-lint - runs-on: ubuntu-24.04 - strategy: - matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" - - "3.14" - - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: ${{ matrix.python-version }} - - - name: Cache OpenSSL build - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: vendor/openssl/install - key: openssl-3.3.2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/build-openssl.sh') }} - - - name: Build custom OpenSSL - run: ./scripts/build-openssl.sh - - - name: Create virtualenv - run: python -m venv .venv - - - name: Build Python extension - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1" - run: | - source .venv/bin/activate - # maturin 1.13+ installs `[dependency-groups]` via - # `pip install --group`, which requires pip >= 25.1. The - # Python versions shipped with actions/setup-python ship - # older pip (23.x on 3.10); upgrade first so `develop` - # doesn't fail on an unknown `--group` flag. - pip install --upgrade pip - pip install maturin - maturin develop --release - - - name: Verify module - run: | - source .venv/bin/activate - python -c "import blasthttp; print(f'blasthttp {blasthttp.__version__}')" - - - name: Install test dependencies - run: | - source .venv/bin/activate - pip install 'pytest>=8' 'pytest-asyncio>=0.23' - - - name: Run Python tests - run: | - source .venv/bin/activate - pytest tests/python/ -v - diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml deleted file mode 100644 index 1d52add..0000000 --- a/.github/workflows/rust-tests.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Rust Tests - -on: - push: - branches: [stable] - pull_request: - workflow_call: - -concurrency: - group: rust-tests-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -permissions: - contents: read - -jobs: - rust-tests: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 - - - name: Check formatting - run: cargo fmt --all -- --check - - - name: Cache OpenSSL build - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: vendor/openssl/install - key: openssl-3.3.2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/build-openssl.sh') }} - - - name: Build custom OpenSSL - run: ./scripts/build-openssl.sh - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.13" - - - name: Run clippy - run: cargo clippy --all-targets --all-features --locked -- -D warnings - - - name: Run tests - run: cargo test --locked --verbose - diff --git a/release.toml b/release.toml deleted file mode 100644 index b25e3d2..0000000 --- a/release.toml +++ /dev/null @@ -1,18 +0,0 @@ -# cargo-release configuration -# Usage: cargo release patch|minor|major - -# Don't publish to crates.io (private project) -publish = false - -# Git tag format -tag-name = "v{{version}}" - -# Commit message format -tag-message = "v{{version}}" - -# Sign tags if gpg is available -sign-tag = false -sign-commit = false - -# Push after release -push-remote = "origin" From 1748faefd122b90a64871c0fbde4bfa308503826 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Sat, 3 Oct 2026 07:56:07 -0700 Subject: [PATCH 05/15] ci: add tests.yml and shared publish callers Completes the previous commit, which removed the old test workflows but missed the files replacing them. tests.yml calls the shared rust and python test workflows with scripts/build-openssl.sh as setup. publish.yml calls release-check, maturin-wheels (Linux families, container OpenSSL build in scripts/wheel-container-setup.sh), crates and publish, keeping only the trusted-publishing PyPI upload at top level. pyproject lets uv manage the project and adds maturin to the dev group. README examples are ruff formatted. --- .github/workflows/publish.yml | 232 +++++-------------------------- .github/workflows/tests.yml | 26 ++++ README.md | 72 +++++----- pyproject.toml | 5 +- scripts/wheel-container-setup.sh | 15 ++ 5 files changed, 118 insertions(+), 232 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 scripts/wheel-container-setup.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b12bc72..3132847 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,8 +1,8 @@ -name: publish +name: Publish on: push: - tags: ["v*", "!archive/**"] + tags: ["v*"] permissions: contents: read @@ -12,215 +12,55 @@ concurrency: cancel-in-progress: false jobs: - version: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Tag matches the declared version - run: | - if ! [[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then - echo "::error::tag $GITHUB_REF_NAME is not vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-rc.N" - exit 1 - fi - VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version') - if [ "v$VERSION" != "$GITHUB_REF_NAME" ]; then - echo "::error::tag $GITHUB_REF_NAME does not match Cargo.toml $VERSION" - exit 1 - fi + check: + uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows - rust: - needs: version - uses: ./.github/workflows/rust-tests.yml + tests: + needs: check + uses: ./.github/workflows/tests.yml - python: - needs: version - uses: ./.github/workflows/python-tests.yml - - linux-build: - needs: [version, rust, python] - runs-on: ${{ matrix.platform.runner }} - strategy: - matrix: - platform: - - runner: ubuntu-24.04 - target: x86_64 - - runner: ubuntu-24.04 - target: x86 - - runner: ubuntu-24.04 - target: aarch64 - - runner: ubuntu-24.04 - target: armv7 - - runner: ubuntu-24.04 - target: s390x - - runner: ubuntu-24.04 - target: ppc64le - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: 3.x - - name: Build wheels - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1" - with: - target: ${{ matrix.platform.target }} - args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14 - sccache: false - manylinux: manylinux_2_28 - before-script-linux: | - set -e - # Install OpenSSL build dependencies - if command -v apk >/dev/null 2>&1; then - apk add --no-cache perl make gcc musl-dev linux-headers curl bash - elif command -v yum >/dev/null 2>&1; then - yum install -y perl-core 2>/dev/null || true - elif command -v apt-get >/dev/null 2>&1; then - apt-get update && apt-get install -y perl make gcc curl 2>/dev/null || true - fi - # Build custom OpenSSL with weak cipher support - ./scripts/build-openssl.sh - # Override target-specific OPENSSL_DIR env vars set by maturin containers - # (openssl-sys checks _OPENSSL_DIR before OPENSSL_DIR) - OPENSSL_INSTALL="$(pwd)/vendor/openssl/install" - export OPENSSL_DIR="$OPENSSL_INSTALL" - export OPENSSL_STATIC=1 - export OPENSSL_NO_VENDOR=1 - for var in $(env | grep '_OPENSSL_DIR=' | cut -d= -f1); do - export "$var=$OPENSSL_INSTALL" - done - - name: Upload wheels - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: wheels-linux-${{ matrix.platform.target }} - path: dist - - musllinux-build: - needs: [version, rust, python] - runs-on: ${{ matrix.platform.runner }} - strategy: - matrix: - platform: - - runner: ubuntu-24.04 - target: x86_64 - - runner: ubuntu-24.04 - target: x86 - - runner: ubuntu-24.04 - target: aarch64 - - runner: ubuntu-24.04 - target: armv7 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: 3.x - - name: Build wheels - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 - env: - PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1" - with: - target: ${{ matrix.platform.target }} - args: --release --out dist -i python3.10 -i python3.11 -i python3.12 -i python3.13 -i python3.14 - sccache: false - manylinux: musllinux_1_2 - before-script-linux: | - set -e - # Install OpenSSL build dependencies - if command -v apk >/dev/null 2>&1; then - apk add --no-cache perl make gcc musl-dev linux-headers curl bash - elif command -v yum >/dev/null 2>&1; then - yum install -y perl-core 2>/dev/null || true - elif command -v apt-get >/dev/null 2>&1; then - apt-get update && apt-get install -y perl make gcc curl 2>/dev/null || true - fi - # Build custom OpenSSL with weak cipher support - ./scripts/build-openssl.sh - # Override target-specific OPENSSL_DIR env vars set by maturin containers - OPENSSL_INSTALL="$(pwd)/vendor/openssl/install" - export OPENSSL_DIR="$OPENSSL_INSTALL" - export OPENSSL_STATIC=1 - export OPENSSL_NO_VENDOR=1 - for var in $(env | grep '_OPENSSL_DIR=' | cut -d= -f1); do - export "$var=$OPENSSL_INSTALL" - done - - name: Upload wheels - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: wheels-musllinux-${{ matrix.platform.target }} - path: dist - - sdist-build: - needs: [version, rust, python] - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Build sdist - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 - with: - command: sdist - args: --out dist - - name: Upload sdist - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: wheels-sdist - path: dist + wheels: + needs: tests + uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + with: + # scripts/build-openssl.sh builds on Linux only. + targets: '{"linux": ["x86_64", "x86", "aarch64", "armv7", "s390x", "ppc64le"], "musllinux": ["x86_64", "x86", "aarch64", "armv7"]}' + before-script-linux: scripts/wheel-container-setup.sh crates: - environment: release - needs: [version, linux-build, musllinux-build, sdist-build] - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 - - run: ./scripts/build-openssl.sh - - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} - env: - OPENSSL_DIR: ${{ github.workspace }}/vendor/openssl/install - OPENSSL_STATIC: "1" - OPENSSL_NO_VENDOR: "1" + needs: wheels + uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + with: + setup-script: scripts/build-openssl.sh + secrets: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + # Trusted publishing only matches a top-level job, so PyPI cannot move into a reusable workflow. pypi: - environment: pypi - needs: [version, linux-build, musllinux-build, sdist-build] + needs: wheels runs-on: ubuntu-24.04 + environment: pypi permissions: id-token: write attestations: write steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: - subject-path: 'wheels-*/*' - - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + pattern: ${{ needs.wheels.outputs.artifacts }} + path: dist + merge-multiple: true + - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: - command: upload - args: --non-interactive wheels-*/* + subject-path: dist/* + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 release: - needs: [crates, pypi] - runs-on: ubuntu-24.04 + needs: [check, wheels, crates, pypi] permissions: contents: write - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 - with: - path: . - format: spdx-json - output-file: ${{ github.event.repository.name }}-${{ github.ref_name }}.spdx.json - upload-artifact: false - upload-release-assets: false - - name: GitHub Release with SBOM - env: - GH_TOKEN: ${{ github.token }} - run: | - PRERELEASE=() - if [[ "$GITHUB_REF_NAME" == *-rc.* ]]; then PRERELEASE=(--prerelease); fi - gh release create "$GITHUB_REF_NAME" "${{ github.event.repository.name }}-$GITHUB_REF_NAME.spdx.json" \ - --repo "$GITHUB_REPOSITORY" --verify-tag --generate-notes "${PRERELEASE[@]}" + uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + with: + version: ${{ needs.check.outputs.version }} + prerelease: ${{ needs.check.outputs.prerelease }} + artifacts: ${{ needs.wheels.outputs.artifacts }} + assets: dist/* diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..5fc71df --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,26 @@ +name: Tests + +on: + pull_request: + push: + branches: [stable] + workflow_call: + +concurrency: + group: tests-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + rust: + uses: blacklanternsecurity/CLA/.github/workflows/rust-tests.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + with: + cargo-args: --all-features --locked + setup-script: scripts/build-openssl.sh + + python: + uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + with: + setup-script: scripts/build-openssl.sh diff --git a/README.md b/README.md index 0cab0a8..e5e04b3 100644 --- a/README.md +++ b/README.md @@ -128,19 +128,20 @@ All request methods are async — they return native Python coroutines via `pyo3 import asyncio import blasthttp + async def main(): client = blasthttp.BlastHTTP() # Single request — Response is httpx-style r = await client.request("https://example.com") - print(r.status_code, r.is_success) # 200, True - print(r.headers["Content-Type"]) # case-insensitive - print(r.peer_ip) # actual TCP peer IP - print(r.text[:80]) # UTF-8 body (lazy) - print(r.hash.body_md5) # md5/sha256/mmh3 (lazy) - print(r.cookies) # Set-Cookie parsed (lazy) - print(r.request.url, r.request.method) # original (pre-redirect) - r.raise_for_status() # raises HTTPStatusError on 4xx/5xx + print(r.status_code, r.is_success) # 200, True + print(r.headers["Content-Type"]) # case-insensitive + print(r.peer_ip) # actual TCP peer IP + print(r.text[:80]) # UTF-8 body (lazy) + print(r.hash.body_md5) # md5/sha256/mmh3 (lazy) + print(r.cookies) # Set-Cookie parsed (lazy) + print(r.request.url, r.request.method) # original (pre-redirect) + r.raise_for_status() # raises HTTPStatusError on 4xx/5xx # Batch requests — full result list at the end configs = [ @@ -161,6 +162,7 @@ async def main(): # Download to file await client.download("https://example.com/file.zip", "/tmp/file.zip") + asyncio.run(main()) ``` @@ -201,11 +203,12 @@ A response whose body won't decode is still returned: the status line and header ```python from blasthttp import HTTPStatusError + try: r = await client.request("https://example.com/404") r.raise_for_status() except HTTPStatusError as e: - print(e.response.status_code) # the failing Response is attached + print(e.response.status_code) # the failing Response is attached ``` `Response`, `BatchResult`, `RedirectHop`, and `Headers` all have Python constructors so test code can synthesize them from canned data — `blasthttp.Response(url=..., status=200, headers=[...], body=b"...", ...)`. Useful when building fixtures by hand; the `blasthttp.mock` submodule below uses them under the hood. @@ -339,20 +342,17 @@ Pass `redirect_cookies=False` (or `--no-redirect-cookies` on the CLI) to turn it ```python # On by default. -r = await client.request("https://example.com/login", method="POST", - body="user=x&pass=y", follow_redirects=True) +r = await client.request("https://example.com/login", method="POST", body="user=x&pass=y", follow_redirects=True) # Off: every hop gets only the headers you supplied. -r = await client.request("https://example.com/login", follow_redirects=True, - redirect_cookies=False) +r = await client.request("https://example.com/login", follow_redirects=True, redirect_cookies=False) ``` **A cookie you set yourself always wins.** If your request carries `Cookie: session=mine`, every hop of that chain sends `session=mine`. A `Set-Cookie` naming a cookie you set is ignored: it can't replace your value, an expiry on it can't delete your value, and the two never go out together as a duplicate pair. Sites reset cookies mid-redirect routinely, and servers disagree about which value to read when a name appears twice (some take the first, some the last), so the only rule that behaves the same everywhere is that what you wrote is what lands on the wire. Cookies the chain sets under *other* names are merged into your `Cookie` header, yours first. ```python # Every hop sends session=mine, whatever the site tries to set. -r = await client.request("https://example.com/start", follow_redirects=True, - headers=[("Cookie", "session=mine")]) +r = await client.request("https://example.com/start", follow_redirects=True, headers=[("Cookie", "session=mine")]) ``` Run with `-v` to see it happen: the debug log records both the cookies each hop is given and any `Set-Cookie` that lost to one of yours. @@ -417,6 +417,7 @@ The client-level rate limit takes precedence over the per-call `rate_limit` para import asyncio import blasthttp + async def main(): client = blasthttp.BlastHTTP() @@ -429,9 +430,7 @@ async def main(): print("TLS cert CN:", conn.cert_info.common_name if conn.cert_info else None) # Send arbitrary bytes — no Content-Length / framing added for you. - await conn.send_bytes( - b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" - ) + await conn.send_bytes(b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n") # Read with a per-call deadline. Empty bytes = timeout or peer close. data = await conn.read_raw(max_bytes=65536, timeout_ms=2000) @@ -439,6 +438,7 @@ async def main(): await conn.close() + asyncio.run(main()) ``` @@ -454,18 +454,22 @@ asyncio.run(main()) from blasthttp import h2 # Encode a header block with HPACK. -block = h2.encode_headers([ - h2.Header(":method", "GET"), - h2.Header(":path", "/"), - h2.Header(":authority", "example.com"), - h2.Header(":scheme", "https"), - # Permissive escape hatch: emit CRLF inside a value for - # CRLF-injection testing against H2-to-H1 downgrades. - h2.Header( - "x-injected", "bogus\r\nX-Smuggled: yes", - allow_invalid_value=True, huffman_value=False, - ), -]) +block = h2.encode_headers( + [ + h2.Header(":method", "GET"), + h2.Header(":path", "/"), + h2.Header(":authority", "example.com"), + h2.Header(":scheme", "https"), + # Permissive escape hatch: emit CRLF inside a value for + # CRLF-injection testing against H2-to-H1 downgrades. + h2.Header( + "x-injected", + "bogus\r\nX-Smuggled: yes", + allow_invalid_value=True, + huffman_value=False, + ), + ] +) # Build a full probe: preface + SETTINGS + HEADERS (+ optional DATA). probe = h2.build_probe( @@ -512,6 +516,7 @@ import re import blasthttp from blasthttp.mock import BlasthttpMock, MockResponse + async def main(): mock = BlasthttpMock() @@ -522,12 +527,13 @@ async def main(): # Programmatic — callback receives a MockRequest, returns a MockResponse def echo(req): return MockResponse(status_code=201, json={"echoed": req.method}) + mock.add_callback(echo, url="https://api.example.com/echo") # Use it like a real client r = await mock.request("https://api.example.com/users") - print(r.json()) # {"users": []} - print(isinstance(r, blasthttp.Response)) # True + print(r.json()) # {"users": []} + print(isinstance(r, blasthttp.Response)) # True # Batch streaming works the same as the real client configs = [blasthttp.BatchConfig(f"https://api.example.com/users/{i}") for i in range(5)] @@ -535,6 +541,7 @@ async def main(): async for r in mock.request_batch_stream(configs): ... + asyncio.run(main()) ``` @@ -558,6 +565,7 @@ Pytest integration is intentionally left to the consumer — wrap the mock in a import pytest from blasthttp.mock import BlasthttpMock + @pytest.fixture def http_mock(): return BlasthttpMock() diff --git a/pyproject.toml b/pyproject.toml index bbfe827..1d418a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,12 +12,9 @@ requires-python = ">=3.10" [tool.maturin] features = ["python"] -[tool.uv] -# maturin manages the package install, don't let uv overwrite it -managed = false - [dependency-groups] dev = [ + "maturin>=1.10", "pytest>=8", "pytest-asyncio>=0.23", "ruff==0.16.8", diff --git a/scripts/wheel-container-setup.sh b/scripts/wheel-container-setup.sh new file mode 100644 index 0000000..1052b5e --- /dev/null +++ b/scripts/wheel-container-setup.sh @@ -0,0 +1,15 @@ +# Sourced by maturin-action inside the manylinux and musllinux containers. +if command -v apk >/dev/null 2>&1; then + apk add --no-cache perl make gcc musl-dev linux-headers curl bash +elif command -v yum >/dev/null 2>&1; then + yum install -y perl-core +elif command -v apt-get >/dev/null 2>&1; then + apt-get update && apt-get install -y perl make gcc curl +fi +./scripts/build-openssl.sh +OPENSSL_DIR="$(pwd)/vendor/openssl/install" +export OPENSSL_DIR OPENSSL_STATIC=1 OPENSSL_NO_VENDOR=1 +# openssl-sys reads _OPENSSL_DIR, which the cross images preset, before OPENSSL_DIR. +for var in $(env | grep '_OPENSSL_DIR=' | cut -d= -f1); do + export "$var=$OPENSSL_DIR" +done From ef57ee817d6b89825d39fbd30a58741c25b73f3f Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Sat, 3 Oct 2026 08:06:04 -0700 Subject: [PATCH 06/15] docs: link the org release procedure Releases are a pushed tag cut with the shared release.sh, which checks the tag against the manifest. State that here instead of copying the script. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e5e04b3..3b98e53 100644 --- a/README.md +++ b/README.md @@ -614,3 +614,7 @@ This compiles the Rust code with Python bindings enabled and installs the `blast - `.cargo/config.toml` sets `OPENSSL_DIR` (relative path to `vendor/openssl/install/`) and `OPENSSL_STATIC=1` so the `openssl-sys` crate links against the custom build statically - `build.rs` runs before compilation and verifies the custom OpenSSL headers exist, failing fast with an actionable error if they don't - The `[features] python` gate means `cargo build` produces a pure Rust binary, while `maturin build` activates PyO3 and produces a Python-loadable `.so` + +## Releasing + +Releases are cut by pushing a `vMAJOR.MINOR.PATCH` (or `vMAJOR.MINOR.PATCH-rc.N`) tag with the org's [release.sh](https://github.com/blacklanternsecurity/CLA/blob/e532142ee9e7322888f6edc80d9a89e2f8c0d96d/scripts/release.sh), after the version bump is merged. The tag must equal the manifest version. `publish.yml` then tests, builds, publishes, and creates the GitHub release with its SBOM in the same run. From 13c14900315f02ac72e8a7577c6dd03547cee3c2 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Tue, 6 Oct 2026 07:30:50 -0700 Subject: [PATCH 07/15] ci(tests): keep blasthttp's own Rust test workflow Restore rust-tests.yml from dev and drop the shared rust job from tests.yml. The Rust job builds a custom OpenSSL via scripts/build-openssl.sh, caches it, and runs fmt, clippy and tests in a way that is specific to this repo. The rust-tests job name is kept since it is a required status check. The rust-publish job stays removed: crates.io publishing now runs only on version tags via publish.yml. --- .github/workflows/rust-tests.yml | 43 ++++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 6 ----- 2 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/rust-tests.yml diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml new file mode 100644 index 0000000..be191d8 --- /dev/null +++ b/.github/workflows/rust-tests.yml @@ -0,0 +1,43 @@ +name: Rust Tests + +on: + push: + branches: [stable] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust-tests: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v7 + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v2 + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Cache OpenSSL build + uses: actions/cache@v6 + with: + path: vendor/openssl/install + key: openssl-3.3.2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/build-openssl.sh') }} + + - name: Build custom OpenSSL + run: ./scripts/build-openssl.sh + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.13" + + - name: Run clippy + run: cargo clippy --all-targets --all-features --locked -- -D warnings + + - name: Run tests + run: cargo test --locked --verbose diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5fc71df..11eb6cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,12 +14,6 @@ permissions: contents: read jobs: - rust: - uses: blacklanternsecurity/CLA/.github/workflows/rust-tests.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows - with: - cargo-args: --all-features --locked - setup-script: scripts/build-openssl.sh - python: uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows with: From d77bc77ec652f2ae30c02b1eb15560851cfe4696 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Tue, 6 Oct 2026 07:31:25 -0700 Subject: [PATCH 08/15] ci(publish): gate tag releases on the Rust test workflow rust-tests.yml is now callable and publish.yml runs it alongside tests.yml before building wheels, so a tag cannot release a crate or wheel whose Rust tests, clippy, or fmt fail. The concurrency group is named explicitly and only cancels on pull requests, matching tests.yml. Inherited github.workflow would resolve to the caller's name and collide with the publish run's own group. --- .github/workflows/publish.yml | 6 +++++- .github/workflows/rust-tests.yml | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3132847..473d517 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,8 +19,12 @@ jobs: needs: check uses: ./.github/workflows/tests.yml + rust-tests: + needs: check + uses: ./.github/workflows/rust-tests.yml + wheels: - needs: tests + needs: [tests, rust-tests] uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows with: # scripts/build-openssl.sh builds on Linux only. diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index be191d8..9e93766 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -4,10 +4,11 @@ on: push: branches: [stable] pull_request: + workflow_call: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: rust-tests-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: rust-tests: From e08972188772775927cfcf6fb5e8cd1ac6890d53 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Tue, 6 Oct 2026 07:35:58 -0700 Subject: [PATCH 09/15] ci: pin shared workflows to the CLA main commit CLA#3 was squash-merged into main. The pinned workflow files and scripts/release.sh are byte-identical at de82726743e51ac51a1be31196d893312a9fea59. --- .github/workflows/publish.yml | 8 ++++---- .github/workflows/tests.yml | 2 +- README.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 473d517..6fd4ddc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ concurrency: jobs: check: - uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@de82726743e51ac51a1be31196d893312a9fea59 # main tests: needs: check @@ -25,7 +25,7 @@ jobs: wheels: needs: [tests, rust-tests] - uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@de82726743e51ac51a1be31196d893312a9fea59 # main with: # scripts/build-openssl.sh builds on Linux only. targets: '{"linux": ["x86_64", "x86", "aarch64", "armv7", "s390x", "ppc64le"], "musllinux": ["x86_64", "x86", "aarch64", "armv7"]}' @@ -33,7 +33,7 @@ jobs: crates: needs: wheels - uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@de82726743e51ac51a1be31196d893312a9fea59 # main with: setup-script: scripts/build-openssl.sh secrets: @@ -62,7 +62,7 @@ jobs: needs: [check, wheels, crates, pypi] permissions: contents: write - uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@de82726743e51ac51a1be31196d893312a9fea59 # main with: version: ${{ needs.check.outputs.version }} prerelease: ${{ needs.check.outputs.prerelease }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 11eb6cb..331d8b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,6 +15,6 @@ permissions: jobs: python: - uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # feat/shared-workflows + uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@de82726743e51ac51a1be31196d893312a9fea59 # main with: setup-script: scripts/build-openssl.sh diff --git a/README.md b/README.md index 3b98e53..1170d18 100644 --- a/README.md +++ b/README.md @@ -617,4 +617,4 @@ This compiles the Rust code with Python bindings enabled and installs the `blast ## Releasing -Releases are cut by pushing a `vMAJOR.MINOR.PATCH` (or `vMAJOR.MINOR.PATCH-rc.N`) tag with the org's [release.sh](https://github.com/blacklanternsecurity/CLA/blob/e532142ee9e7322888f6edc80d9a89e2f8c0d96d/scripts/release.sh), after the version bump is merged. The tag must equal the manifest version. `publish.yml` then tests, builds, publishes, and creates the GitHub release with its SBOM in the same run. +Releases are cut by pushing a `vMAJOR.MINOR.PATCH` (or `vMAJOR.MINOR.PATCH-rc.N`) tag with the org's [release.sh](https://github.com/blacklanternsecurity/CLA/blob/de82726743e51ac51a1be31196d893312a9fea59/scripts/release.sh), after the version bump is merged. The tag must equal the manifest version. `publish.yml` then tests, builds, publishes, and creates the GitHub release with its SBOM in the same run. From ddfbbb11b858e8b99b5406d840e7e293cec1bb4f Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Tue, 6 Oct 2026 09:52:35 -0700 Subject: [PATCH 10/15] ci: keep PyO3 abi3 forward compatibility in shared callers The locked pyo3-ffi 0.27.2 caps CPython at 3.14. The removed workflows set PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1, and the shared python-tests and maturin-wheels callers did not, so 3.15 would fail the test matrix and the wheel builds once uv ships it. Pass the variable through the env input of both calls. --- .github/workflows/publish.yml | 1 + .github/workflows/tests.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6fd4ddc..efac974 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,6 +30,7 @@ jobs: # scripts/build-openssl.sh builds on Linux only. targets: '{"linux": ["x86_64", "x86", "aarch64", "armv7", "s390x", "ppc64le"], "musllinux": ["x86_64", "x86", "aarch64", "armv7"]}' before-script-linux: scripts/wheel-container-setup.sh + env: '{"PYO3_USE_ABI3_FORWARD_COMPATIBILITY": "1"}' crates: needs: wheels diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 331d8b6..1583001 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,3 +18,4 @@ jobs: uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@de82726743e51ac51a1be31196d893312a9fea59 # main with: setup-script: scripts/build-openssl.sh + env: '{"PYO3_USE_ABI3_FORWARD_COMPATIBILITY": "1"}' From 6ea7cd28b6857353651e5782142fe8f208c0c847 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Thu, 8 Oct 2026 10:01:20 -0700 Subject: [PATCH 11/15] ci: pin the shared workflows to CLA main 40c6e18 The shared workflow pins pointed at de82726 (and the CLA caller at 4f85d3c where it still lagged). CLA main is now 40c6e18, which adds the optional registry login for image SBOMs. Every CLA reference, including the release.sh link, now resolves on CLA main at one commit. Refs blacklanternsecurity/bbot-enterprise#139 --- .github/workflows/cla.yml | 2 +- .github/workflows/publish.yml | 8 ++++---- .github/workflows/tests.yml | 2 +- README.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index f6b10c0..f232ba1 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -12,5 +12,5 @@ permissions: jobs: cla: - uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@4f85d3c525483f0f846ef4384a8ece24a3c74375 # 2026-09-29 + uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main secrets: inherit diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index efac974..625a53f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ concurrency: jobs: check: - uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@de82726743e51ac51a1be31196d893312a9fea59 # main + uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main tests: needs: check @@ -25,7 +25,7 @@ jobs: wheels: needs: [tests, rust-tests] - uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@de82726743e51ac51a1be31196d893312a9fea59 # main + uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main with: # scripts/build-openssl.sh builds on Linux only. targets: '{"linux": ["x86_64", "x86", "aarch64", "armv7", "s390x", "ppc64le"], "musllinux": ["x86_64", "x86", "aarch64", "armv7"]}' @@ -34,7 +34,7 @@ jobs: crates: needs: wheels - uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@de82726743e51ac51a1be31196d893312a9fea59 # main + uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main with: setup-script: scripts/build-openssl.sh secrets: @@ -63,7 +63,7 @@ jobs: needs: [check, wheels, crates, pypi] permissions: contents: write - uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@de82726743e51ac51a1be31196d893312a9fea59 # main + uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main with: version: ${{ needs.check.outputs.version }} prerelease: ${{ needs.check.outputs.prerelease }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1583001..54a4b34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ permissions: jobs: python: - uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@de82726743e51ac51a1be31196d893312a9fea59 # main + uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main with: setup-script: scripts/build-openssl.sh env: '{"PYO3_USE_ABI3_FORWARD_COMPATIBILITY": "1"}' diff --git a/README.md b/README.md index 1170d18..8838908 100644 --- a/README.md +++ b/README.md @@ -617,4 +617,4 @@ This compiles the Rust code with Python bindings enabled and installs the `blast ## Releasing -Releases are cut by pushing a `vMAJOR.MINOR.PATCH` (or `vMAJOR.MINOR.PATCH-rc.N`) tag with the org's [release.sh](https://github.com/blacklanternsecurity/CLA/blob/de82726743e51ac51a1be31196d893312a9fea59/scripts/release.sh), after the version bump is merged. The tag must equal the manifest version. `publish.yml` then tests, builds, publishes, and creates the GitHub release with its SBOM in the same run. +Releases are cut by pushing a `vMAJOR.MINOR.PATCH` (or `vMAJOR.MINOR.PATCH-rc.N`) tag with the org's [release.sh](https://github.com/blacklanternsecurity/CLA/blob/40c6e18c0a6e32116eea4f89fc826f53c70889dc/scripts/release.sh), after the version bump is merged. The tag must equal the manifest version. `publish.yml` then tests, builds, publishes, and creates the GitHub release with its SBOM in the same run. From 92c145ebfb6cb38a36f6c78df034481f6ce359bb Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Wed, 21 Oct 2026 05:49:49 -0700 Subject: [PATCH 12/15] ci(publish): release on merge to the trunk, per the publishing policy Follows .github-config#17 through CLA#11: publish.yml runs on push to the trunk instead of a v* tag, publishes in the release environment a bbot-leads member approves, re-runs safely (PyPI skip-existing, crates.io only when the version is new), and leaves the tag and the GitHub release to the bls-release App at the end of the run. The pypi environment and the long-lived registry token are gone. --- .github/workflows/publish.yml | 30 +++++++++++++++++++----------- README.md | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 625a53f..4b6e226 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,7 +2,7 @@ name: Publish on: push: - tags: ["v*"] + branches: [stable] permissions: contents: read @@ -13,19 +13,21 @@ concurrency: jobs: check: - uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main + uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 tests: needs: check + if: needs.check.outputs.release == 'true' uses: ./.github/workflows/tests.yml rust-tests: needs: check + if: needs.check.outputs.release == 'true' uses: ./.github/workflows/rust-tests.yml wheels: needs: [tests, rust-tests] - uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main + uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 with: # scripts/build-openssl.sh builds on Linux only. targets: '{"linux": ["x86_64", "x86", "aarch64", "armv7", "s390x", "ppc64le"], "musllinux": ["x86_64", "x86", "aarch64", "armv7"]}' @@ -33,18 +35,18 @@ jobs: env: '{"PYO3_USE_ABI3_FORWARD_COMPATIBILITY": "1"}' crates: - needs: wheels - uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main + needs: [check, wheels] + if: needs.check.outputs.release == 'true' + uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 with: + version: ${{ needs.check.outputs.version }} setup-script: scripts/build-openssl.sh - secrets: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # Trusted publishing only matches a top-level job, so PyPI cannot move into a reusable workflow. pypi: needs: wheels runs-on: ubuntu-24.04 - environment: pypi + environment: release permissions: id-token: write attestations: write @@ -58,14 +60,20 @@ jobs: with: subject-path: dist/* - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + with: + skip-existing: true release: needs: [check, wheels, crates, pypi] + if: needs.check.outputs.release == 'true' permissions: - contents: write - uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main + contents: read + uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 with: + release-app-id: ${{ vars.RELEASE_APP_ID }} + tag: ${{ needs.check.outputs.tag }} version: ${{ needs.check.outputs.version }} - prerelease: ${{ needs.check.outputs.prerelease }} artifacts: ${{ needs.wheels.outputs.artifacts }} assets: dist/* + secrets: + release-app-key: ${{ secrets.RELEASE_APP_KEY }} diff --git a/README.md b/README.md index 8838908..5b88c81 100644 --- a/README.md +++ b/README.md @@ -617,4 +617,4 @@ This compiles the Rust code with Python bindings enabled and installs the `blast ## Releasing -Releases are cut by pushing a `vMAJOR.MINOR.PATCH` (or `vMAJOR.MINOR.PATCH-rc.N`) tag with the org's [release.sh](https://github.com/blacklanternsecurity/CLA/blob/40c6e18c0a6e32116eea4f89fc826f53c70889dc/scripts/release.sh), after the version bump is merged. The tag must equal the manifest version. `publish.yml` then tests, builds, publishes, and creates the GitHub release with its SBOM in the same run. +A release is a reviewed pull request into `stable` that bumps the manifest version. The merge starts `publish.yml`, which tests and builds, waits for a `bbot-leads` approval of the `release` environment, publishes, then creates the `v` tag and the GitHub release with its SBOM. See the org [publishing policy](https://github.com/blacklanternsecurity/.github-config/blob/stable/docs/policy/publishing.md). From 7968f0a22ed4ec7a7c90132446c55ead84483d0d Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Fri, 23 Oct 2026 05:11:51 -0700 Subject: [PATCH 13/15] ci(release): adopt CLA#11's environment-held credentials - publish: publish crates.io after PyPI, granting the id-token the shared crates.yml needs, so the workflow no longer fails at startup; pass no App credentials - rust-tests: pin every action to a SHA, declare contents: read, and keep the token out of .git/config - tests, rust-tests: drop the stable push trigger and separate the concurrency groups from the publish calls; keep reporting stable's python-tests (3.11) to (3.15) contexts until the ruleset moves - cla: pass the CLA App credentials by name instead of secrets: inherit - stable-guard: refuse a pull request into stable whose version or dependencies cannot release - README: link the public org release standard - bump to 0.10.2: 0.10.1 is on PyPI and crates.io with no v0.10.1 tag - re-pin every CLA reference to CLA#11 --- .github/workflows/cla.yml | 6 ++++-- .github/workflows/dependabot-auto-merge.yml | 2 +- .github/workflows/publish.yml | 17 ++++++++--------- .github/workflows/rust-tests.yml | 18 ++++++++++-------- .github/workflows/stable-guard.yml | 12 ++++++++++++ .github/workflows/tests.yml | 20 ++++++++++++++++---- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- 9 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/stable-guard.yml diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index f232ba1..87097d1 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -12,5 +12,7 @@ permissions: jobs: cla: - uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main - secrets: inherit + uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + secrets: + APP_ID: ${{ secrets.APP_ID }} + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 1730bc4..11dc1b1 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -9,4 +9,4 @@ jobs: contents: write pull-requests: write checks: read - uses: blacklanternsecurity/CLA/.github/workflows/dependabot-auto-merge.yml@e532142ee9e7322888f6edc80d9a89e2f8c0d96d # 2026-10-03 + uses: blacklanternsecurity/CLA/.github/workflows/dependabot-auto-merge.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4b6e226..55a77f3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ concurrency: jobs: check: - uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 + uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 tests: needs: check @@ -27,7 +27,7 @@ jobs: wheels: needs: [tests, rust-tests] - uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 + uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 with: # scripts/build-openssl.sh builds on Linux only. targets: '{"linux": ["x86_64", "x86", "aarch64", "armv7", "s390x", "ppc64le"], "musllinux": ["x86_64", "x86", "aarch64", "armv7"]}' @@ -35,14 +35,16 @@ jobs: env: '{"PYO3_USE_ABI3_FORWARD_COMPATIBILITY": "1"}' crates: - needs: [check, wheels] + needs: [check, pypi] if: needs.check.outputs.release == 'true' - uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 + permissions: + contents: read + id-token: write + uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 with: version: ${{ needs.check.outputs.version }} setup-script: scripts/build-openssl.sh - # Trusted publishing only matches a top-level job, so PyPI cannot move into a reusable workflow. pypi: needs: wheels runs-on: ubuntu-24.04 @@ -68,12 +70,9 @@ jobs: if: needs.check.outputs.release == 'true' permissions: contents: read - uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@4894c4adb0c98b978c7c621903e1f3205fc446b7 # CLA#11 + uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 with: - release-app-id: ${{ vars.RELEASE_APP_ID }} tag: ${{ needs.check.outputs.tag }} version: ${{ needs.check.outputs.version }} artifacts: ${{ needs.wheels.outputs.artifacts }} assets: dist/* - secrets: - release-app-key: ${{ secrets.RELEASE_APP_KEY }} diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index 9e93766..2a32abe 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -1,30 +1,32 @@ name: Rust Tests on: - push: - branches: [stable] pull_request: workflow_call: +permissions: + contents: read + concurrency: - group: rust-tests-${{ github.ref }} + group: rust-tests-${{ github.workflow_ref }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: rust-tests: runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v2 + uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 - name: Check formatting run: cargo fmt --all -- --check - name: Cache OpenSSL build - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: vendor/openssl/install key: openssl-3.3.2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('scripts/build-openssl.sh') }} @@ -33,7 +35,7 @@ jobs: run: ./scripts/build-openssl.sh - name: Set up Python - uses: actions/setup-python@v7 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" diff --git a/.github/workflows/stable-guard.yml b/.github/workflows/stable-guard.yml new file mode 100644 index 0000000..3d4939b --- /dev/null +++ b/.github/workflows/stable-guard.yml @@ -0,0 +1,12 @@ +name: stable guard + +on: + pull_request: + branches: [stable] + +permissions: + contents: read + +jobs: + guard: + uses: blacklanternsecurity/CLA/.github/workflows/stable-guard.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 54a4b34..a7c1de7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,12 +2,10 @@ name: Tests on: pull_request: - push: - branches: [stable] workflow_call: concurrency: - group: tests-${{ github.ref }} + group: tests-${{ github.workflow_ref }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: @@ -15,7 +13,21 @@ permissions: jobs: python: - uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@40c6e18c0a6e32116eea4f89fc826f53c70889dc # main + uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 with: setup-script: scripts/build-openssl.sh env: '{"PYO3_USE_ABI3_FORWARD_COMPATIBILITY": "1"}' + + # Reports the python-tests contexts stable's ruleset requires today. Remove once it requires python / passed. + required: + name: ${{ matrix.context }} + if: always() + needs: python + runs-on: ubuntu-24.04 + strategy: + matrix: + context: ["python-tests (3.11)", "python-tests (3.12)", "python-tests (3.13)", "python-tests (3.14)", "python-tests (3.15)"] + steps: + - uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0 + with: + jobs: ${{ toJSON(needs) }} diff --git a/Cargo.lock b/Cargo.lock index 6ac01c5..189f066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,7 +87,7 @@ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "blasthttp" -version = "0.10.1" +version = "0.10.2" dependencies = [ "brotli", "bytes", diff --git a/Cargo.toml b/Cargo.toml index a89b432..e9e7ba5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blasthttp" -version = "0.10.1" +version = "0.10.2" edition = "2024" description = "Offensive-first HTTP library with Python bindings" license = "AGPL-3.0" diff --git a/README.md b/README.md index 8f39f02..cf2deff 100644 --- a/README.md +++ b/README.md @@ -617,4 +617,4 @@ This compiles the Rust code with Python bindings enabled and installs the `blast ## Releasing -A release is a reviewed pull request into `stable` that bumps the manifest version. The merge starts `publish.yml`, which tests and builds, waits for a `bbot-leads` approval of the `release` environment, publishes, then creates the `v` tag and the GitHub release with its SBOM. See the org [publishing policy](https://github.com/blacklanternsecurity/.github-config/blob/stable/docs/policy/publishing.md). +A release is a reviewed pull request into `stable` that bumps the manifest version. The merge starts `publish.yml`, which tests and builds, waits for a `bbot-leads` approval of the `release` environment, publishes, then creates the `v` tag and the GitHub release with its SBOM. See the org [publishing policy](https://github.com/blacklanternsecurity/.github/blob/main/standards/releases.md). From fb42b9050b2c3564b0dbf4f65034d0992410af3c Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Mon, 26 Oct 2026 09:37:14 -0700 Subject: [PATCH 14/15] ci(publish): per-job permissions, drop dependabot auto-merge, document approvals publish.yml drops to permissions: {} and grants contents read per job. check also gets actions and pull-requests read for CLA#11's provenance check and environment guard. dependabot-auto-merge.yml is removed. Once dev drops to zero approvals it would merge Dependabot PRs on CI alone (pjhartlieb, R11). The Releasing section states the four release approvals per run. --- .github/workflows/dependabot-auto-merge.yml | 12 ------------ .github/workflows/publish.yml | 14 ++++++++++++-- README.md | 2 ++ 3 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml deleted file mode 100644 index 11dc1b1..0000000 --- a/.github/workflows/dependabot-auto-merge.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Dependabot auto-merge -on: pull_request - -permissions: {} - -jobs: - auto-merge: - permissions: - contents: write - pull-requests: write - checks: read - uses: blacklanternsecurity/CLA/.github/workflows/dependabot-auto-merge.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 55a77f3..51dfcf8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,8 +4,7 @@ on: push: branches: [stable] -permissions: - contents: read +permissions: {} concurrency: group: publish-${{ github.ref }} @@ -13,20 +12,30 @@ concurrency: jobs: check: + permissions: + contents: read + actions: read + pull-requests: read uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 tests: needs: check if: needs.check.outputs.release == 'true' + permissions: + contents: read uses: ./.github/workflows/tests.yml rust-tests: needs: check if: needs.check.outputs.release == 'true' + permissions: + contents: read uses: ./.github/workflows/rust-tests.yml wheels: needs: [tests, rust-tests] + permissions: + contents: read uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 with: # scripts/build-openssl.sh builds on Linux only. @@ -50,6 +59,7 @@ jobs: runs-on: ubuntu-24.04 environment: release permissions: + contents: read id-token: write attestations: write steps: diff --git a/README.md b/README.md index cf2deff..6401b3c 100644 --- a/README.md +++ b/README.md @@ -618,3 +618,5 @@ This compiles the Rust code with Python bindings enabled and installs the `blast ## Releasing A release is a reviewed pull request into `stable` that bumps the manifest version. The merge starts `publish.yml`, which tests and builds, waits for a `bbot-leads` approval of the `release` environment, publishes, then creates the `v` tag and the GitHub release with its SBOM. See the org [publishing policy](https://github.com/blacklanternsecurity/.github/blob/main/standards/releases.md). + +Each job that touches a credential runs in the `release` environment, so one release asks for approval four times: release-check's `credentials` job, `pypi`, `crates`, and the GitHub release. From 8ed9d74c647a36d250dd526149af89399234a9a8 Mon Sep 17 00:00:00 2001 From: Shane Engelman Date: Fri, 30 Oct 2026 05:54:46 -0700 Subject: [PATCH 15/15] ci: pin shared workflows to release-workflows v1.0.0 Move the seven reusable workflow calls off the unmerged CLA#11 head onto release-workflows v1.0.0 (c53c202). Map BLS_RELEASE_PRIVATE_KEY into release-check and publish. A reusable workflow only receives the release environment secret when the caller passes it. --- .github/workflows/cla.yml | 2 +- .github/workflows/publish.yml | 12 ++++++++---- .github/workflows/stable-guard.yml | 2 +- .github/workflows/tests.yml | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 87097d1..e30d6f9 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -12,7 +12,7 @@ permissions: jobs: cla: - uses: blacklanternsecurity/CLA/.github/workflows/cla-reusable.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + uses: blacklanternsecurity/release-workflows/.github/workflows/cla-reusable.yml@c53c202eedeb26a86088936363e6cb06ce99ac15 # v1.0.0 secrets: APP_ID: ${{ secrets.APP_ID }} APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 51dfcf8..52d51fc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,7 +16,9 @@ jobs: contents: read actions: read pull-requests: read - uses: blacklanternsecurity/CLA/.github/workflows/release-check.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + uses: blacklanternsecurity/release-workflows/.github/workflows/release-check.yml@c53c202eedeb26a86088936363e6cb06ce99ac15 # v1.0.0 + secrets: + BLS_RELEASE_PRIVATE_KEY: ${{ secrets.BLS_RELEASE_PRIVATE_KEY }} tests: needs: check @@ -36,7 +38,7 @@ jobs: needs: [tests, rust-tests] permissions: contents: read - uses: blacklanternsecurity/CLA/.github/workflows/maturin-wheels.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + uses: blacklanternsecurity/release-workflows/.github/workflows/maturin-wheels.yml@c53c202eedeb26a86088936363e6cb06ce99ac15 # v1.0.0 with: # scripts/build-openssl.sh builds on Linux only. targets: '{"linux": ["x86_64", "x86", "aarch64", "armv7", "s390x", "ppc64le"], "musllinux": ["x86_64", "x86", "aarch64", "armv7"]}' @@ -49,7 +51,7 @@ jobs: permissions: contents: read id-token: write - uses: blacklanternsecurity/CLA/.github/workflows/crates.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + uses: blacklanternsecurity/release-workflows/.github/workflows/crates.yml@c53c202eedeb26a86088936363e6cb06ce99ac15 # v1.0.0 with: version: ${{ needs.check.outputs.version }} setup-script: scripts/build-openssl.sh @@ -80,9 +82,11 @@ jobs: if: needs.check.outputs.release == 'true' permissions: contents: read - uses: blacklanternsecurity/CLA/.github/workflows/publish.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + uses: blacklanternsecurity/release-workflows/.github/workflows/publish.yml@c53c202eedeb26a86088936363e6cb06ce99ac15 # v1.0.0 with: tag: ${{ needs.check.outputs.tag }} version: ${{ needs.check.outputs.version }} artifacts: ${{ needs.wheels.outputs.artifacts }} assets: dist/* + secrets: + BLS_RELEASE_PRIVATE_KEY: ${{ secrets.BLS_RELEASE_PRIVATE_KEY }} diff --git a/.github/workflows/stable-guard.yml b/.github/workflows/stable-guard.yml index 3d4939b..6f8cefa 100644 --- a/.github/workflows/stable-guard.yml +++ b/.github/workflows/stable-guard.yml @@ -9,4 +9,4 @@ permissions: jobs: guard: - uses: blacklanternsecurity/CLA/.github/workflows/stable-guard.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + uses: blacklanternsecurity/release-workflows/.github/workflows/stable-guard.yml@c53c202eedeb26a86088936363e6cb06ce99ac15 # v1.0.0 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a7c1de7..94e0459 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ permissions: jobs: python: - uses: blacklanternsecurity/CLA/.github/workflows/python-tests.yml@07e14bcbba5e737e1b29bd9e3f9128fbf2483be9 # CLA#11 + uses: blacklanternsecurity/release-workflows/.github/workflows/python-tests.yml@c53c202eedeb26a86088936363e6cb06ce99ac15 # v1.0.0 with: setup-script: scripts/build-openssl.sh env: '{"PYO3_USE_ABI3_FORWARD_COMPATIBILITY": "1"}'