From b83fe958ccf8d00765f0d8f78ac7454f360fb02f Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Tue, 25 Aug 2026 16:44:57 -0700 Subject: [PATCH 1/7] Add zizmor config scoping the ref-pin policy to the stripe namespace Actions under stripe/* are first-party and tracked on a branch on purpose, so requiring a commit hash for them would be churn without a security gain. Scoping the policy to that namespace keeps the blanket hash requirement in force for every third-party action. This lands before the pins so the auto-fixer honours the policy and leaves stripe/openapi/actions/* alone rather than hash-pinning them for a revert. Committed-By-Agent: claude --- .github/zizmor.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/zizmor.yml diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 000000000..b806a9d40 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,8 @@ +# Actions under the stripe namespace are unpinned on purpose. +# This is deliberately scoped to that namespace so that other actions +# still require a full commit hash. +rules: + unpinned-uses: + config: + policies: + "stripe/*": ref-pin From 98ed3076d326d51a0ebdc301498046f2e1c90e06 Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Tue, 25 Aug 2026 16:45:19 -0700 Subject: [PATCH 2/7] Pin GitHub Actions references to commit hashes A tag is a mutable pointer, so any action referenced by tag can change what CI executes without a commit here. Pinning to a full commit hash makes the reference immutable and the version comment auditable. Thirteen of the fourteen pins came from the auto-fixer; every hash was then resolved against its claimed tag through the GitHub API before committing. pypa/gh-action-pypi-publish was pinned by hand because the auto-fixer cannot resolve a version for a branch reference. It tracked release/v1, whose head is currently tag v1.14.2, so the pin is both immutable and current. Dependabot would be the mechanism to keep it moving. Also sets persist-credentials: false on all three checkouts. The default leaves the workflow token in .git/config for the rest of the job, where any later step can read it; none of these jobs push, so nothing needs it retained. Committed-By-Agent: claude --- .github/workflows/ci.yml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f3f7ad1c..8af19624a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,10 +27,12 @@ jobs: permissions: contents: read steps: - - uses: extractions/setup-just@v2 - - uses: actions/checkout@v3 + - uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.0 + - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + with: + persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@v4 + uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 with: python-version: "3.13" - name: check examples w/ mypy (against python@3.13) @@ -54,11 +56,13 @@ jobs: permissions: contents: read steps: - - uses: extractions/setup-just@v2 - - uses: actions/checkout@v3 + - uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.0 + - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + with: + persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@v4 + uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 with: python-version: "3.10" @@ -67,7 +71,7 @@ jobs: just build - name: "Upload Artifact" - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dist path: dist/ @@ -100,10 +104,12 @@ jobs: # use any modern-ish version python_version: "3.14" steps: - - uses: extractions/setup-just@v2 - - uses: actions/checkout@v3 + - uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.0 + - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python_version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 with: python-version: ${{ matrix.python_version }} - uses: stripe/openapi/actions/stripe-mock@master @@ -127,12 +133,12 @@ jobs: id-token: write steps: - name: Download all workflow run artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: dist path: dist - name: Publish packages to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 with: packages-dir: dist/ - uses: stripe/openapi/actions/notify-release@master From 7ebfd732f969e4139ba6b2429c25b2d2ebf99b6a Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Tue, 25 Aug 2026 16:46:27 -0700 Subject: [PATCH 3/7] Upgrade pinned actions off end-of-life runtimes Pinning froze these at the versions master happened to reference, and four of the five were on a Node runtime GitHub force-migrates, which every job warns about today: extractions/setup-just v2.0.0 -> v4.0.0 actions/checkout v3.7.0 -> v7.0.1 (node16 -> node24) actions/setup-python v4.9.1 -> v7.0.0 (node16 -> node24) actions/upload-artifact v4.6.2 -> v7.0.1 (node20 -> node24) actions/download-artifact v4.3.0 -> v8.0.1 (node20 -> node24) The artifact pair moves together on purpose: build uploads dist and publish downloads it, and upload v7 / download v8 are the majors upstream ships as a pair. download-artifact v5's breaking change is scoped to downloads by ID; this one downloads by name, so it does not apply. v8 skips decompression based on Content-Type, which only diverges when the uploader sets archive: false, and this workflow does not. setup-python's cache input has no default in v7, unlike setup-node's package-manager-cache from v5 onward, so the upgrade adds no cache surface to audit. v7 drops the pip-install input, which this workflow never set. All five majors require Actions runner 2.327.1 or later, which the GitHub-hosted runners this workflow uses already satisfy. Committed-By-Agent: claude --- .github/workflows/ci.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8af19624a..b066afbdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,12 +27,12 @@ jobs: permissions: contents: read steps: - - uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.0 - - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + - uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.13" - name: check examples w/ mypy (against python@3.13) @@ -56,13 +56,13 @@ jobs: permissions: contents: read steps: - - uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.0 - - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + - uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.10" @@ -71,7 +71,7 @@ jobs: just build - name: "Upload Artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: dist path: dist/ @@ -104,12 +104,12 @@ jobs: # use any modern-ish version python_version: "3.14" steps: - - uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.0 - - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + - uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Set up Python ${{ matrix.python_version }} - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4.9.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python_version }} - uses: stripe/openapi/actions/stripe-mock@master @@ -133,7 +133,7 @@ jobs: id-token: write steps: - name: Download all workflow run artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist path: dist From 633c7070b67714eafd7a93b70211392996fe611b Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Tue, 25 Aug 2026 17:18:02 -0700 Subject: [PATCH 4/7] document download-artifact v8 digest-mismatch behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v8 fails the job on an artifact digest mismatch where v4 only logged a warning. The publish job is tag-gated, so CI never exercises it — the first person to see this failure will be mid-release, and the escape hatch should be next to the code rather than in a PR body. Committed-By-Agent: claude --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b066afbdb..2407e5b55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,6 +133,9 @@ jobs: id-token: write steps: - name: Download all workflow run artifacts + # v8 errors on an artifact digest mismatch; v4 only warned. A mismatch + # means the artifact `test` gated on isn't the one reaching PyPI, so + # failing is right — set `digest-mismatch: warn` if it ever misfires. uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist From 5361bc8873fd0d23ef935186a2d8c1fe6b391b2a Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Wed, 26 Aug 2026 09:47:47 -0700 Subject: [PATCH 5/7] Add zizmor workflow security check Committed-By-Agent: codex Co-authored-by: codex --- .github/workflows/zizmor.yml | 21 +++++++++++++++++++++ .github/zizmor.yml | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 000000000..b9b202bee --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,21 @@ +on: [push, pull_request] +name: Workflow security + +permissions: {} + +jobs: + zizmor: + name: zizmor + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + with: + advanced-security: false + annotations: true + min-severity: high + version: 1.29.0 diff --git a/.github/zizmor.yml b/.github/zizmor.yml index b806a9d40..25cd8201c 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -5,4 +5,4 @@ rules: unpinned-uses: config: policies: - "stripe/*": ref-pin + "stripe/openapi/*": ref-pin From b7f3a2c35f8fe08cf887737f93b3b946183ff6eb Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Wed, 26 Aug 2026 10:33:18 -0700 Subject: [PATCH 6/7] Add temporary release workflow probe Committed-By-Agent: codex Co-authored-by: codex --- .github/workflows/ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2407e5b55..529c884de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,3 +148,16 @@ jobs: if: always() with: bot_token: ${{ secrets.SLACK_BOT_TOKEN }} + + release-probe: + if: github.event_name == 'workflow_dispatch' + needs: [build, test, lint] + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist + path: dist + - run: test -n "$(find dist -maxdepth 1 -type f -print -quit)" From dd61217e0c417b2ffdc5cb6fb78d1d4976702013 Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Thu, 27 Aug 2026 11:49:01 -0700 Subject: [PATCH 7/7] Remove temporary release workflow probe Committed-By-Agent: codex Co-authored-by: codex --- .github/workflows/ci.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 529c884de..2407e5b55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,16 +148,3 @@ jobs: if: always() with: bot_token: ${{ secrets.SLACK_BOT_TOKEN }} - - release-probe: - if: github.event_name == 'workflow_dispatch' - needs: [build, test, lint] - runs-on: ubuntu-24.04 - permissions: - contents: read - steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: dist - path: dist - - run: test -n "$(find dist -maxdepth 1 -type f -print -quit)"