From c12178732f2db26f6ec2ffea2883d78ffca2748c Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 21:16:03 +0000 Subject: [PATCH 01/10] ci: use draft releases to support immutable GitHub releases --- .github/workflows/manual-publish.yml | 24 ++++++------- .github/workflows/release-please.yml | 50 ++++++++++++++++++++++------ release-please-config.json | 1 + 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index d0340da..e656c0b 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -14,8 +14,7 @@ jobs: permissions: id-token: write contents: read - outputs: - package-hashes: ${{ steps.ci.outputs.package-hashes }} + attestations: write steps: - uses: actions/checkout@v4 @@ -43,13 +42,14 @@ jobs: token: ${{ env.HACKAGE_TOKEN }} dry_run: ${{ inputs.dry_run }} - release-provenance: - needs: ["build-publish"] - 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 }} + - name: Generate checksums file + env: + HASHES: ${{ steps.ci.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 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 7756729..6340a32 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -13,10 +13,10 @@ 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 outputs: release-created: ${{ steps.release.outputs.release_created }} upload-tag-name: ${{ steps.release.outputs.tag_name }} - package-hashes: ${{ steps.ci.outputs.package-hashes }} steps: - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4 @@ -34,6 +34,20 @@ jobs: with: branch: ${{ fromJSON(steps.release.outputs.pr).headBranchName }} + - 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 tag "${TAG_NAME}" + git push origin "${TAG_NAME}" + fi + # # These remaining steps are ONLY run if a release was actually created # @@ -73,15 +87,31 @@ jobs: # and another token, then add more tokens to the composite action. token: ${{secrets.GITHUB_TOKEN}} - release-provenance: - needs: ["release-package"] + - name: Generate checksums file + if: ${{ steps.release.outputs.releases_created == 'true' }} + env: + HASHES: ${{ steps.ci.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 2d28dab..60b2347 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,6 +1,7 @@ { "packages": { ".": { + "draft": true, "release-type": "simple", "bump-minor-pre-major": true, "versioning": "default", From 55a6d5366ca091df03d22438152cdc091d58cbb8 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 21:42:28 +0000 Subject: [PATCH 02/10] ci: add force-tag-creation and publish_release option --- .github/workflows/manual-publish.yml | 25 +++++++++++++++++++++++++ release-please-config.json | 1 + 2 files changed, 26 insertions(+) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index e656c0b..b031a98 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: @@ -53,3 +62,19 @@ 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/release-please-config.json b/release-please-config.json index 60b2347..11df8a7 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -2,6 +2,7 @@ "packages": { ".": { "draft": true, + "force-tag-creation": true, "release-type": "simple", "bump-minor-pre-major": true, "versioning": "default", From 9ba587a09cc4a7d782008eefc164ae5081031979 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 22:06:51 +0000 Subject: [PATCH 03/10] 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 | 33 ---------------------------- release-please-config.json | 1 - 3 files changed, 55 deletions(-) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index b031a98..4fb29f6 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: @@ -62,19 +57,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 6340a32..3033d03 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -34,23 +34,6 @@ jobs: with: branch: ${{ fromJSON(steps.release.outputs.pr).headBranchName }} - - 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 tag "${TAG_NAME}" - git push origin "${TAG_NAME}" - fi - - # - # These remaining steps are ONLY run if a release was actually created - # - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0 name: "Get Hackage token" if: ${{ steps.release.outputs.releases_created == 'true' }} @@ -99,19 +82,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 11df8a7..63be229 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,7 +1,6 @@ { "packages": { ".": { - "draft": true, "force-tag-creation": true, "release-type": "simple", "bump-minor-pre-major": true, From dce8b659d30f824e807a38cc5446d4863058b6a3 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 22:33:47 +0000 Subject: [PATCH 04/10] 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 | 1 - 1 file changed, 1 deletion(-) diff --git a/release-please-config.json b/release-please-config.json index 63be229..2d28dab 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,7 +1,6 @@ { "packages": { ".": { - "force-tag-creation": true, "release-type": "simple", "bump-minor-pre-major": true, "versioning": "default", From 32009e7b6105472dff916ad99d902ab43ea802d3 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 22:55:02 +0000 Subject: [PATCH 05/10] ci: add dry_run guard to checksums generation in manual-publish workflow The attest step was already guarded, but the checksums file generation was not. Now both steps are skipped during dry runs. --- .github/workflows/manual-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index 4fb29f6..e85b87c 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -47,6 +47,7 @@ jobs: dry_run: ${{ inputs.dry_run }} - name: Generate checksums file + if: ${{ !inputs.dry_run }} env: HASHES: ${{ steps.ci.outputs.package-hashes }} run: | From 73856f023b32b0df20b24437b5b01bdb0e8ab866 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Tue, 31 Mar 2026 23:12:54 +0000 Subject: [PATCH 06/10] ci: switch from subject-checksums to subject-path for attestation --- .github/actions/ci/action.yml | 17 +++++++---------- .github/workflows/manual-publish.yml | 9 +-------- .github/workflows/release-please.yml | 9 +-------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index dd2ed98..5ea01fb 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -13,9 +13,9 @@ inputs: description: 'GH token used to download SDK test harness.' required: true outputs: - package-hashes: - description: "base64-encoded sha256 hashes of distribution files" - value: ${{ steps.package-hashes.outputs.package-hashes }} + dist-dir: + description: "Path to the stack dist directory containing built artifacts" + value: ${{ steps.dist-dir.outputs.dist-dir }} runs: using: composite @@ -34,15 +34,12 @@ runs: run: stack --no-terminal --resolver=${{ inputs.resolver }} sdist - name: Setup dist directory + id: dist-dir shell: bash - run: echo "STACK_DIR=$(stack --no-terminal path --dist-dir --resolver=${{ inputs.resolver }})" >> $GITHUB_ENV - - - name: Hash build files for provenance - id: package-hashes - shell: bash - working-directory: ${{ env.STACK_DIR }} run: | - echo "package-hashes=$(sha256sum *tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT" + STACK_DIR=$(stack --no-terminal path --dist-dir --resolver=${{ inputs.resolver }}) + echo "STACK_DIR=$STACK_DIR" >> $GITHUB_ENV + echo "dist-dir=$STACK_DIR" >> "$GITHUB_OUTPUT" - name: Run tests shell: bash diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index e85b87c..4f7af82 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -46,15 +46,8 @@ jobs: token: ${{ env.HACKAGE_TOKEN }} dry_run: ${{ inputs.dry_run }} - - name: Generate checksums file - if: ${{ !inputs.dry_run }} - env: - HASHES: ${{ steps.ci.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: '${{ steps.ci.outputs.dist-dir }}/*tar.gz' diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3033d03..6d749d2 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -70,15 +70,8 @@ jobs: # and another token, then add more tokens to the composite action. token: ${{secrets.GITHUB_TOKEN}} - - name: Generate checksums file - if: ${{ steps.release.outputs.releases_created == 'true' }} - env: - HASHES: ${{ steps.ci.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: '${{ steps.ci.outputs.dist-dir }}/*tar.gz' From c548cdf887430f5caf98d2ebe1b798dfa6509bac Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 16:34:28 +0000 Subject: [PATCH 07/10] 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 4f7af82..f5f372f 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 6d749d2..352d80a 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -14,9 +14,6 @@ jobs: contents: write # Contents and pull-requests are for release-please to make releases. pull-requests: write attestations: write - outputs: - release-created: ${{ steps.release.outputs.release_created }} - upload-tag-name: ${{ steps.release.outputs.tag_name }} steps: - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4 From 12fe847c9a735eca590ce84d692f5027bca1c1e2 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 19:08:03 +0000 Subject: [PATCH 08/10] ci: use format() for dry_run conditions to handle both string and boolean inputs --- .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 f5f372f..b5a174c 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -43,7 +43,7 @@ jobs: dry_run: ${{ inputs.dry_run }} - name: Attest build provenance - if: ${{ !inputs.dry_run }} + if: ${{ format('{0}', inputs.dry_run) == 'false' }} uses: actions/attest@v4 with: subject-path: '${{ steps.ci.outputs.dist-dir }}/*tar.gz' From d2519b7b77bc0e0aeafe572655cbcacbb41e7283 Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 21:52:15 +0000 Subject: [PATCH 09/10] docs: update PROVENANCE.md and README.md for GitHub artifact attestations --- PROVENANCE.md | 44 +++++++++++++++++++++++++------------------- README.md | 4 ++-- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/PROVENANCE.md b/PROVENANCE.md index e7c98ba..51b6e01 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,31 +13,37 @@ SDK_VERSION=4.5.1 ``` - ``` # Download package from Hackage $ curl -O https://hackage.haskell.org/package/launchdarkly-server-sdk-${SDK_VERSION}/launchdarkly-server-sdk-${SDK_VERSION}.tar.gz -# Download provenance from Github release into same directory -$ curl --location -O \ - https://github.com/launchdarkly/haskell-server-sdk/releases/download/${SDK_VERSION}/launchdarkly-server-sdk-${SDK_VERSION}.tar.gz.intoto.jsonl - -# Run slsa-verifier to verify provenance against package artifacts -$ slsa-verifier verify-artifact \ ---provenance-path launchdarkly-server-sdk-${SDK_VERSION}.tar.gz.intoto.jsonl \ ---source-uri github.com/launchdarkly/haskell-server-sdk \ -launchdarkly-server-sdk-${SDK_VERSION}.tar.gz +# Verify provenance using the GitHub CLI +$ gh attestation verify launchdarkly-server-sdk-${SDK_VERSION}.tar.gz --owner launchdarkly ``` Below is a sample of expected output. + ``` -Verified signature against tlog entry index 76419919 at URL: https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77a56491ff79d66537ddc16157d7ba7f31d59f0929cc6ce75ed98a0efed7fd3272a -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 dcf5e4be8e0c176c875919dcd5877193fac4f634 -Verifying artifact launchdarkly-server-sdk-4.0.4.tar.gz: PASSED +Loaded digest sha256:... for file://launchdarkly-server-sdk-4.5.1.tar.gz +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! + +The following 1 attestation matched the policy criteria -PASSED: Verified SLSA provenance +- Attestation #1 + - Build repo:..... launchdarkly/haskell-server-sdk + - Build workflow:. .github/workflows/release-please.yml + - Signer repo:.... launchdarkly/haskell-server-sdk + - Signer workflow: .github/workflows/release-please.yml ``` -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. diff --git a/README.md b/README.md index 1548ebd..f56b5bb 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,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 5fe7380697ec56f14ab4be874179c779ca6328ff Mon Sep 17 00:00:00 2001 From: "mkeeler@launchdarkly.com" Date: Wed, 1 Apr 2026 22:12:08 +0000 Subject: [PATCH 10/10] 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 f56b5bb..1548ebd 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,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