From 8b27a20fcf9d9c80de16c381eb3fe44ed19d8668 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 21:15:33 +0000 Subject: [PATCH 01/11] ci: use draft releases to support immutable GitHub releases --- .github/workflows/manual-publish.yml | 44 ++++++++++++++++++----- .github/workflows/release-please.yml | 53 ++++++++++++++++++++++------ release-please-config.json | 3 +- 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index 85defd29..a09052b1 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -6,6 +6,15 @@ on: description: "Is this a dry run? If so no package will be published." type: boolean required: true + tag: + description: 'Tag of an existing draft release to upload artifacts to.' + type: string + required: false + publish_release: + description: 'Publish (un-draft) the release after all artifacts are uploaded?' + type: boolean + required: false + default: true jobs: build-publish: @@ -14,6 +23,7 @@ jobs: permissions: id-token: write contents: read + attestations: write # Needed for artifact attestations outputs: package-hashes: ${{ steps.build.outputs.package-hashes}} steps: @@ -41,13 +51,31 @@ jobs: with: password: ${{env.PYPI_AUTH_TOKEN}} - release-provenance: - needs: ["build-publish"] + - name: Generate checksums file + if: ${{ !inputs.dry_run }} + env: + HASHES: ${{ steps.build.outputs.package-hashes }} + run: | + echo "$HASHES" | base64 -d > checksums.txt + + - name: Attest build provenance + if: ${{ !inputs.dry_run }} + uses: actions/attest@v4 + with: + subject-checksums: checksums.txt + + publish-release: + needs: ['build-publish'] + if: ${{ !inputs.dry_run && inputs.publish_release }} + runs-on: ubuntu-latest permissions: - actions: read - id-token: write contents: write - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0 - with: - base64-subjects: "${{ needs.build-publish.outputs.package-hashes }}" - upload-assets: ${{ !inputs.dry_run }} + steps: + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ inputs.tag }} + run: > + gh release edit "$TAG_NAME" + --repo ${{ github.repository }} + --draft=false diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 091b7636..cafc1a74 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -11,6 +11,7 @@ jobs: id-token: write # Needed if using OIDC to get release secrets. contents: write # Contents and pull-requests are for release-please to make releases. pull-requests: write + attestations: write # Needed for artifact attestations outputs: release-created: ${{ steps.release.outputs.release_created }} upload-tag-name: ${{ steps.release.outputs.tag_name }} @@ -22,7 +23,23 @@ jobs: - uses: actions/checkout@v4 if: ${{ steps.release.outputs.releases_created == 'true' }} with: - fetch-depth: 0 # Full history is required for proper changelog generation + fetch-depth: 0 + + - name: Create release tag + if: ${{ steps.release.outputs.releases_created == 'true' }} + env: + TAG_NAME: ${{ steps.release.outputs.tag_name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then + echo "Tag ${TAG_NAME} already exists, skipping creation." + else + echo "Creating tag ${TAG_NAME}." + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "${TAG_NAME}" + git push origin "${TAG_NAME}" + fi - uses: actions/setup-python@v5 if: ${{ steps.release.outputs.releases_created == 'true' }} @@ -53,15 +70,31 @@ jobs: with: password: ${{env.PYPI_AUTH_TOKEN}} - release-provenance: - needs: ["release-package"] + - name: Generate checksums file + if: ${{ steps.release.outputs.releases_created == 'true' }} + env: + HASHES: ${{ steps.build.outputs.package-hashes }} + run: | + echo "$HASHES" | base64 -d > checksums.txt + + - name: Attest build provenance + if: ${{ steps.release.outputs.releases_created == 'true' }} + uses: actions/attest@v4 + with: + subject-checksums: checksums.txt + + publish-release: + needs: ['release-package'] if: ${{ needs.release-package.outputs.release-created == 'true' }} + runs-on: ubuntu-latest permissions: - actions: read - id-token: write contents: write - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0 - with: - base64-subjects: "${{ needs.release-package.outputs.package-hashes }}" - upload-assets: true - upload-tag-name: ${{ needs.release-package.outputs.upload-tag-name }} + steps: + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ needs.release-package.outputs.upload-tag-name }} + run: > + gh release edit "$TAG_NAME" + --repo ${{ github.repository }} + --draft=false diff --git a/release-please-config.json b/release-please-config.json index cf43e768..124f8875 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -8,7 +8,8 @@ "ldclient/version.py", "PROVENANCE.md" ], - "include-component-in-tag": false + "include-component-in-tag": false, + "draft": true } } } From 669851504d9ef1af78ceb205c6eb6997b91055c6 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 21:42:10 +0000 Subject: [PATCH 02/11] ci: add force-tag-creation and publish_release option --- release-please-config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release-please-config.json b/release-please-config.json index 124f8875..f4d6a9e3 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,7 +9,8 @@ "PROVENANCE.md" ], "include-component-in-tag": false, - "draft": true + "draft": true, + "force-tag-creation": true } } } From fda68d7a5fdcc7404c170a25ee4481b0f20947f9 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 22:06:51 +0000 Subject: [PATCH 03/11] ci: simplify for attestation-only releases (no draft needed) Since actions/attest@v4 stores attestations via GitHub's attestation API (not as release assets), repos that only use attestation don't need draft releases. Release-please can publish the release directly. Changes: - Remove draft:true from release-please-config.json - Remove create-tag job/steps (force-tag-creation handles this) - Remove publish-release job (release is published directly) - Remove publish_release input from manual workflows --- .github/workflows/manual-publish.yml | 21 ------------------ .github/workflows/release-please.yml | 32 ---------------------------- release-please-config.json | 1 - 3 files changed, 54 deletions(-) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index a09052b1..e4f211b0 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -10,11 +10,6 @@ on: description: 'Tag of an existing draft release to upload artifacts to.' type: string required: false - publish_release: - description: 'Publish (un-draft) the release after all artifacts are uploaded?' - type: boolean - required: false - default: true jobs: build-publish: @@ -63,19 +58,3 @@ jobs: uses: actions/attest@v4 with: subject-checksums: checksums.txt - - publish-release: - needs: ['build-publish'] - if: ${{ !inputs.dry_run && inputs.publish_release }} - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Publish release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ inputs.tag }} - run: > - gh release edit "$TAG_NAME" - --repo ${{ github.repository }} - --draft=false diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index cafc1a74..074c0181 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -25,22 +25,6 @@ jobs: with: fetch-depth: 0 - - name: Create release tag - if: ${{ steps.release.outputs.releases_created == 'true' }} - env: - TAG_NAME: ${{ steps.release.outputs.tag_name }} - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if gh api "repos/${{ github.repository }}/git/ref/tags/${TAG_NAME}" >/dev/null 2>&1; then - echo "Tag ${TAG_NAME} already exists, skipping creation." - else - echo "Creating tag ${TAG_NAME}." - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "${TAG_NAME}" - git push origin "${TAG_NAME}" - fi - - uses: actions/setup-python@v5 if: ${{ steps.release.outputs.releases_created == 'true' }} with: @@ -82,19 +66,3 @@ jobs: uses: actions/attest@v4 with: subject-checksums: checksums.txt - - publish-release: - needs: ['release-package'] - if: ${{ needs.release-package.outputs.release-created == 'true' }} - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Publish release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ needs.release-package.outputs.upload-tag-name }} - run: > - gh release edit "$TAG_NAME" - --repo ${{ github.repository }} - --draft=false diff --git a/release-please-config.json b/release-please-config.json index f4d6a9e3..0e887b8f 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,7 +9,6 @@ "PROVENANCE.md" ], "include-component-in-tag": false, - "draft": true, "force-tag-creation": true } } From 6378f68945eb09b3720b7fbf62caba39371b0b74 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 22:33:39 +0000 Subject: [PATCH 04/11] ci: remove force-tag-creation from attestation-only repo force-tag-creation only operates in conjunction with draft releases. Since this repo does not use draft releases (attestation-only, no artifact uploads to the release), force-tag-creation is not needed. --- release-please-config.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/release-please-config.json b/release-please-config.json index 0e887b8f..cf43e768 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -8,8 +8,7 @@ "ldclient/version.py", "PROVENANCE.md" ], - "include-component-in-tag": false, - "force-tag-creation": true + "include-component-in-tag": false } } } From 21f2567aa8d533e1b238eeeb9879de80fd39dc61 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 23:12:03 +0000 Subject: [PATCH 05/11] ci: switch from subject-checksums to subject-path for attestation --- .github/actions/build/action.yml | 10 ---------- .github/workflows/manual-publish.yml | 11 +---------- .github/workflows/release-please.yml | 10 +--------- 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 33e5280d..5cf127c1 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -1,9 +1,5 @@ name: Build distribution files description: 'Build distribution files' -outputs: - package-hashes: - description: "base64-encoded sha256 hashes of distribution files" - value: ${{ steps.package-hashes.outputs.package-hashes }} runs: using: composite @@ -11,9 +7,3 @@ runs: - name: Build distribution files shell: bash run: poetry build - - name: Hash build files for provenance - id: package-hashes - shell: bash - working-directory: ./dist - run: | - echo "package-hashes=$(sha256sum * | base64 -w0)" >> "$GITHUB_OUTPUT" \ No newline at end of file diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index e4f211b0..2ae93d9b 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -19,8 +19,6 @@ jobs: id-token: write contents: read attestations: write # Needed for artifact attestations - outputs: - package-hashes: ${{ steps.build.outputs.package-hashes}} steps: - uses: actions/checkout@v4 @@ -46,15 +44,8 @@ jobs: with: password: ${{env.PYPI_AUTH_TOKEN}} - - name: Generate checksums file - if: ${{ !inputs.dry_run }} - env: - HASHES: ${{ steps.build.outputs.package-hashes }} - run: | - echo "$HASHES" | base64 -d > checksums.txt - - name: Attest build provenance if: ${{ !inputs.dry_run }} uses: actions/attest@v4 with: - subject-checksums: checksums.txt + subject-path: 'dist/*' diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 074c0181..dc90545a 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -15,7 +15,6 @@ jobs: outputs: release-created: ${{ steps.release.outputs.release_created }} upload-tag-name: ${{ steps.release.outputs.tag_name }} - package-hashes: ${{ steps.build.outputs.package-hashes}} steps: - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 id: release @@ -54,15 +53,8 @@ jobs: with: password: ${{env.PYPI_AUTH_TOKEN}} - - name: Generate checksums file - if: ${{ steps.release.outputs.releases_created == 'true' }} - env: - HASHES: ${{ steps.build.outputs.package-hashes }} - run: | - echo "$HASHES" | base64 -d > checksums.txt - - name: Attest build provenance if: ${{ steps.release.outputs.releases_created == 'true' }} uses: actions/attest@v4 with: - subject-checksums: checksums.txt + subject-path: 'dist/*' From 6a189a124094afc15dd011aee2a56fa708634b93 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 16:34:21 +0000 Subject: [PATCH 06/11] ci: remove unused tag input and orphaned job outputs --- .github/workflows/manual-publish.yml | 4 ---- .github/workflows/release-please.yml | 3 --- 2 files changed, 7 deletions(-) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index 2ae93d9b..ec257507 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -6,10 +6,6 @@ on: description: "Is this a dry run? If so no package will be published." type: boolean required: true - tag: - description: 'Tag of an existing draft release to upload artifacts to.' - type: string - required: false jobs: build-publish: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index dc90545a..aa45d7d4 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -12,9 +12,6 @@ jobs: contents: write # Contents and pull-requests are for release-please to make releases. pull-requests: write attestations: write # Needed for artifact attestations - outputs: - release-created: ${{ steps.release.outputs.release_created }} - upload-tag-name: ${{ steps.release.outputs.tag_name }} steps: - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 id: release From efadac58ac865f49e84a63a1d8bc6aefc0523573 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 18:34:21 +0000 Subject: [PATCH 07/11] ci: fix inconsistent dry_run condition style --- .github/workflows/manual-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index ec257507..59850add 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -41,7 +41,7 @@ jobs: password: ${{env.PYPI_AUTH_TOKEN}} - name: Attest build provenance - if: ${{ !inputs.dry_run }} + if: ${{ inputs.dry_run == false }} uses: actions/attest@v4 with: subject-path: 'dist/*' From 3e77b7b9531d861991c2d77804a41d83cfe4ad6a Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 19:08:03 +0000 Subject: [PATCH 08/11] ci: use format() for dry_run conditions to handle both string and boolean inputs --- .github/workflows/manual-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index 59850add..b836ee57 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -35,13 +35,13 @@ jobs: id: build - name: Publish package distributions to PyPI - if: ${{ inputs.dry_run == false }} + if: ${{ format('{0}', inputs.dry_run) == 'false' }} uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: password: ${{env.PYPI_AUTH_TOKEN}} - name: Attest build provenance - if: ${{ inputs.dry_run == false }} + if: ${{ format('{0}', inputs.dry_run) == 'false' }} uses: actions/attest@v4 with: subject-path: 'dist/*' From 2bfd14077c54655113a7fa419ca03ef6829fd313 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 21:46:24 +0000 Subject: [PATCH 09/11] docs: update PROVENANCE.md and README.md for GitHub artifact attestations --- PROVENANCE.md | 34 +++++++++++++--------------------- README.md | 4 ++-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/PROVENANCE.md b/PROVENANCE.md index e5d7d715..8f838b3c 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -1,10 +1,10 @@ -## Verifying SDK build provenance with the SLSA framework +## Verifying SDK build provenance with GitHub artifact attestations -LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. +LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. -As part of [SLSA requirements for level 3 compliance](https://slsa.dev/spec/v1.0/requirements), LaunchDarkly publishes provenance about our SDK package builds using [GitHub's generic SLSA3 provenance generator](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#generation-of-slsa3-provenance-for-arbitrary-projects) for distribution alongside our packages. These attestations are available for download from the GitHub release page for the release version under Assets > `multiple.intoto.jsonl`. +LaunchDarkly publishes provenance about our SDK package builds using [GitHub's `actions/attest` action](https://github.com/actions/attest). These attestations are stored in GitHub's attestation API and can be verified using the [GitHub CLI](https://cli.github.com/). -To verify SLSA provenance attestations, we recommend using [slsa-verifier](https://github.com/slsa-framework/slsa-verifier). Example usage for verifying SDK packages is included below: +To verify build provenance attestations, we recommend using the [GitHub CLI `attestation verify` command](https://cli.github.com/manual/gh_attestation_verify). Example usage for verifying SDK packages is included below: ``` @@ -13,32 +13,24 @@ SDK_VERSION=9.15.0 ``` - ``` -# Download package from PyPi +# Download package from PyPI $ pip download --only-binary=:all: launchdarkly-server-sdk==${SDK_VERSION} -# Download provenance from Github release into same directory -$ curl --location -O \ - https://github.com/launchdarkly/python-server-sdk/releases/download/${SDK_VERSION}/multiple.intoto.jsonl - -# Run slsa-verifier to verify provenance against package artifacts -$ slsa-verifier verify-artifact \ ---provenance-path multiple.intoto.jsonl \ ---source-uri github.com/launchdarkly/python-server-sdk \ -launchdarkly_server_sdk-${SDK_VERSION}-py3-none-any.whl +# Verify provenance using the GitHub CLI +$ gh attestation verify launchdarkly_server_sdk-${SDK_VERSION}-py3-none-any.whl -R launchdarkly/python-server-sdk ``` Below is a sample of expected output. ``` -Verified signature against tlog entry index 76390194 at URL: https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77ac42700bfad5eb5597ea8bda92acb470aade248c01ccfc44047c0cd5b4433021a -Verified build using builder "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.7.0" at commit c7b42a2e7486904978d77cf05a07280e7e1bbec2 -Verifying artifact launchdarkly_server_sdk-9.2.2-py3-none-any.whl: PASSED +Loaded digest sha256:... for file://launchdarkly_server_sdk-9.15.0-py3-none-any.whl +Loaded 1 attestation from GitHub API +✓ Verification succeeded! -PASSED: Verified SLSA provenance +launchdarkly_server_sdk-9.15.0-py3-none-any.whl was attested by a trusted GitHub Actions workflow ``` -Alternatively, to verify the provenance manually, the SLSA framework specifies [recommendations for verifying build artifacts](https://slsa.dev/spec/v1.0/verifying-artifacts) in their documentation. +For more information, see [GitHub's documentation on verifying artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli). -**Note:** These instructions do not apply when building our SDKs from source. +**Note:** These instructions do not apply when building our SDKs from source. diff --git a/README.md b/README.md index 1917db35..55a7513c 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ We run integration tests for all our SDKs using a centralized test harness. This We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK. -## Verifying SDK build provenance with the SLSA framework +## Verifying SDK build provenance with GitHub artifact attestations -LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md). +LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md). ## About LaunchDarkly From 59d924570f8d8017fe8ca027c3a800a69a066d97 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 21:53:14 +0000 Subject: [PATCH 10/11] docs: use real gh attestation verify output template and --owner flag --- PROVENANCE.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/PROVENANCE.md b/PROVENANCE.md index 8f838b3c..298972ba 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -18,7 +18,7 @@ SDK_VERSION=9.15.0 $ pip download --only-binary=:all: launchdarkly-server-sdk==${SDK_VERSION} # Verify provenance using the GitHub CLI -$ gh attestation verify launchdarkly_server_sdk-${SDK_VERSION}-py3-none-any.whl -R launchdarkly/python-server-sdk +$ gh attestation verify launchdarkly_server_sdk-${SDK_VERSION}-py3-none-any.whl --owner launchdarkly ``` Below is a sample of expected output. @@ -26,11 +26,24 @@ Below is a sample of expected output. ``` Loaded digest sha256:... for file://launchdarkly_server_sdk-9.15.0-py3-none-any.whl Loaded 1 attestation from GitHub API + +The following policy criteria will be enforced: +- Predicate type must match:................ https://slsa.dev/provenance/v1 +- Source Repository Owner URI must match:... https://github.com/launchdarkly +- Subject Alternative Name must match regex: (?i)^https://github.com/launchdarkly/ +- OIDC Issuer must match:................... https://token.actions.githubusercontent.com + ✓ Verification succeeded! -launchdarkly_server_sdk-9.15.0-py3-none-any.whl was attested by a trusted GitHub Actions workflow +The following 1 attestation matched the policy criteria + +- Attestation #1 + - Build repo:..... launchdarkly/python-server-sdk + - Build workflow:. .github/workflows/release-please.yml + - Signer repo:.... launchdarkly/python-server-sdk + - Signer workflow: .github/workflows/release-please.yml ``` For more information, see [GitHub's documentation on verifying artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli). -**Note:** These instructions do not apply when building our SDKs from source. +**Note:** These instructions do not apply when building our SDKs from source. From 1e7fd436c2db28f0f7a9b07c31694ef8abc11114 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 22:11:58 +0000 Subject: [PATCH 11/11] docs: restore original SLSA framework text in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 55a7513c..db121c86 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ We run integration tests for all our SDKs using a centralized test harness. This We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK. -## Verifying SDK build provenance with GitHub artifact attestations +## Verifying SDK build provenance with the SLSA framework -LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md). +LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md). ## About LaunchDarkly