Compare commits

...

30 commits

Author SHA1 Message Date
github-actions[bot]
ff76e6c96e
[BOT] update JSON schemas from SchemaStore (#1462)
Some checks failed
CI / Lint (push) Has been cancelled
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Has been cancelled
CodSpeed Benchmarks / Run benchmarks (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Test site build (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Has been cancelled
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Release (push) Has been cancelled
CI / All tests pass (push) Has been cancelled
Co-authored-by: woodruffw <3059210+woodruffw@users.noreply.github.com>
Co-authored-by: William Woodruff <william@yossarian.net>
2025-12-22 17:20:59 -05:00
dependabot[bot]
8a138d4d7f
chore(deps): bump the cargo group with 3 updates (#1463)
Some checks are pending
CodSpeed Benchmarks / Run benchmarks (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
2025-12-22 08:44:33 -05:00
dependabot[bot]
27041c58c9
chore(deps): bump the github-actions group with 4 updates (#1464) 2025-12-22 08:43:38 -05:00
William Woodruff
c3913e7eff
excessive-permissions: add missing known permissions (#1461)
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Test site build (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Has been cancelled
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Has been cancelled
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Has been cancelled
CodSpeed Benchmarks / Run benchmarks (push) Has been cancelled
CI / All tests pass (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Release (push) Has been cancelled
2025-12-18 23:19:35 -05:00
William Woodruff
1a6a008951
Fix links to impostor-commit (#1459)
Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-18 23:01:34 +00:00
William Woodruff
a5e304f536
Prep zizmor 1.19.0 (#1458) 2025-12-18 17:48:37 -05:00
William Woodruff
2942f11dc2
Bump all tree-sitter dependent crates (#1457)
Some checks are pending
CodSpeed Benchmarks / Run benchmarks (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
Avoids dep hell.

Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-18 22:36:14 +00:00
William Woodruff
9d61a10dc1
Bump yamlpath to 0.30.0 (#1456) 2025-12-18 22:30:30 +00:00
William Woodruff
de6f9d6042
ci: add plain presentation test (#1454) 2025-12-18 17:25:07 -05:00
William Woodruff
cc76e2b93f
chore: bump github-actions-models to 0.42.0 (#1453)
Some checks failed
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Has been cancelled
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Has been cancelled
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Has been cancelled
CodSpeed Benchmarks / Run benchmarks (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Test site build (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Release (push) Has been cancelled
CI / All tests pass (push) Has been cancelled
Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-16 23:34:11 +00:00
William Woodruff
7e1c93b760
fix: add OpenTofu to Dependabot package ecosystems (#1452) 2025-12-16 18:27:24 -05:00
William Woodruff
081f844760
docs: bump trophies (#1450)
Some checks are pending
CodSpeed Benchmarks / Run benchmarks (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-16 16:44:33 +00:00
William Woodruff
abdfe5d9b6
docs: bump trophies (#1449)
Some checks are pending
CodSpeed Benchmarks / Run benchmarks (push) Waiting to run
CI / Lint (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-15 23:11:47 +00:00
dependabot[bot]
32d6831a1f
chore(deps): bump tower-lsp-server from 0.22.1 to 0.23.0 in the cargo group (#1447)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Woodruff <william@yossarian.net>
2025-12-15 15:10:10 -08:00
dependabot[bot]
eafa9e43fc
chore(deps): bump actions/checkout in the github-actions group (#1448)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 15:00:18 -08:00
William Woodruff
f0525e0f30
ci: bump actions in test-output.yml (#1446)
Some checks failed
CodSpeed Benchmarks / Run benchmarks (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Test site build (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Has been cancelled
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Has been cancelled
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Has been cancelled
CI / All tests pass (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Release (push) Has been cancelled
2025-12-14 20:37:02 -08:00
William Woodruff
5b7e8bcd72
bench: offline benchmarks (#1444) 2025-12-14 18:24:23 -08:00
William Woodruff
4a7e4e27bc
chore: bump online test runtimes (#1443)
Some checks are pending
CodSpeed Benchmarks / Run benchmarks (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
2025-12-14 00:50:22 -08:00
William Woodruff
147bfabc0f
ci: only run benchmarks on labeled PRs (#1442) 2025-12-14 00:06:46 -08:00
William Woodruff
411b74a5a9
chore: online benchmarks (#1441) 2025-12-13 23:22:35 -08:00
William Woodruff
c9fc966d0a
chore: switch to CodSpeed benchmarking (#1440) 2025-12-13 22:42:08 -08:00
William Woodruff
b3e8725791
feat: improve "no inputs" error message (#1439)
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
2025-12-13 20:22:59 -08:00
William Woodruff
5bb11ce251
docs: bump trophies (#1437)
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Test site build (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Has been cancelled
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Has been cancelled
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Has been cancelled
CI / All tests pass (push) Has been cancelled
zizmor wheel builds for PyPI 🐍 / Release (push) Has been cancelled
Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-12 20:37:47 +00:00
William Woodruff
94eea71efd
docs: bump pinned actions (#1436)
Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-12 20:34:12 +00:00
William Woodruff
b9b65b0e80
docs: bump trophies (#1435)
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
Signed-off-by: William Woodruff <william@yossarian.net>
2025-12-12 04:40:45 +00:00
William Woodruff
c9f0ea9aaf
feat: discover zizmor.yaml config files (#1431)
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
2025-12-10 23:12:36 -08:00
dependabot[bot]
5987ac7503
chore(deps): bump the cargo group across 1 directory with 12 updates (#1430)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Woodruff <william@yossarian.net>
2025-12-10 22:54:31 -08:00
William Woodruff
b714997a0c
fix: bump tree-sitter-powershell and fix query (#1427)
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
2025-12-10 09:46:41 -08:00
William Woodruff
b78376a737
feat: add another fast path for impostor commit detection (#1429)
Some checks are pending
CI / Lint (push) Waiting to run
CI / Test (push) Waiting to run
CI / Test site build (push) Waiting to run
CI / All tests pass (push) Blocked by required conditions
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (manylinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Linux wheels (musllinux) (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build Windows wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build macOS wheels (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Build source distribution (push) Waiting to run
zizmor wheel builds for PyPI 🐍 / Release (push) Blocked by required conditions
Deploy zizmor documentation site 🌐 / Deploy zizmor documentation to GitHub Pages 🌐 (push) Waiting to run
GitHub Actions Security Analysis with zizmor 🌈 / Run zizmor 🌈 (push) Waiting to run
2025-12-10 09:20:05 +00:00
Daniel D. Beck
1f71a18100
docs: fix typo in dependabot-execution rule description (#1428) 2025-12-10 09:12:11 +00:00
85 changed files with 1304 additions and 706 deletions

51
.github/workflows/benchmark.yml vendored Normal file
View file

@ -0,0 +1,51 @@
name: CodSpeed Benchmarks
on:
push:
branches:
- "main"
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
- labeled
# `workflow_dispatch` allows CodSpeed to trigger backtest
# performance analysis in order to generate initial data.
workflow_dispatch:
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
benchmarks:
name: Run benchmarks
# PRs only get benchmarked if they have the `run-benchmarks` label.
if: |
contains(github.event.pull_request.labels.*.name, 'run-benchmarks')
|| github.event_name == 'push'
|| github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
cache-all-crates: true
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: Build zizmor (release)
run: cargo build --release
- name: Run the benchmarks
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
with:
mode: walltime
run: make bench

View file

@ -22,7 +22,7 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
@ -38,13 +38,13 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: Test dependencies
run: |
@ -67,11 +67,11 @@ jobs:
name: Test site build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: Test site
run: make site

View file

@ -22,7 +22,7 @@ jobs:
pull-requests: write # for opening PRs
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
@ -31,7 +31,7 @@ jobs:
make refresh-schemas
- name: create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
draft: true
commit-message: "[BOT] update JSON schemas from SchemaStore"
@ -59,18 +59,18 @@ jobs:
pull-requests: write # for opening PRs
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: try to refresh context capabilities
run: |
make webhooks-to-contexts
- name: create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
draft: true
commit-message: "[BOT] update context capabilities"
@ -97,18 +97,18 @@ jobs:
pull-requests: write # for opening PRs
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: try to refresh CodeQL injection sinks
run: |
make codeql-injection-sinks
- name: create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
draft: true
commit-message: "[BOT] update CodeQL injection sinks"

View file

@ -27,7 +27,7 @@ jobs:
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
@ -60,7 +60,7 @@ jobs:
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: artifacts-${{ matrix.target }}
path: ${{ steps.archive-release.outputs.filename }}
@ -78,7 +78,7 @@ jobs:
steps:
- name: Download artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: artifacts-*
path: distrib/

View file

@ -43,7 +43,7 @@ jobs:
packages: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
@ -86,7 +86,7 @@ jobs:
shell: bash
- name: Upload digest
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: digests-${{ matrix.image.platform-pair }}
path: ${{ runner.temp }}/digests/*
@ -107,7 +107,7 @@ jobs:
steps:
- name: Download digests
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
path: ${{ runner.temp }}/digests
pattern: digests-*

View file

@ -37,7 +37,7 @@ jobs:
# target: ppc64le
# manylinux: "2_28"
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Build wheels
@ -47,7 +47,7 @@ jobs:
args: --release --out dist --manifest-path crates/zizmor/Cargo.toml
manylinux: ${{ matrix.platform.manylinux }}
- name: Upload wheels
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-linux-${{ matrix.platform.target }}
path: dist
@ -67,7 +67,7 @@ jobs:
- runner: ubuntu-24.04
target: armv7
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Build wheels
@ -77,7 +77,7 @@ jobs:
args: --release --out dist --manifest-path crates/zizmor/Cargo.toml
manylinux: musllinux_1_2
- name: Upload wheels
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-musllinux-${{ matrix.platform.target }}
path: dist
@ -93,7 +93,7 @@ jobs:
- runner: windows-latest
target: x86
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Build wheels
@ -102,7 +102,7 @@ jobs:
target: ${{ matrix.platform.target }}
args: --release --out dist --manifest-path crates/zizmor/Cargo.toml
- name: Upload wheels
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-windows-${{ matrix.platform.target }}
path: dist
@ -118,7 +118,7 @@ jobs:
- runner: macos-15
target: aarch64
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Build wheels
@ -127,7 +127,7 @@ jobs:
target: ${{ matrix.platform.target }}
args: --release --out dist --manifest-path crates/zizmor/Cargo.toml
- name: Upload wheels
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-macos-${{ matrix.platform.target }}
path: dist
@ -136,7 +136,7 @@ jobs:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Build sdist
@ -145,7 +145,7 @@ jobs:
command: sdist
args: --out dist --manifest-path crates/zizmor/Cargo.toml
- name: Upload sdist
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: wheels-sdist
path: dist
@ -161,7 +161,7 @@ jobs:
permissions:
id-token: write # Trusted Publishing + PEP 740 attestations
steps:
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
- name: Attest
uses: astral-sh/attest-action@2c727738cea36d6c97dd85eb133ea0e0e8fe754b # v0.0.4
with:

View file

@ -53,7 +53,7 @@ jobs:
permissions:
id-token: write # for trusted publishing to crates.io
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false

View file

@ -19,7 +19,7 @@ jobs:
permissions:
id-token: write # for trusted publishing to crates.io
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false

View file

@ -26,12 +26,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- name: build site
run: make site

View file

@ -19,7 +19,7 @@ jobs:
pull-requests: write # for 'Leave comment' step
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
@ -30,7 +30,7 @@ jobs:
cargo run -- --format sarif . > results.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: results.sarif
category: zizmor-test-sarif-presentation
@ -52,7 +52,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
@ -67,3 +67,27 @@ jobs:
--no-exit-codes \
--format github \
crates/zizmor/tests/integration/test-data/several-vulnerabilities.yml
test-plain-presentation:
name: Test plain text presentation
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'test-plain-presentation')
permissions: {}
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: Run zizmor
run: |
# Normally we'd want a workflow to fail if the audit fails,
# but we're only testing presentation here.
cargo run \
-- \
--no-exit-codes \
--format plain \
crates/zizmor/tests/integration/test-data/several-vulnerabilities.yml

View file

@ -19,7 +19,7 @@ jobs:
issues: write # to create an issue if a new version is found
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
sparse-checkout: support/

View file

@ -21,7 +21,7 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false

3
.gitignore vendored
View file

@ -10,3 +10,6 @@
# pending snapshots
.*.pending-snap
# benchmarks
.codspeed/

184
Cargo.lock generated
View file

@ -58,9 +58,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "annotate-snippets"
version = "0.12.9"
version = "0.12.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44baf24dd94e781f74dfe67ffee75a09a57971ddf0f615a178b4f6d404b48ff"
checksum = "15580ece6ea97cbf832d60ba19c021113469480852c6a2a6beb0db28f097bf1f"
dependencies = [
"anstyle",
"unicode-width 0.2.2",
@ -228,12 +228,6 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
@ -313,18 +307,18 @@ dependencies = [
[[package]]
name = "camino"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
dependencies = [
"serde_core",
]
[[package]]
name = "cc"
version = "1.2.41"
version = "1.2.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
dependencies = [
"find-msvc-tools",
"shlex",
@ -344,9 +338,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
version = "4.5.51"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
"clap_derive",
@ -364,9 +358,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.51"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
@ -376,9 +370,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.60"
version = "4.5.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e602857739c5a4291dfa33b5a298aeac9006185229a700e5810a3ef7272d971"
checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992"
dependencies = [
"clap",
]
@ -564,6 +558,12 @@ dependencies = [
"parking_lot_core",
]
[[package]]
name = "data-encoding"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "deranged"
version = "0.5.4"
@ -705,9 +705,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "flate2"
@ -721,11 +721,12 @@ dependencies = [
[[package]]
name = "fluent-uri"
version = "0.1.4"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5"
dependencies = [
"bitflags 1.3.2",
"borrow-or-share",
"ref-cast",
]
[[package]]
@ -923,7 +924,7 @@ dependencies = [
[[package]]
name = "github-actions-models"
version = "0.41.0"
version = "0.42.0"
dependencies = [
"indexmap",
"insta",
@ -954,9 +955,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
@ -977,12 +978,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "1.3.1"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"fnv",
"itoa",
]
@ -1291,12 +1291,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
@ -1317,9 +1317,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.43.2"
version = "1.44.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698"
dependencies = [
"console 0.15.11",
"once_cell",
@ -1384,13 +1384,13 @@ dependencies = [
[[package]]
name = "jsonschema"
version = "0.35.0"
version = "0.37.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0303b14f91cbac17c64aaf2ef60ab71fe5f34c3867cedcbca72c9dd15f5040fe"
checksum = "73c9ffb2b5c56d58030e1b532d8e8389da94590515f118cf35b5cb68e4764a7e"
dependencies = [
"ahash",
"base64 0.22.1",
"bytecount",
"data-encoding",
"email_address",
"fancy-regex",
"fraction",
@ -1428,7 +1428,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"libc",
"redox_syscall",
]
@ -1477,16 +1477,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "lsp-types"
version = "0.97.0"
name = "ls-types"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
checksum = "7a7deb98ef9daaa7500324351a5bab7c80c644cfb86b4be0c4433b582af93510"
dependencies = [
"bitflags 1.3.2",
"fluent-uri 0.1.4",
"bitflags",
"fluent-uri 0.3.2",
"percent-encoding",
"serde",
"serde_json",
"serde_repr",
]
[[package]]
@ -1782,9 +1782,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
version = "2.8.3"
version = "2.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22"
dependencies = [
"memchr",
"ucd-trie",
@ -1792,9 +1792,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.8.3"
version = "2.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f"
dependencies = [
"pest",
"pest_generator",
@ -1802,9 +1802,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.8.3"
version = "2.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625"
dependencies = [
"pest",
"pest_meta",
@ -1815,9 +1815,9 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.8.3"
version = "2.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82"
dependencies = [
"pest",
"sha2",
@ -2048,7 +2048,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.10.0",
"bitflags",
]
[[package]]
@ -2073,14 +2073,14 @@ dependencies = [
[[package]]
name = "referencing"
version = "0.35.0"
version = "0.37.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22d0d0665043906aacf1d83bea9d61e5134f8f437815b84320e7facf8ff4e9c2"
checksum = "4283168a506f0dcbdce31c9f9cce3129c924da4c6bca46e46707fcb746d2d70c"
dependencies = [
"ahash",
"fluent-uri 0.4.1",
"getrandom 0.3.4",
"hashbrown 0.16.0",
"hashbrown 0.16.1",
"parking_lot",
"percent-encoding",
"serde_json",
@ -2129,9 +2129,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.12.24"
version = "0.12.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f"
dependencies = [
"base64 0.22.1",
"bytes",
@ -2224,7 +2224,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"errno",
"libc",
"linux-raw-sys",
@ -2347,7 +2347,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
@ -2490,17 +2490,6 @@ dependencies = [
"syn 2.0.108",
]
[[package]]
name = "serde_repr"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.108",
]
[[package]]
name = "serde_spanned"
version = "1.0.3"
@ -3004,11 +2993,11 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags 2.10.0",
"bitflags",
"bytes",
"futures-util",
"http",
@ -3028,17 +3017,16 @@ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-lsp-server"
version = "0.22.1"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88f3f8ec0dcfdda4d908bad2882fe0f89cf2b606e78d16491323e918dfa95765"
checksum = "2f0e711655c89181a6bc6a2cc348131fcd9680085f5b06b6af13427a393a6e72"
dependencies = [
"bytes",
"dashmap",
"futures",
"httparse",
"lsp-types",
"ls-types",
"memchr",
"percent-encoding",
"serde",
"serde_json",
"tokio",
@ -3055,9 +3043,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.41"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@ -3066,9 +3054,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.30"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
@ -3077,9 +3065,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.34"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
dependencies = [
"once_cell",
"valuable",
@ -3087,9 +3075,9 @@ dependencies = [
[[package]]
name = "tracing-indicatif"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04d4e11e0e27acef25a47f27e9435355fecdc488867fa2bc90e75b0700d2823d"
checksum = "e1ef6990e0438749f0080573248e96631171a0b5ddfddde119aa5ba8c3a9c47e"
dependencies = [
"indicatif",
"tracing",
@ -3110,9 +3098,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.20"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"matchers",
"nu-ansi-term",
@ -3128,9 +3116,9 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.25.10"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87"
checksum = "974d205cc395652cfa8b37daa053fe56eebd429acf8dc055503fee648dae981e"
dependencies = [
"cc",
"regex",
@ -3142,9 +3130,9 @@ dependencies = [
[[package]]
name = "tree-sitter-bash"
version = "0.25.0"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871b0606e667e98a1237ebdc1b0d7056e0aebfdc3141d12b399865d4cb6ed8a6"
checksum = "9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062"
dependencies = [
"cc",
"tree-sitter-language",
@ -3152,7 +3140,7 @@ dependencies = [
[[package]]
name = "tree-sitter-iter"
version = "0.0.2"
version = "0.0.3"
dependencies = [
"tree-sitter",
"tree-sitter-yaml",
@ -3166,9 +3154,9 @@ checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8"
[[package]]
name = "tree-sitter-powershell"
version = "0.25.9"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae0e37101b110badaf99aa40460915a8797ceba15fc0ed22773280377a8dffb6"
checksum = "415ec6251d133d26b4f62c60721149fe36c315334f47812450187d6ea59cffdf"
dependencies = [
"cc",
"tree-sitter-language",
@ -3816,7 +3804,7 @@ checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
[[package]]
name = "yamlpatch"
version = "0.7.0"
version = "0.8.0"
dependencies = [
"indexmap",
"insta",
@ -3832,7 +3820,7 @@ dependencies = [
[[package]]
name = "yamlpath"
version = "0.29.0"
version = "0.31.0"
dependencies = [
"line-index",
"self_cell",
@ -3956,7 +3944,7 @@ dependencies = [
[[package]]
name = "zizmor"
version = "1.18.0"
version = "1.19.0"
dependencies = [
"annotate-snippets",
"anstream",

View file

@ -21,38 +21,38 @@ rust-version = "1.88.0"
[workspace.dependencies]
anyhow = "1.0.100"
github-actions-expressions = { path = "crates/github-actions-expressions", version = "0.0.11" }
github-actions-models = { path = "crates/github-actions-models", version = "0.41.0" }
github-actions-models = { path = "crates/github-actions-models", version = "0.42.0" }
itertools = "0.14.0"
pest = "2.8.3"
pest_derive = "2.8.3"
pest = "2.8.4"
pest_derive = "2.8.4"
pretty_assertions = "1.4.1"
annotate-snippets = "0.12.9"
annotate-snippets = "0.12.10"
anstream = "0.6.21"
assert_cmd = "2.1.1"
async-trait = "0.1.89"
camino = "1.2.1"
clap = "4.5.51"
camino = "1.2.2"
clap = "4.5.53"
clap-verbosity-flag = { version = "3.0.4", default-features = false }
clap_complete = "4.5.60"
clap_complete = "4.5.61"
clap_complete_nushell = "4.5.10"
csv = "1.3.1"
etcetera = "0.11.0"
flate2 = "1.1.5"
fst = "0.4.7"
futures = "0.3"
http = "1.3.1"
http = "1.4.0"
http-cache-reqwest = { version = "1.0.0-alpha.2", features = ["manager-moka"] }
human-panic = "2.0.4"
ignore = "0.4.25"
indexmap = { version = "2.11.4", features = ["serde"] }
indexmap = { version = "2.12.1", features = ["serde"] }
indicatif = "0.18"
insta = "1.43.2"
jsonschema = "0.35.0"
insta = "1.44.3"
jsonschema = "0.37.4"
line-index = "0.1.2"
memchr = "2.7.6"
owo-colors = "4.2.3"
regex = "1.12.1"
reqwest = { version = "0.12.23", default-features = false }
reqwest = { version = "0.12.25", default-features = false }
reqwest-middleware = "0.4.2"
self_cell = "1"
serde = { version = "1.0.228", features = ["derive"] }
@ -65,16 +65,18 @@ tar = "0.4.44"
terminal-link = "0.1.0"
thiserror = "2.0.17"
tokio = { version = "1.47.1", features = ["rt-multi-thread", "io-std"] }
tower-lsp-server = "0.22"
tracing = "0.1.41"
tracing-indicatif = "0.3.13"
tower-lsp-server = "0.23"
tracing = "0.1.43"
tracing-indicatif = "0.3.14"
tracing-subscriber = "0.3.20"
tree-sitter = "0.25.10"
tree-sitter-bash = "0.25.0"
tree-sitter-iter = { path = "crates/tree-sitter-iter", version = "0.0.2" }
tree-sitter-powershell = "0.25.9"
yamlpath = { path = "crates/yamlpath", version = "0.29.0" }
yamlpatch = { path = "crates/yamlpatch", version = "0.7.0" }
tree-sitter = "0.26.3"
tree-sitter-bash = "0.25.1"
tree-sitter-iter = { path = "crates/tree-sitter-iter", version = "0.0.3" }
# Exact version since the upstream performed a breaking change outside of semver.
# See: https://github.com/zizmorcore/zizmor/pull/1427
tree-sitter-powershell = "=0.25.10"
yamlpath = { path = "crates/yamlpath", version = "0.31.0" }
yamlpatch = { path = "crates/yamlpatch", version = "0.8.0" }
tree-sitter-yaml = "0.7.2"
tikv-jemallocator = "0.6"

View file

@ -53,4 +53,4 @@ pinact:
.PHONY: bench
bench:
uv run bench/benchmark.py --offline
uv run --only-group=bench pytest bench/ --codspeed

1
bench/.gitignore vendored
View file

@ -1 +0,0 @@
results/

0
bench/__init__.py Normal file
View file

View file

@ -1,242 +0,0 @@
# /// script
# requires-python = ">=3.12"
# ///
import argparse
import hashlib
import json
import os
import shlex
import shutil
import subprocess
import sys
import tempfile
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator, NoReturn, TypedDict
_DEPS = ["hyperfine", "curl", "unzip"]
_HERE = Path(__file__).parent
_PROJECT_ROOT = _HERE.parent
_ZIZMOR = _PROJECT_ROOT / "target" / "release" / "zizmor"
assert (_PROJECT_ROOT / "Cargo.toml").is_file(), "Missing project root?"
_BENCHMARKS = _HERE / "benchmarks.json"
_RESULTS = _HERE / "results"
assert _BENCHMARKS.is_file(), f"Benchmarks file not found: {_BENCHMARKS}"
_RESULTS.mkdir(exist_ok=True)
_CACHE_DIR = Path(tempfile.gettempdir()) / "zizmor-benchmark-cache"
_CACHE_DIR.mkdir(exist_ok=True)
_GH_TOKEN = os.getenv("GH_TOKEN")
class Log:
def __init__(self, scope: str | None) -> None:
self.scopes = [scope] if scope else []
def info(self, message: str) -> None:
scopes = " ".join(f"[{s}]" for s in self.scopes)
print(f"[+] {scopes} {message}", file=sys.stderr)
def warn(self, message: str) -> None:
scopes = " ".join(f"[{s}]" for s in self.scopes)
print(f"[!] {scopes} {message}", file=sys.stderr)
def error(self, message: str) -> NoReturn:
self.warn(message)
sys.exit(1)
@contextmanager
def scope(self, new_scope: str) -> Iterator[None]:
"""Create a new logging scope."""
self.scopes.append(new_scope)
try:
yield None
finally:
self.scopes.pop()
LOG = Log("benchmarks")
def _curl(url: str, expected_sha256: str) -> Path:
"""Download a URL and cache it using content addressing with SHA256."""
cached_file = _CACHE_DIR / expected_sha256
if cached_file.exists():
LOG.info("Using cached file")
return cached_file
result = subprocess.run(
["curl", "-fsSL", url],
capture_output=True,
check=True,
)
content = result.stdout
content_hash = hashlib.sha256(content).hexdigest()
if content_hash != expected_sha256:
LOG.error(f"Hash mismatch: {expected_sha256} != {content_hash}")
cached_file.write_bytes(content)
return cached_file
def _unzip(archive_path: Path, extract_name: str) -> Path:
"""Extract an archive to a directory in the cache."""
extract_dir = _CACHE_DIR / extract_name
if extract_dir.exists():
LOG.info("Using cached extraction")
return extract_dir
extract_dir.mkdir(exist_ok=True)
subprocess.run(
["unzip", "-q", str(archive_path), "-d", str(extract_dir)],
check=True,
)
LOG.info(f"Extracted {archive_path.name} to {extract_dir}")
return extract_dir
class Benchmark(TypedDict):
name: str
source_type: str
source: str
source_sha256: str
stencil: str
online: bool | None
Plan = list[str]
class Bench:
def __init__(self, benchmark: Benchmark) -> None:
self.benchmark = benchmark
def plan(self) -> Plan:
match self.benchmark["source_type"]:
case "archive-url":
url = self.benchmark["source"]
sha256 = self.benchmark["source_sha256"]
archive = _curl(url, sha256)
inputs = [str(_unzip(archive, self.benchmark["name"]))]
case _:
LOG.error(f"Unknown source type: {self.benchmark['source_type']}")
if self.benchmark.get("online", False):
if not _GH_TOKEN:
LOG.error("Benchmark requires online access but GH_TOKEN is not set")
stencil = self.benchmark["stencil"]
command = stencil.replace("$ZIZMOR", str(_ZIZMOR)).replace(
"$INPUTS", " ".join(inputs)
)
return shlex.split(command)
def run(self, plan: Plan, *, dry_run: bool) -> None:
command = shlex.join(plan)
result_file = _RESULTS / f"{self.benchmark['name']}.json"
if result_file.exists() and not dry_run:
LOG.warn("clobbering existing result file")
hyperfine_command = [
"hyperfine",
"--warmup",
"3",
# NOTE: not needed because we use --no-exit-codes in the stencil
# "--ignore-failure",
"--export-json",
str(result_file),
command,
]
if dry_run:
LOG.warn(f"would have run: {shlex.join(hyperfine_command)}")
return
try:
subprocess.run(
hyperfine_command,
check=True,
)
except subprocess.CalledProcessError:
LOG.error("run failed, see above for details")
# Stupid hack: fixup each result file's results[0].command
# to be a more useful benchmark identifier, since bencher
# apparently keys on these.
result_json = json.loads(result_file.read_bytes())
result_json["results"][0]["command"] = f"zizmor::{self.benchmark['name']}"
result_file.write_text(json.dumps(result_json))
LOG.info(f"run written to {result_file}")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--dry-run", action="store_true", help="Show plans without running them"
)
parser.add_argument(
"--offline", action="store_true", help="Run only offline benchmarks"
)
args = parser.parse_args()
missing = []
for dep in _DEPS:
if not shutil.which(dep):
missing.append(dep)
if missing:
LOG.error(
f"Missing dependencies: {', '.join(missing)}. "
"Please install them before running benchmarks."
)
LOG.info("ensuring we have a benchable zizmor build")
subprocess.run(
["cargo", "build", "--release", "-p", "zizmor"],
check=True,
cwd=_PROJECT_ROOT,
)
if not _ZIZMOR.is_file():
LOG.error("zizmor build presumably failed, see above for details")
LOG.info(f"using cache dir: {_CACHE_DIR}")
benchmarks: list[Benchmark] = json.loads(_BENCHMARKS.read_text(encoding="utf-8"))
LOG.info(f"found {len(benchmarks)} benchmarks in {_BENCHMARKS.name}")
if args.offline:
benchmarks = [b for b in benchmarks if not b.get("online", False)]
LOG.info(f"filtered to {len(benchmarks)} offline benchmarks")
benches = [Bench(benchmark) for benchmark in benchmarks]
plans = []
with LOG.scope("plan"):
for bench in benches:
with LOG.scope(bench.benchmark["name"]):
LOG.info("beginning plan")
plans.append(bench.plan())
with LOG.scope("run"):
for bench, plan in zip(benches, plans):
with LOG.scope(bench.benchmark["name"]):
bench.run(plan, dry_run=args.dry_run)
if __name__ == "__main__":
main()

View file

@ -1,24 +0,0 @@
[
{
"name": "grafana-9f212d11d0ac",
"source_type": "archive-url",
"source": "https://github.com/grafana/grafana/archive/9f212d11d0ac9c38ada62a7db830844bb9b02905.zip",
"source_sha256": "c6d42b52c8d912db2698d8b06f227de46f0c2d04cc757841792ed6567f0c56c7",
"stencil": "$ZIZMOR --offline --format=plain --no-exit-codes --no-config $INPUTS"
},
{
"name": "cpython-48f88310044c",
"source_type": "archive-url",
"source": "https://github.com/python/cpython/archive/48f88310044c6ef877f3b0761cf7afece2f8fb3a.zip",
"source_sha256": "a52a67f1dd9cfa67c7d1305d5b9639629abe247b2c32f01b77f790ddf8b49503",
"stencil": "$ZIZMOR --offline --format=plain --no-exit-codes --no-config $INPUTS"
},
{
"name": "gha-hazmat-da3c3cd-online",
"source_type": "archive-url",
"source": "https://github.com/woodruffw/gha-hazmat/archive/da3c3cd.zip",
"source_sha256": "f0aa224c5203218ad26e9f104d8dc3eaf8b322c97056add04d79f4a0d53c8f1f",
"stencil": "$ZIZMOR --format=plain --no-exit-codes --no-config $INPUTS",
"online": true
}
]

12
bench/common.py Normal file
View file

@ -0,0 +1,12 @@
import subprocess
from pathlib import Path
_HERE = Path(__file__).parent
_ZIZMOR = _HERE.parent / "target" / "release" / "zizmor"
def zizmor(args: list[str], *, check: bool = False) -> None:
assert _ZIZMOR.is_file(), (
f"zizmor binary not found at {_ZIZMOR}, run prepare() first"
)
subprocess.run([str(_ZIZMOR), *args], check=check)

0
bench/conftest.py Normal file
View file

13
bench/test_bench_basic.py Normal file
View file

@ -0,0 +1,13 @@
import pytest
from .common import zizmor
@pytest.mark.benchmark
def test_zizmor_startup():
zizmor(["--version"])
@pytest.mark.benchmark
def test_zizmor_help():
zizmor(["--help"])

View file

@ -0,0 +1,68 @@
import io
import zipfile
from pathlib import Path
import pytest
import urllib3
from bench.common import zizmor
@pytest.fixture(scope="session")
def grafana(tmp_path_factory) -> Path:
archive = "https://github.com/grafana/grafana/archive/9f212d11d0ac9c38ada62a7db830844bb9b02905.zip"
raw_zip = urllib3.PoolManager().request("GET", archive).data
path = tmp_path_factory.mktemp("grafana")
zipfile.ZipFile(io.BytesIO(raw_zip)).extractall(path)
return path
@pytest.fixture(scope="session")
def cpython(tmp_path_factory) -> Path:
archive = "https://github.com/python/cpython/archive/48f88310044c6ef877f3b0761cf7afece2f8fb3a.zip"
raw_zip = urllib3.PoolManager().request("GET", archive).data
path = tmp_path_factory.mktemp("cpython")
zipfile.ZipFile(io.BytesIO(raw_zip)).extractall(path)
return path
@pytest.mark.benchmark
def test_zizmor_offline_grafana_9f212d11d0(grafana: Path):
"""
Runs `zizmor --offline --format=plain --no-exit-codes --no-config <path-to-grafana-source>`
"""
zizmor(
[
"--offline",
"--format=plain",
"--no-exit-codes",
"--no-config",
str(grafana),
],
check=True,
)
@pytest.mark.benchmark
def test_zizmor_offline_cpython_48f88310044c(cpython: Path):
"""
Runs `zizmor --offline --format=plain --no-exit-codes --no-config <path-to-cpython-source>`
"""
zizmor(
[
"--offline",
"--format=plain",
"--no-exit-codes",
"--no-config",
str(cpython),
],
check=True,
)

View file

@ -0,0 +1,47 @@
import os
import pytest
from bench.common import zizmor
@pytest.mark.skipif("GH_TOKEN" not in os.environ, reason="GH_TOKEN not set")
def test_zizmor_online_gha_hazmat_da3c3cd(benchmark):
"""
Runs `zizmor --format=plain --no-exit-codes --no-config woodruffw/gha-hazmat@da3c3cd`
"""
benchmark.pedantic(
zizmor,
args=(
[
"--format=plain",
"--no-exit-codes",
"--no-config",
"woodruffw/gha-hazmat@da3c3cd",
],
),
warmup_rounds=2,
iterations=10,
)
@pytest.mark.skipif("GH_TOKEN" not in os.environ, reason="GH_TOKEN not set")
def test_zizmor_online_cpython_48f88310044c(benchmark):
"""
Runs `zizmor --format=plain --no-exit-codes --no-config python/cpython@48f88310044c`
"""
benchmark.pedantic(
zizmor,
args=(
[
"--format=plain",
"--no-exit-codes",
"--no-config",
"python/cpython@48f88310044c",
],
),
warmup_rounds=2,
iterations=10,
)

View file

@ -1,6 +1,6 @@
[package]
name = "github-actions-models"
version = "0.41.0"
version = "0.42.0"
description = "Unofficial, high-quality data models for GitHub Actions workflows, actions, and related components"
repository = "https://github.com/zizmorcore/zizmor/tree/main/crates/github-actions-models"
keywords = ["github", "ci"]

View file

@ -297,6 +297,12 @@ self_cell!(
impl {Debug, PartialEq}
);
impl Display for RepositoryUses {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.raw())
}
}
impl RepositoryUses {
/// Parse a `uses: some/repo` clause.
pub fn parse(uses: impl Into<String>) -> Result<Self, UsesError> {

View file

@ -349,6 +349,8 @@ pub enum AllowDeny {
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum PackageEcosystem {
/// `bazel`
Bazel,
/// `bun`
Bun,
/// `bundler`
@ -369,6 +371,8 @@ pub enum PackageEcosystem {
DotnetSdk,
/// `helm`
Helm,
/// `julia`
Julia,
/// `elm`
Elm,
/// `gitsubmodule`
@ -387,6 +391,8 @@ pub enum PackageEcosystem {
Npm,
/// `nuget`
Nuget,
/// `opentofu`
Opentofu,
/// `pip`
Pip,
/// `pub`

View file

@ -0,0 +1,23 @@
# https://github.com/zizmorcore/zizmor/issues/1451
version: 2
enable-beta-ecosystems: true
updates:
- package-ecosystem: "opentofu"
directories:
- "/stack"
- "/modules/default-branch-protection"
schedule:
interval: "cron"
cronjob: "30 7 * * *"
timezone: "Europe/London"
target-branch: "main"
groups:
terraform:
applies-to: "version-updates"
patterns:
- "*"
update-types:
- "patch"
- "minor"

View file

@ -1,7 +1,7 @@
[package]
name = "tree-sitter-iter"
description = "A very simple pre-order iterator for tree-sitter CSTs"
version = "0.0.2"
version = "0.0.3"
authors.workspace = true
homepage.workspace = true
edition.workspace = true

View file

@ -1,6 +1,6 @@
[package]
name = "yamlpatch"
version = "0.7.0"
version = "0.8.0"
description = "Comment and format-preserving YAML patch operations"
repository = "https://github.com/zizmorcore/zizmor/tree/main/crates/yamlpatch"
keywords = ["yaml", "patch"]

View file

@ -903,21 +903,21 @@ normal:
let end = find_content_end(&feature, &doc);
insta::assert_snapshot!(doc.source()[feature.location.byte_span.0..end], @r"
bar: baz
abc: def # comment
");
bar: baz
abc: def # comment
");
let feature = route_to_feature_exact(&route!("interior-spaces"), &doc)
.unwrap()
.unwrap();
let end = find_content_end(&feature, &doc);
insta::assert_snapshot!(doc.source()[feature.location.byte_span.0..end], @r"
- foo
- foo
- bar
# hello
- baz # hello
");
- bar
# hello
- baz # hello
");
let feature = route_to_feature_exact(&route!("normal"), &doc)
.unwrap()
@ -2775,11 +2775,12 @@ items:
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r"
items:
- first
- second
- third
");
items:
- first
- second
- third
");
}
#[test]
@ -2822,19 +2823,20 @@ databases:
let result =
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r#"
databases:
- name: primary
host: db1.example.com
port: 5432
max_connections: 100
ssl: true
readonly: false
- name: analytics
host: db2.example.com
port: 5433
readonly: true
"#);
insta::assert_snapshot!(result.source(), @r"
databases:
- name: primary
host: db1.example.com
port: 5432
max_connections: 100
ssl: true
readonly: false
- name: analytics
host: db2.example.com
port: 5433
readonly: true
");
}
#[test]
@ -2870,16 +2872,17 @@ jobs:
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r#"
jobs:
test:
steps:
- name: First step
run: echo "first"
- name: Second step
run: echo "second"
- name: Third step
run: echo "third"
"#);
jobs:
test:
steps:
- name: First step
run: echo "first"
- name: Second step
run: echo "second"
- name: Third step
run: echo "third"
"#);
}
#[test]
@ -2924,20 +2927,21 @@ servers:
assert!(result.source().contains("# Staging server"));
assert!(result.source().contains("# internal only"));
insta::assert_snapshot!(result.source(), @r#"
servers:
# Production server
- name: prod
host: prod.example.com
port: 443
# Staging server
- name: staging
host: staging.example.com # internal only
port: 8443
- name: dev
host: localhost
port: 8080
"#);
insta::assert_snapshot!(result.source(), @r"
servers:
# Production server
- name: prod
host: prod.example.com
port: 443
# Staging server
- name: staging
host: staging.example.com # internal only
port: 8443
- name: dev
host: localhost
port: 8080
");
}
#[test]
@ -2959,11 +2963,12 @@ ports:
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r"
ports:
- 8080
- 8081
- 8082
");
ports:
- 8080
- 8081
- 8082
");
}
#[test]
@ -2985,11 +2990,12 @@ configs:
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r"
configs:
- name: config1
value: 123
- {}
");
configs:
- name: config1
value: 123
- {}
");
}
#[test]
@ -3031,15 +3037,16 @@ services:
let result =
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r#"
services:
- name: api
port: 8080
- name: worker
port: 9090
config:
replicas: 3
"#);
insta::assert_snapshot!(result.source(), @r"
services:
- name: api
port: 8080
- name: worker
port: 9090
config:
replicas: 3
");
}
#[test]
@ -3096,11 +3103,12 @@ tasks:
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r"
tasks:
- task1
- task2
- task3
");
tasks:
- task1
- task2
- task3
");
}
#[test]
@ -3138,20 +3146,21 @@ jobs:
let result =
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r#"
name: CI
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run tests
run: npm test
- name: Upload coverage
uses: codecov/codecov-action@v3
"#);
insta::assert_snapshot!(result.source(), @r"
name: CI
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run tests
run: npm test
- name: Upload coverage
uses: codecov/codecov-action@v3
");
}
#[test]
@ -3176,9 +3185,10 @@ foo:
apply_yaml_patches(&yamlpath::Document::new(original).unwrap(), &operations).unwrap();
insta::assert_snapshot!(result.source(), @r"
foo:
- abc
- - def
- ghi
");
foo:
- abc
- - def
- ghi
");
}

View file

@ -1,6 +1,6 @@
[package]
name = "yamlpath"
version = "0.29.0"
version = "0.31.0"
description = "Format-preserving YAML feature extraction"
repository = "https://github.com/zizmorcore/zizmor/tree/main/crates/yamlpath"
readme = "README.md"

View file

@ -1,7 +1,7 @@
[package]
name = "zizmor"
description = "Static analysis for GitHub Actions"
version = "1.18.0"
version = "1.19.0"
repository = "https://github.com/zizmorcore/zizmor"
documentation = "https://docs.zizmor.sh"
keywords = ["cli", "github-actions", "static-analysis", "security"]
@ -25,8 +25,6 @@ gh-token-tests = []
online-tests = ["gh-token-tests"]
# Test-only: enable tests that require `unbuffer` for TTY behavior.
tty-tests = []
# Test-only: enable tests that are typically very slow.
slow-tests = []
[dependencies]
annotate-snippets.workspace = true

View file

@ -524,6 +524,7 @@ jobs:
|workflow: &Workflow, findings| {
let fixed = apply_fix_for_snapshot(workflow.as_document(), findings);
insta::assert_snapshot!(fixed.source(), @r"
name: Test Workflow
on: push
jobs:
@ -571,6 +572,7 @@ jobs:
|workflow: &Workflow, findings| {
let fixed = apply_fix_for_snapshot(workflow.as_document(), findings);
insta::assert_snapshot!(fixed.source(), @r"
name: Test Workflow
on: push
jobs:

View file

@ -538,6 +538,7 @@ jobs:
}
insta::assert_snapshot!(document.source(), @r#"
name: Test Workflow
on:
pull_request_target:
@ -588,6 +589,7 @@ jobs:
}
}
insta::assert_snapshot!(document.source(), @r#"
name: Test Workflow
on:
pull_request_target:
@ -641,6 +643,7 @@ jobs:
// Verify it suggests comment.user.login for issue_comment events
insta::assert_snapshot!(document.source(), @r#"
name: Test Issue Comment
on: issue_comment
@ -690,6 +693,7 @@ jobs:
// Verify it suggests review.user.login for pull_request_review events
insta::assert_snapshot!(document.source(), @r#"
name: Test PR Review
on: pull_request_review
@ -739,6 +743,7 @@ jobs:
// Verify it suggests issue.user.login for issues events
insta::assert_snapshot!(document.source(), @r#"
name: Test Issues
on: issues
@ -788,6 +793,7 @@ jobs:
// Verify it suggests release.author.login for release events
insta::assert_snapshot!(document.source(), @r#"
name: Test Release
on: release
@ -836,6 +842,7 @@ jobs:
}
insta::assert_snapshot!(document.source(), @r#"
name: Test Create
on: create
@ -885,6 +892,7 @@ jobs:
}
insta::assert_snapshot!(document.source(), @r#"
name: Test Workflow
on:
pull_request_target:

View file

@ -555,6 +555,7 @@ jobs:
|findings: Vec<Finding>| {
let fixed_content = apply_fix_for_snapshot(workflow_content, findings);
insta::assert_snapshot!(fixed_content, @r"
name: Test Workflow
on: release
@ -599,6 +600,7 @@ jobs:
|findings: Vec<Finding>| {
let fixed_content = apply_fix_for_snapshot(workflow_content, findings);
insta::assert_snapshot!(fixed_content, @r"
name: Test Workflow
on: release

View file

@ -202,6 +202,7 @@ updates:
let fix = &finding.fixes[0];
let fixed_document = fix.apply(dependabot.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r"
version: 2
updates:
@ -243,6 +244,7 @@ updates:
let fix = &finding.fixes[0];
let fixed_document = fix.apply(dependabot.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r"
version: 2
updates:
@ -284,6 +286,7 @@ updates:
let fix = &finding.fixes[0];
let fixed_document = fix.apply(dependabot.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r"
version: 2
updates:
@ -335,6 +338,7 @@ updates:
}
insta::assert_snapshot!(document.source(), @r"
version: 2
updates:
@ -379,6 +383,7 @@ updates:
// Verify the document remains unchanged
insta::assert_snapshot!(dependabot.as_document().source(), @r"
version: 2
updates:

View file

@ -123,6 +123,7 @@ updates:
let fix = &finding.fixes[0];
let fixed_document = fix.apply(dependabot.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r"
version: 2
updates:
@ -158,6 +159,7 @@ updates:
// Verify the document remains unchanged
insta::assert_snapshot!(dependabot.as_document().source(), @r"
version: 2
updates:
@ -192,6 +194,7 @@ updates:
// Verify the document remains unchanged
insta::assert_snapshot!(dependabot.as_document().source(), @r"
version: 2
updates:
@ -240,6 +243,7 @@ updates:
}
insta::assert_snapshot!(document.source(), @r"
version: 2
updates:

View file

@ -14,6 +14,7 @@ use crate::{
static KNOWN_PERMISSIONS: LazyLock<HashMap<&str, Severity>> = LazyLock::new(|| {
[
("actions", Severity::High),
("artifact-metadata", Severity::Medium),
("attestations", Severity::High),
("checks", Severity::Medium),
("contents", Severity::High),
@ -21,6 +22,8 @@ static KNOWN_PERMISSIONS: LazyLock<HashMap<&str, Severity>> = LazyLock::new(|| {
("discussions", Severity::Medium),
("id-token", Severity::High),
("issues", Severity::High),
// What does the write permission even do here?
("models", Severity::Low),
("packages", Severity::High),
("pages", Severity::High),
("pull-requests", Severity::High),

View file

@ -88,20 +88,22 @@ const PWSH_REDIRECT_QUERY: &str = r#"
const PWSH_PIPELINE_QUERY: &str = r#"
(pipeline
(command
command_name: (command_name) @cmd
command_elements: (command_elements
(_)*
(array_literal_expression
(unary_expression [
(string_literal
(expandable_string_literal (variable) @destination))
(variable) @destination
])
)
(_)*))
(#match? @cmd "(?i)out-file|add-content|set-content|tee-object")
(#match? @destination "(?i)ENV:GITHUB_ENV|ENV:GITHUB_PATH")
(pipeline_chain
(command
command_name: (command_name) @cmd
command_elements: (command_elements
(_)*
(array_literal_expression
(unary_expression [
(string_literal
(expandable_string_literal (variable) @destination))
(variable) @destination
])
)
(_)*))
(#match? @cmd "(?i)out-file|add-content|set-content|tee-object")
(#match? @destination "(?i)ENV:GITHUB_ENV|ENV:GITHUB_PATH")
)
) @span
"#;

View file

@ -77,7 +77,7 @@ impl ImpostorCommit {
return Ok(false);
};
// Fast path: almost all commit refs will be at the tip of
// Fastest path: almost all commit refs will be at the tip of
// the branch or tag's history, so check those first.
// Check tags before branches, since in practice version tags
// are more commonly pinned.
@ -105,6 +105,21 @@ impl ImpostorCommit {
}
}
// Fast path: attempt to use GitHub's undocumented `branch_commits`
// API to see if the commit is present in any branch/tag.
// There are no stabilitiy guarantees for this API, so we fall back
// to the slow(er) paths if it fails.
match self
.client
.branch_commits(uses.owner(), uses.repo(), head_ref)
.await
{
Ok(branch_commits) => return Ok(branch_commits.is_empty()),
Err(e) => tracing::warn!("fast path impostor check failed for {uses}: {e}"),
}
// Slow path: use GitHub's comparison API to check each branch and tag's
// history for presence of the commit.
for branch in &branches {
if self
.named_ref_contains_commit(uses, &format!("refs/heads/{}", &branch.name), head_ref)
@ -400,6 +415,7 @@ jobs:
// Apply the fix and snapshot test the result
let new_doc = findings[0].fixes[0].apply(input.as_document()).unwrap();
assert_snapshot!(new_doc.source(), @r"
name: Test Impostor Commit Fix
on: push
jobs:

View file

@ -295,6 +295,7 @@ jobs:
assert!(fixed_document.source().contains("ANOTHER_VAR: also-keep"));
insta::assert_snapshot!(fixed_document.source(), @r#"
on: push
jobs:
@ -349,6 +350,7 @@ jobs:
assert!(fixed_document.source().contains("GLOBAL_VAR: keep-me"));
insta::assert_snapshot!(fixed_document.source(), @r#"
on: push
env:
@ -403,6 +405,7 @@ jobs:
assert!(fixed_document.source().contains("STEP_VAR: keep-me"));
insta::assert_snapshot!(fixed_document.source(), @r#"
on: push
jobs:
@ -446,6 +449,7 @@ jobs:
let fixed_document = fix.apply(workflow.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r#"
on: push
jobs:

View file

@ -372,6 +372,7 @@ jobs:
let fixed_document = fix.apply(workflow.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r#"
name: Test Vulnerable Actions
on: push
jobs:
@ -421,7 +422,8 @@ jobs:
.unwrap();
let fixed_document = fix.apply(workflow.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r#"
insta::assert_snapshot!(fixed_document.source(), @r"
name: Test Node Setup
on: push
jobs:
@ -434,7 +436,7 @@ jobs:
node-version: '18'
- name: Install dependencies
run: npm install
"#);
");
}
#[tokio::test]
@ -474,6 +476,7 @@ jobs:
let fixed_document = fix.apply(workflow.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r#"
name: Test Third Party Action
on: push
jobs:
@ -549,7 +552,8 @@ jobs:
.unwrap();
current_document = fix_cache.apply(&current_document).unwrap();
insta::assert_snapshot!(current_document.source(), @r#"
insta::assert_snapshot!(current_document.source(), @r"
name: Test Multiple Vulnerable Actions
on: push
jobs:
@ -569,7 +573,7 @@ jobs:
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm install
"#);
");
}
#[tokio::test]
@ -607,6 +611,7 @@ jobs:
let fixed_document = fix.apply(workflow.as_document()).unwrap();
insta::assert_snapshot!(fixed_document.source(), @r"
name: Test Action with Subpath
on: push
jobs:
@ -655,7 +660,8 @@ jobs:
.apply(workflow.as_document())
.unwrap();
insta::assert_snapshot!(fixed_document.source(), @r#"
insta::assert_snapshot!(fixed_document.source(), @r"
name: Test First Patched Version Priority
on: push
jobs:
@ -664,7 +670,7 @@ jobs:
steps:
- name: Vulnerable action
uses: actions/checkout@v3.1.0
"#);
");
}
#[tokio::test]
@ -700,6 +706,7 @@ jobs:
let new_doc = fix.apply(workflow.as_document()).unwrap();
assert_snapshot!(new_doc.source(), @r"
name: Test Non-Commit Ref
on: push
jobs:
@ -763,6 +770,7 @@ jobs:
let new_doc = findings[0].fixes[0].apply(input.as_document()).unwrap();
assert_snapshot!(new_doc.source(), @r"
name: Test Commit Hash Pinning Real API
on: push
permissions: {}
@ -819,6 +827,7 @@ jobs:
let new_doc = findings[0].fixes[0].apply(input.as_document()).unwrap();
assert_snapshot!(new_doc.source(), @r"
name: Test Commit Hash Pinning Real API
on: push
permissions: {}

View file

@ -407,7 +407,8 @@ jobs:
"#;
let result = apply_fix_for_snapshot(workflow_content, "obfuscation").await;
insta::assert_snapshot!(result, @r#"
insta::assert_snapshot!(result, @r"
name: Test Workflow
on: push
@ -416,7 +417,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
"#);
");
}
#[tokio::test]
@ -433,7 +434,8 @@ jobs:
"#;
let result = apply_fix_for_snapshot(workflow_content, "obfuscation").await;
insta::assert_snapshot!(result, @r#"
insta::assert_snapshot!(result, @r"
name: Test Workflow
on: push
@ -442,7 +444,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: github/codeql-action/init@v2
"#);
");
}
#[tokio::test]
@ -459,7 +461,8 @@ jobs:
"#;
let result = apply_fix_for_snapshot(workflow_content, "obfuscation").await;
insta::assert_snapshot!(result, @r#"
insta::assert_snapshot!(result, @r"
name: Test Workflow
on: push
@ -468,6 +471,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/cache/save@v4
"#);
");
}
}

View file

@ -707,6 +707,7 @@ jobs:
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection
on: push
jobs:
@ -758,6 +759,7 @@ jobs:
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection
on: push
jobs:
@ -811,6 +813,7 @@ jobs:
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection
on: push
jobs:
@ -920,6 +923,7 @@ jobs:
}
insta::assert_snapshot!(current_document.source(), @r#"
name: Test Multiple Template Injections
on: push
jobs:
@ -986,6 +990,7 @@ jobs:
}
insta::assert_snapshot!(current_document.source(), @r#"
name: Test Duplicate Template Injections
on: push
jobs:
@ -1046,6 +1051,7 @@ jobs:
}
insta::assert_snapshot!(current_document.source(), @r#"
name: Test Duplicate Template Injections
on: push
jobs:
@ -1109,6 +1115,7 @@ jobs:
}
insta::assert_snapshot!(current_document.source(), @r#"
name: Test Duplicate Template Injections
on: push
jobs:
@ -1225,6 +1232,7 @@ jobs:
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection - Bash
on: push
jobs:
@ -1271,6 +1279,7 @@ jobs:
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection - Bash
on: push
jobs:
@ -1316,7 +1325,8 @@ jobs:
finding,
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
insta::assert_snapshot!(fixed_content.source(), @r"
name: Test Template Injection - CMD
on: push
jobs:
@ -1326,7 +1336,7 @@ jobs:
- name: Vulnerable step with cmd shell
shell: cmd
run: echo User is %GITHUB_ACTOR%
"#);
");
}
}
);
@ -1363,6 +1373,7 @@ jobs:
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection - PowerShell
on: push
jobs:
@ -1409,6 +1420,7 @@ jobs:
);
// Ubuntu default shell is bash, so should use ${VAR} syntax
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection - Default Shell Ubuntu
on: push
jobs:
@ -1454,6 +1466,7 @@ jobs:
);
// Windows default shell is pwsh, so should use $env:VAR syntax
insta::assert_snapshot!(fixed_content.source(), @r#"
name: Test Template Injection - Default Shell Windows
on: push
jobs:
@ -1498,7 +1511,8 @@ jobs:
finding,
"replace expression with environment variable",
);
insta::assert_snapshot!(fixed_content.source(), @r#"
insta::assert_snapshot!(fixed_content.source(), @r"
name: Test Template Injection - CMD with Custom Env
on: push
jobs:
@ -1510,7 +1524,7 @@ jobs:
run: echo PR title is %GITHUB_EVENT_PULL_REQUEST_TITLE%
env:
GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }}
"#);
");
}
}
);

View file

@ -244,6 +244,7 @@ jobs:
let fixed_document = apply_fix_for_snapshot(workflow.as_document(), findings);
insta::assert_snapshot!(fixed_document.source(), @r#"
name: Test
on: push
jobs:
@ -283,6 +284,7 @@ jobs:
let fixed_document = apply_fix_for_snapshot(workflow.as_document(), findings);
insta::assert_snapshot!(fixed_document.source(), @r#"
name: Test
on: push
jobs:
@ -323,6 +325,7 @@ jobs:
let fixed_document = apply_fix_for_snapshot(workflow.as_document(), findings);
insta::assert_snapshot!(fixed_document.source(), @r#"
name: Test
on: push
jobs:
@ -367,6 +370,7 @@ jobs:
let fixed_document = apply_fix_for_snapshot(workflow.as_document(), findings);
insta::assert_snapshot!(fixed_document.source(), @r#"
name: Test
on: push
jobs:
@ -406,7 +410,8 @@ jobs:
assert_eq!(findings.len(), 1);
let fixed_document = apply_fix_for_snapshot(workflow.as_document(), findings);
insta::assert_snapshot!(fixed_document.source(), @r#"
insta::assert_snapshot!(fixed_document.source(), @r"
name: Test
on: push
jobs:
@ -414,7 +419,7 @@ jobs:
if: |-
${{ github.event_name == 'pull_request' }}
uses: ./.github/workflows/reusable.yml
"#);
");
}
);
}
@ -463,6 +468,7 @@ jobs:
}
insta::assert_snapshot!(document.source(), @r#"
name: Test
on: push
jobs:

View file

@ -21,7 +21,12 @@ use crate::{
registry::input::RepoSlug,
};
const CONFIG_CANDIDATES: &[&str] = &[".github/zizmor.yml", "zizmor.yml"];
const CONFIG_CANDIDATES: &[&str] = &[
".github/zizmor.yml",
".github/zizmor.yaml",
"zizmor.yml",
"zizmor.yaml",
];
#[derive(Error, Debug)]
#[error("configuration error in {path}")]

View file

@ -647,24 +647,28 @@
},
"package-ecosystem-values": {
"enum": [
"bazel",
"bun",
"bundler",
"cargo",
"composer",
"conda",
"devcontainers",
"docker",
"docker-compose",
"dotnet-sdk",
"elm",
"gitsubmodule",
"github-actions",
"gitsubmodule",
"gomod",
"gradle",
"helm",
"julia",
"maven",
"mix",
"npm",
"nuget",
"opentofu",
"pip",
"pub",
"rust-toolchain",

View file

@ -575,6 +575,29 @@ impl Client {
.max_by_key(|t| t.name.len()))
}
#[instrument(skip(self))]
pub(crate) async fn branch_commits(
&self,
owner: &str,
repo: &str,
commit: &str,
) -> Result<BranchCommits, ClientError> {
// NOTE(ww): This API is undocumented.
// See: https://github.com/orgs/community/discussions/78161
let url = format!("https://github.com/{owner}/{repo}/branch_commits/{commit}");
// We ask GitHub for JSON, because it sends HTML by default for this endpoint.
self.base_client
.get(&url)
.header(ACCEPT, "application/json")
.send()
.await?
.error_for_status()?
.json()
.await
.map_err(Into::into)
}
#[instrument(skip(self))]
pub(crate) async fn compare_commits(
&self,
@ -859,6 +882,23 @@ pub(crate) struct Commit {
pub(crate) sha: String,
}
/// The response structure from GitHub's undocumented `branch_commits` API.
///
/// This model is intentionally incomplete.
#[derive(Clone, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub(crate) struct BranchCommits {
branches: Vec<serde_json::Value>,
tags: Vec<String>,
}
impl BranchCommits {
pub(crate) fn is_empty(&self) -> bool {
self.branches.is_empty() && self.tags.is_empty()
}
}
#[derive(Clone, Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum ComparisonStatus {

View file

@ -4,7 +4,7 @@ use std::str::FromStr;
use camino::Utf8Path;
use thiserror::Error;
use tower_lsp_server::lsp_types::{self, TextDocumentSyncKind};
use tower_lsp_server::ls_types::{self, TextDocumentSyncKind};
use tower_lsp_server::{Client, LanguageServer, LspService, Server};
use crate::audit::AuditInput;
@ -25,7 +25,7 @@ pub(crate) struct Error {
}
struct LspDocumentCommon {
uri: lsp_types::Uri,
uri: ls_types::Uri,
text: String,
version: Option<i32>,
}
@ -39,35 +39,35 @@ struct Backend {
impl LanguageServer for Backend {
async fn initialize(
&self,
_: lsp_types::InitializeParams,
) -> tower_lsp_server::jsonrpc::Result<lsp_types::InitializeResult> {
Ok(lsp_types::InitializeResult {
server_info: Some(lsp_types::ServerInfo {
_: ls_types::InitializeParams,
) -> tower_lsp_server::jsonrpc::Result<ls_types::InitializeResult> {
Ok(ls_types::InitializeResult {
server_info: Some(ls_types::ServerInfo {
name: "zizmor (LSP)".into(),
version: Some(env!("CARGO_PKG_VERSION").into()),
}),
capabilities: lsp_types::ServerCapabilities {
text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind(
lsp_types::TextDocumentSyncKind::FULL,
capabilities: ls_types::ServerCapabilities {
text_document_sync: Some(ls_types::TextDocumentSyncCapability::Kind(
ls_types::TextDocumentSyncKind::FULL,
)),
..Default::default()
},
})
}
async fn initialized(&self, _: lsp_types::InitializedParams) {
async fn initialized(&self, _: ls_types::InitializedParams) {
let selectors = vec![
lsp_types::DocumentFilter {
ls_types::DocumentFilter {
language: Some("yaml".into()),
scheme: None,
pattern: Some("**/.github/workflows/*.{yml,yaml}".into()),
},
lsp_types::DocumentFilter {
ls_types::DocumentFilter {
language: Some("yaml".into()),
scheme: None,
pattern: Some("**/action.{yml,yaml}".into()),
},
lsp_types::DocumentFilter {
ls_types::DocumentFilter {
language: Some("yaml".into()),
scheme: None,
pattern: Some("**/.github/dependabot.{yml,yaml}".into()),
@ -80,46 +80,46 @@ impl LanguageServer for Backend {
// neglects to.
self.client
.register_capability(vec![
lsp_types::Registration {
ls_types::Registration {
id: "zizmor-didopen".into(),
method: "textDocument/didOpen".into(),
register_options: Some(
serde_json::to_value(lsp_types::TextDocumentRegistrationOptions {
serde_json::to_value(ls_types::TextDocumentRegistrationOptions {
document_selector: Some(selectors.clone()),
})
.expect("failed to serialize LSP document registration options"),
),
},
lsp_types::Registration {
ls_types::Registration {
id: "zizmor-didchange".into(),
method: "textDocument/didChange".into(),
register_options: Some(
serde_json::to_value(lsp_types::TextDocumentChangeRegistrationOptions {
serde_json::to_value(ls_types::TextDocumentChangeRegistrationOptions {
document_selector: Some(selectors.clone()),
sync_kind: TextDocumentSyncKind::FULL,
})
.expect("failed to serialize LSP document registration options"),
),
},
lsp_types::Registration {
ls_types::Registration {
id: "zizmor-didsave".into(),
method: "textDocument/didSave".into(),
register_options: Some(
serde_json::to_value(lsp_types::TextDocumentSaveRegistrationOptions {
serde_json::to_value(ls_types::TextDocumentSaveRegistrationOptions {
include_text: Some(true),
text_document_registration_options:
lsp_types::TextDocumentRegistrationOptions {
ls_types::TextDocumentRegistrationOptions {
document_selector: Some(selectors.clone()),
},
})
.expect("failed to serialize LSP document registration options"),
),
},
lsp_types::Registration {
ls_types::Registration {
id: "zizmor-didclose".into(),
method: "textDocument/didClose".into(),
register_options: Some(
serde_json::to_value(lsp_types::TextDocumentRegistrationOptions {
serde_json::to_value(ls_types::TextDocumentRegistrationOptions {
document_selector: Some(selectors),
})
.expect("failed to serialize LSP document registration options"),
@ -130,7 +130,7 @@ impl LanguageServer for Backend {
.expect("failed to register text document capabilities with the LSP client");
self.client
.log_message(lsp_types::MessageType::INFO, "server initialized!")
.log_message(ls_types::MessageType::INFO, "server initialized!")
.await;
}
@ -139,7 +139,7 @@ impl LanguageServer for Backend {
Ok(())
}
async fn did_open(&self, params: lsp_types::DidOpenTextDocumentParams) {
async fn did_open(&self, params: ls_types::DidOpenTextDocumentParams) {
tracing::debug!("did_open: {:?}", params);
self.audit(LspDocumentCommon {
uri: params.text_document.uri,
@ -149,7 +149,7 @@ impl LanguageServer for Backend {
.await;
}
async fn did_change(&self, params: lsp_types::DidChangeTextDocumentParams) {
async fn did_change(&self, params: ls_types::DidChangeTextDocumentParams) {
tracing::debug!("did_change: {:?}", params);
let mut params = params;
let Some(change) = params.content_changes.pop() else {
@ -164,7 +164,7 @@ impl LanguageServer for Backend {
.await;
}
async fn did_save(&self, params: lsp_types::DidSaveTextDocumentParams) {
async fn did_save(&self, params: ls_types::DidSaveTextDocumentParams) {
tracing::debug!("did_save: {:?}", params);
if let Some(text) = params.text {
self.audit(LspDocumentCommon {
@ -217,15 +217,15 @@ impl Backend {
.iter()
.map(|finding| {
let primary = finding.primary_location();
lsp_types::Diagnostic {
range: lsp_types::Range {
ls_types::Diagnostic {
range: ls_types::Range {
start: primary.concrete.location.start_point.into(),
end: primary.concrete.location.end_point.into(),
},
severity: Some(finding.determinations.severity.into()),
code: Some(lsp_types::NumberOrString::String(finding.ident.into())),
code_description: Some(lsp_types::CodeDescription {
href: lsp_types::Uri::from_str(finding.url)
code: Some(ls_types::NumberOrString::String(finding.ident.into())),
code_description: Some(ls_types::CodeDescription {
href: ls_types::Uri::from_str(finding.url)
.expect("finding contains an invalid URL somehow"),
}),
source: Some("zizmor".into()),
@ -248,25 +248,25 @@ impl Backend {
async fn audit(&self, params: LspDocumentCommon) {
if let Err(e) = self.audit_inner(params).await {
self.client
.log_message(lsp_types::MessageType::ERROR, format!("audit failed: {e}"))
.log_message(ls_types::MessageType::ERROR, format!("audit failed: {e}"))
.await;
}
}
}
impl From<Severity> for lsp_types::DiagnosticSeverity {
impl From<Severity> for ls_types::DiagnosticSeverity {
fn from(value: Severity) -> Self {
// TODO: Does this mapping make sense?
match value {
Severity::Informational => lsp_types::DiagnosticSeverity::INFORMATION,
Severity::Low => lsp_types::DiagnosticSeverity::WARNING,
Severity::Medium => lsp_types::DiagnosticSeverity::WARNING,
Severity::High => lsp_types::DiagnosticSeverity::ERROR,
Severity::Informational => ls_types::DiagnosticSeverity::INFORMATION,
Severity::Low => ls_types::DiagnosticSeverity::WARNING,
Severity::Medium => ls_types::DiagnosticSeverity::WARNING,
Severity::High => ls_types::DiagnosticSeverity::ERROR,
}
}
}
impl From<Point> for lsp_types::Position {
impl From<Point> for ls_types::Position {
fn from(value: Point) -> Self {
Self {
line: value.row as u32,

View file

@ -118,6 +118,15 @@ struct App {
#[arg(long, value_enum, default_value_t)]
format: OutputFormat,
/// Whether to render OSC 8 links in the output.
///
/// This affects links under audit IDs, as well as any links
/// produced by audit rules.
///
/// Only affects `--format=plain` (the default).
#[arg(long, value_enum, default_value_t, env = "ZIZMOR_RENDER_LINKS")]
render_links: CliRenderLinks,
/// Whether to render audit URLs in the output, separately from any URLs
/// embedded in OSC 8 links.
///
@ -325,6 +334,44 @@ pub(crate) enum OutputFormat {
Github,
}
#[derive(Debug, Default, Copy, Clone, ValueEnum)]
pub(crate) enum CliRenderLinks {
/// Render OSC 8 links in output if support is detected.
#[default]
Auto,
/// Always render OSC 8 links in output.
Always,
/// Never render OSC 8 links in output.
Never,
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum RenderLinks {
Always,
Never,
}
impl From<CliRenderLinks> for RenderLinks {
fn from(value: CliRenderLinks) -> Self {
match value {
CliRenderLinks::Auto => {
// We render links if stdout is a terminal. This is assumed
// to preclude CI environments and log files.
//
// TODO: Switch this to the support-hyperlinks crate?
// See: https://github.com/zkat/supports-hyperlinks/pull/8
if stdout().is_terminal() {
RenderLinks::Always
} else {
RenderLinks::Never
}
}
CliRenderLinks::Always => RenderLinks::Always,
CliRenderLinks::Never => RenderLinks::Never,
}
}
}
#[derive(Debug, Default, Copy, Clone, ValueEnum)]
pub(crate) enum CliShowAuditUrls {
/// Render audit URLs in output automatically based on output format and runtime context.
@ -641,6 +688,7 @@ async fn run(app: &mut App) -> Result<ExitCode, Error> {
ColorMode::Never
} else if std::env::var("FORCE_COLOR").is_ok()
|| std::env::var("CLICOLOR_FORCE").is_ok()
|| utils::is_ci()
{
ColorMode::Always
} else {
@ -816,6 +864,7 @@ async fn run(app: &mut App) -> Result<ExitCode, Error> {
&registry,
&results,
&app.show_audit_urls.into(),
&app.render_links.into(),
app.naches,
),
OutputFormat::Json | OutputFormat::JsonV1 => {
@ -920,6 +969,16 @@ async fn main() -> ExitCode {
Some(report)
}
Error::Collection(err) => match err.inner() {
CollectionError::NoInputs => {
let group = Group::with_title(Level::ERROR.primary_title(err.to_string()))
.element(Level::HELP.message("collection yielded no auditable inputs"))
.element(Level::HELP.message("inputs must contain at least one valid workflow, action, or Dependabot config"));
let renderer = Renderer::styled();
let report = renderer.render(&[group]);
Some(report)
}
CollectionError::DuplicateInput(..) => {
let group = Group::with_title(Level::ERROR.primary_title(err.to_string()))
.element(Level::HELP.message(format!(

View file

@ -7,7 +7,7 @@ use anstream::{eprintln, print, println};
use owo_colors::OwoColorize;
use crate::{
ShowAuditUrls,
RenderLinks, ShowAuditUrls,
finding::{
Finding, Severity,
location::{Location, LocationKind},
@ -44,6 +44,7 @@ impl From<&Severity> for Level<'_> {
pub(crate) fn finding_snippets<'doc>(
registry: &'doc InputRegistry,
finding: &'doc Finding<'doc>,
render_links_mode: &RenderLinks,
) -> Vec<Snippet<'doc, Annotation<'doc>>> {
// Our finding might span multiple workflows, so we need to group locations
// by their enclosing workflow to generate each snippet correctly.
@ -68,15 +69,20 @@ pub(crate) fn finding_snippets<'doc>(
for (input_key, locations) in locations_by_workflow {
let input = registry.get_input(input_key);
let path = match render_links_mode {
RenderLinks::Always => input.link().unwrap_or(input_key.presentation_path()),
RenderLinks::Never => input_key.presentation_path(),
};
snippets.push(
Snippet::source(input.as_document().source())
.fold(true)
.line_start(1)
.path(input.link().unwrap_or(input_key.presentation_path()))
.path(path)
.annotations(locations.iter().map(|loc| {
let annotation = match loc.symbolic.link {
Some(ref link) => link,
None => &loc.symbolic.annotation,
let annotation = match (loc.symbolic.link.as_deref(), render_links_mode) {
(Some(link), RenderLinks::Always) => link,
_ => &loc.symbolic.annotation,
};
AnnotationKind::from(loc.symbolic.kind)
@ -96,10 +102,11 @@ pub(crate) fn render_findings(
registry: &InputRegistry,
findings: &FindingRegistry,
show_urls_mode: &ShowAuditUrls,
render_links_mode: &RenderLinks,
naches_mode: bool,
) {
for finding in findings.findings() {
render_finding(registry, finding, show_urls_mode);
render_finding(registry, finding, show_urls_mode, render_links_mode);
println!();
}
@ -192,11 +199,19 @@ pub(crate) fn render_findings(
}
}
fn render_finding(registry: &InputRegistry, finding: &Finding, show_urls_mode: &ShowAuditUrls) {
let title = Level::from(&finding.determinations.severity)
fn render_finding(
registry: &InputRegistry,
finding: &Finding,
show_urls_mode: &ShowAuditUrls,
render_links_mode: &RenderLinks,
) {
let mut title = Level::from(&finding.determinations.severity)
.primary_title(finding.desc)
.id(finding.ident)
.id_url(finding.url);
.id(finding.ident);
if matches!(render_links_mode, RenderLinks::Always) {
title = title.id_url(finding.url);
}
let confidence = format!(
"audit confidence → {:?}",
@ -204,7 +219,7 @@ fn render_finding(registry: &InputRegistry, finding: &Finding, show_urls_mode: &
);
let mut group = Group::with_title(title)
.elements(finding_snippets(registry, finding))
.elements(finding_snippets(registry, finding, render_links_mode))
.element(Level::NOTE.message(confidence));
if let Some(tip) = &finding.tip {

View file

@ -4,12 +4,8 @@ use anyhow::{Error, anyhow};
use camino::Utf8Path;
use github_actions_expressions::context::{Context, ContextPattern};
use github_actions_models::common::{Env, expr::LoE};
use jsonschema::{
BasicOutput::{Invalid, Valid},
Validator,
output::{ErrorDescription, OutputUnit},
validator_for,
};
use jsonschema::ErrorEntry;
use jsonschema::{Validator, validator_for};
use std::ops::{Deref, Range};
use std::{fmt::Write, sync::LazyLock};
@ -307,11 +303,11 @@ pub(crate) static DEFAULT_ENVIRONMENT_VARIABLES: &[(
),
];
fn parse_validation_errors(errors: Vec<OutputUnit<ErrorDescription>>) -> Error {
fn parse_validation_errors(errors: Vec<ErrorEntry<'_>>) -> Error {
let mut message = String::new();
for error in errors {
let description = error.error_description().to_string();
let description = error.error.to_string();
// HACK: error descriptions are sometimes a long rats' nest
// of JSON objects. We should render this in a palatable way
// but doing so is nontrivial, so we just skip them for now.
@ -319,7 +315,7 @@ fn parse_validation_errors(errors: Vec<OutputUnit<ErrorDescription>>) -> Error {
// the error for an unmatched "oneOf", so these errors are
// typically less useful anyways.
if !description.starts_with("{") {
let location = error.instance_location().as_str();
let location = error.instance_location.as_str();
if location.is_empty() {
writeln!(message, "{description}").expect("I/O on a String failed");
} else {
@ -379,18 +375,19 @@ where
// 2. The input is semantically invalid, and the user
// needs to fix it.
// We the JSON schema `validator` to separate these.
Ok(raw_value) => match validator
.apply(
Ok(raw_value) => {
let evaluation = validator.evaluate(
&serde_json::to_value(&raw_value)
.map_err(|e| CollectionError::Syntax(e.into()))?,
)
.basic()
{
Valid(_) => Err(e.into()),
Invalid(errors) => {
);
if evaluation.flag().valid {
Err(e.into())
} else {
let errors = evaluation.iter_errors().collect::<Vec<_>>();
Err(CollectionError::Schema(parse_validation_errors(errors)))
}
},
}
// Syntax error.
Err(e) => Err(CollectionError::Syntax(e.into())),
}

View file

@ -7,7 +7,7 @@ fn test_regular_persona() -> anyhow::Result<()> {
zizmor()
.input(input_under_test("anonymous-definition.yml"))
.run()?,
@r"No findings to report. Good job! (2 suppressed)"
@"No findings to report. Good job! (2 suppressed)"
);
Ok(())

View file

@ -80,28 +80,28 @@ fn test_jobs_missing_no_cancel() -> anyhow::Result<()> {
.args(["--persona=pedantic"])
.run()?,
@r"
help[concurrency-limits]: insufficient job-level concurrency limits
--> @@INPUT@@:9:5
|
9 | concurrency: group
| ^^^^^^^^^^^^^^^^^^ job concurrency is missing cancel-in-progress
|
= note: audit confidence High
help[concurrency-limits]: insufficient job-level concurrency limits
--> @@INPUT@@:9:5
|
9 | concurrency: group
| ^^^^^^^^^^^^^^^^^^ job concurrency is missing cancel-in-progress
|
= note: audit confidence High
help[concurrency-limits]: insufficient job-level concurrency limits
--> @@INPUT@@:1:1
|
1 | / name: Workflow with job 1 missing cancel-in-progress and job 2 missing concurrency
2 | | on: push
3 | | permissions: {}
... |
17 | | - name: 2-ok
18 | | run: echo ok
| |___________________^ missing concurrency setting
|
= note: audit confidence High
help[concurrency-limits]: insufficient job-level concurrency limits
--> @@INPUT@@:1:1
|
1 | / name: Workflow with job 1 missing cancel-in-progress and job 2 missing concurrency
2 | | on: push
3 | | permissions: {}
... |
17 | | - name: 2-ok
18 | | run: echo ok
| |___________________^ missing concurrency setting
|
= note: audit confidence High
2 findings: 0 informational, 2 low, 0 medium, 0 high
2 findings: 0 informational, 2 low, 0 medium, 0 high
"
);

View file

@ -165,7 +165,7 @@ fn test_config_short_cooldown_permitted() -> anyhow::Result<()> {
.input(input_under_test("dependabot-cooldown/default-days-too-short/dependabot.yml"))
.config(input_under_test("dependabot-cooldown/configs/cooldown-one-day.yml"))
.run()?,
@r"No findings to report. Good job!"
@"No findings to report. Good job!"
);
Ok(())

View file

@ -1,9 +1,6 @@
use crate::common::{input_under_test, zizmor};
#[cfg_attr(
any(not(feature = "gh-token-tests"), not(feature = "slow-tests")),
ignore
)]
#[cfg_attr(not(feature = "gh-token-tests"), ignore)]
#[test]
fn test_regular_persona() -> anyhow::Result<()> {
insta::assert_snapshot!(

View file

@ -33,7 +33,7 @@ fn test_issue_518_repro() -> Result<()> {
.input(input_under_test("ref-confusion/issue-518-repro.yml"))
.offline(false)
.run()?,
@r"No findings to report. Good job! (1 ignored, 1 suppressed)"
@"No findings to report. Good job! (1 ignored, 1 suppressed)"
);
Ok(())

View file

@ -42,7 +42,7 @@ fn test_nested_annotated_tags() -> Result<()> {
"ref-version-mismatch/nested-annotated-tags.yml"
))
.run()?,
@r"No findings to report. Good job! (1 suppressed)"
@"No findings to report. Good job! (1 suppressed)"
);
Ok(())

View file

@ -30,7 +30,7 @@ fn test_self_hosted_default() -> Result<()> {
zizmor()
.input(input_under_test("self-hosted.yml"))
.run()?,
@r"No findings to report. Good job! (1 suppressed)"
@"No findings to report. Good job! (1 suppressed)"
);
Ok(())

View file

@ -183,7 +183,7 @@ fn test_issue_418_repro() -> Result<()> {
zizmor()
.input(input_under_test("template-injection/issue-418-repro.yml"))
.run()?,
@r"No findings to report. Good job! (3 suppressed)"
@"No findings to report. Good job! (3 suppressed)"
);
Ok(())

View file

@ -64,7 +64,7 @@ fn test_undocumented_permissions_default() -> Result<()> {
zizmor()
.input(input_under_test("undocumented-permissions.yml"))
.run()?,
@r"No findings to report. Good job! (5 suppressed)"
@"No findings to report. Good job! (5 suppressed)"
);
Ok(())
@ -78,7 +78,7 @@ fn test_documented_permissions_pedantic() -> Result<()> {
.input(input_under_test("undocumented-permissions/documented.yml"))
.args(["--persona=pedantic"])
.run()?,
@r"No findings to report. Good job! (1 ignored)"
@"No findings to report. Good job! (1 ignored)"
);
Ok(())
@ -94,7 +94,7 @@ fn test_contents_read_only_pedantic() -> Result<()> {
))
.args(["--persona=pedantic"])
.run()?,
@r"No findings to report. Good job!"
@"No findings to report. Good job!"
);
Ok(())
@ -110,7 +110,7 @@ fn test_empty_permissions_pedantic() -> Result<()> {
))
.args(["--persona=pedantic"])
.run()?,
@r"No findings to report. Good job!"
@"No findings to report. Good job!"
);
Ok(())

View file

@ -114,7 +114,7 @@ fn test_issue_659_repro() -> Result<()> {
.input(input_under_test("unpinned-uses/issue-659-repro.yml"))
.args(["--pedantic"])
.run()?,
@r"No findings to report. Good job!"
@"No findings to report. Good job!"
);
Ok(())

View file

@ -357,7 +357,7 @@ fn test_issue_1191_repro() -> Result<()> {
"use-trusted-publishing/issue-1191-repro.yml"
))
.run()?,
@r"No findings to report. Good job! (3 suppressed)"
@"No findings to report. Good job! (3 suppressed)"
);
Ok(())

View file

@ -42,6 +42,7 @@ pub struct Zizmor {
stdin: Option<String>,
unbuffer: bool,
offline: bool,
gh_token: bool,
inputs: Vec<String>,
config: Option<String>,
no_config: bool,
@ -53,13 +54,19 @@ pub struct Zizmor {
impl Zizmor {
/// Create a new zizmor runner.
pub fn new() -> Self {
let cmd = Command::new(cargo::cargo_bin!());
let mut cmd = Command::new(cargo::cargo_bin!());
// Our child `zizmor` process starts with a clean environment, to
// ensure we explicitly test interactions with things like `CI`
// and `GH_TOKEN`.
cmd.env_clear();
Self {
cmd,
stdin: None,
unbuffer: false,
offline: true,
gh_token: true,
inputs: vec![],
config: None,
no_config: false,
@ -84,11 +91,6 @@ impl Zizmor {
self
}
pub fn unsetenv(mut self, key: &str) -> Self {
self.cmd.env_remove(key);
self
}
pub fn input(mut self, input: impl Into<String>) -> Self {
self.inputs.push(input.into());
self
@ -114,6 +116,11 @@ impl Zizmor {
self
}
pub fn gh_token(mut self, flag: bool) -> Self {
self.gh_token = flag;
self
}
pub fn output(mut self, output: OutputMode) -> Self {
self.output = output;
self
@ -147,7 +154,12 @@ impl Zizmor {
} else {
// If we're running in online mode, we pre-assert the
// presence of GH_TOKEN to make configuration failures more obvious.
std::env::var("GH_TOKEN").context("online tests require GH_TOKEN to be set")?;
let token =
std::env::var("GH_TOKEN").context("online tests require GH_TOKEN to be set")?;
if self.gh_token {
self.cmd.env("GH_TOKEN", token);
}
}
if self.no_config && self.config.is_some() {

View file

@ -164,6 +164,31 @@ fn test_discovers_config_in_dotgithub() -> anyhow::Result<()> {
Ok(())
}
/// Ensures we correctly discover a `zizmor.yaml` configuration file in a `.github`
/// subdirectory of a given input directory, i.e.
/// `config-in-dotgithub/.github/zizmor.yaml` in this case.
///
/// This tests that both `.yml` and `.yaml` extensions are supported.
#[test]
fn test_discovers_dotyaml_config_in_dotgithub() -> anyhow::Result<()> {
insta::assert_snapshot!(
zizmor()
.input(input_under_test("config-scenarios/dotyaml-config-in-dotgithub"))
.setenv("RUST_LOG", "zizmor::config=debug")
.output(OutputMode::Both)
.run()?,
@r"
🌈 zizmor v@@VERSION@@
DEBUG zizmor::config: discovering config for local input `@@INPUT@@`
DEBUG zizmor::config: attempting config discovery in `@@INPUT@@`
DEBUG zizmor::config: found config candidate at `@@INPUT@@/.github/zizmor.yaml`
No findings to report. Good job! (1 ignored, 2 suppressed)
",
);
Ok(())
}
/// Ensures we correctly discover a configuration file in a `.github`
/// subdirectory from an input filename, i.e. going from
/// `config-in-dotgithub/.github/workflows/hackme.yml`

View file

@ -83,13 +83,21 @@ fn menagerie() -> Result<()> {
#[test]
fn color_control_basic() -> Result<()> {
// No terminal, so no color by default.
// No terminal and not CI, so no color by default.
let no_color_default_output = zizmor()
.output(OutputMode::Both)
.input(input_under_test("e2e-menagerie"))
.run()?;
assert!(!no_color_default_output.contains("\x1b["));
// No terminal but CI, so color by default.
let color_default_ci_output = zizmor()
.setenv("CI", "true")
.output(OutputMode::Both)
.input(input_under_test("e2e-menagerie"))
.run()?;
assert!(color_default_ci_output.contains("\x1b["));
// Force color via --color=always.
let forced_color_via_arg_output = zizmor()
.output(OutputMode::Both)
@ -251,6 +259,25 @@ fn invalid_inputs() -> Result<()> {
);
}
insta::assert_snapshot!(
zizmor()
.expects_failure(true)
.input(input_under_test("invalid/empty/"))
.args(["--strict-collection"])
.run()?,
@r"
🌈 zizmor v@@VERSION@@
fatal: no audit was performed
error: no inputs collected
|
= help: collection yielded no auditable inputs
= help: inputs must contain at least one valid workflow, action, or Dependabot config
Caused by:
no inputs collected
"
);
Ok(())
}
@ -291,7 +318,13 @@ fn test_issue_1394() -> Result<()> {
🌈 zizmor v@@VERSION@@
WARN collect_inputs: zizmor::registry::input: failed to parse input: jobs.demo.steps[0]: duplicate entry with key "env" at line 10 column 9
fatal: no audit was performed
no inputs collected
error: no inputs collected
|
= help: collection yielded no auditable inputs
= help: inputs must contain at least one valid workflow, action, or Dependabot config
Caused by:
no inputs collected
"#
);
@ -575,7 +608,6 @@ fn test_cant_retrieve_offline() -> Result<()> {
zizmor()
.expects_failure(true)
.offline(true)
.unsetenv("GH_TOKEN")
.args(["pypa/sampleproject"])
.run()?,
@r"
@ -601,7 +633,7 @@ fn test_cant_retrieve_no_gh_token() -> Result<()> {
zizmor()
.expects_failure(true)
.offline(false)
.unsetenv("GH_TOKEN")
.gh_token(false)
.args(["pypa/sampleproject"])
.run()?,
@r"

View file

@ -5,4 +5,10 @@ expression: "zizmor().expects_failure(true).input(input_under_test(&format!(\"in
🌈 zizmor v@@VERSION@@
WARN collect_inputs: zizmor::registry::input: failed to validate input as action: input does not match expected validation schema
fatal: no audit was performed
no inputs collected
error: no inputs collected
|
= help: collection yielded no auditable inputs
= help: inputs must contain at least one valid workflow, action, or Dependabot config
Caused by:
no inputs collected

View file

@ -5,4 +5,10 @@ expression: "zizmor().expects_failure(true).input(input_under_test(&format!(\"in
🌈 zizmor v@@VERSION@@
WARN collect_inputs: zizmor::registry::input: failed to validate input as workflow: input does not match expected validation schema
fatal: no audit was performed
no inputs collected
error: no inputs collected
|
= help: collection yielded no auditable inputs
= help: inputs must contain at least one valid workflow, action, or Dependabot config
Caused by:
no inputs collected

View file

@ -8,5 +8,5 @@ failed to load file://@@INPUT@@ as workflow
Caused by:
0: input does not match expected validation schema
1: on.workflow_call.inputs.input: "type" is a required property
Additional properties are not allowed ('boom' was unexpected)
1: Additional properties are not allowed ('boom' was unexpected)
on.workflow_call.inputs.input: "type" is a required property

View file

@ -0,0 +1,16 @@
name: hackme
on:
issues:
permissions: {}
jobs:
inject-me:
name: inject-me
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # tag=v7.0.1
with:
script: |
return "doing a thing: ${{ github.event.issue.title }}"

View file

@ -0,0 +1,4 @@
rules:
template-injection:
ignore:
- hackme.yml

View file

@ -0,0 +1,3 @@
# empty
This is an empty directory, except for this README file.

View file

@ -519,7 +519,7 @@ In general, you should enable `cooldown` for all updaters.
Detects usages of `insecure-external-code-execution` in Dependabot configuration
files.
By default, Dependabot does not execution code from dependency manifests
By default, Dependabot does not execute code from dependency manifests
during updates. However, users can opt in to this behavior by setting
`#!yaml insecure-external-code-execution: allow` in their Dependabot
configuration.

View file

@ -9,7 +9,7 @@ description: zizmor's configuration file and configurable behaviors.
Configuration support was added in `v0.2.0`.
`zizmor` supports a small amount of configuration via [YAML] config files,
typically named `zizmor.yml`.
typically named `zizmor.yml` or `zizmor.yaml`.
[YAML]: https://learnxinyminutes.com/docs/yaml/
@ -41,9 +41,25 @@ typically named `zizmor.yml`.
* File inputs (e.g. `zizmor path/to/workflow.yml`): `zizmor` performs
directory discovery starting in the directory containing the given file.
* Directory inputs (e.g. `zizmor .`): `zizmor` looks for a `zizmor.yml` or
`.github/zizmor.yml` in the given directory or any parent, up to the
filesystem root or the first `.git` directory.
* Directory inputs (e.g. `zizmor .`): `zizmor` looks for a `zizmor.yml`
or `zizmor.yaml` file in the given directory, the `.github` child directory,
or any parent, up to the filesystem root or the first `.git` directory.
!!! example
Given an invocation like `zizmor ./repo/`, `zizmor` will attempt
to discover configuration files in the following order:
1. `./repo/.github/zizmor.yml`
2. `./repo/.github/zizmor.yaml`
3. `./repo/zizmor.yml`
4. `./repo/zizmor.yaml`
5. `./repo/../.github/zizmor.yml`
6. `./repo/../.github/zizmor.yaml`
7. ...and so on, until the filesystem root or a `.git/` directory is found.
!!! note

View file

@ -172,7 +172,7 @@ See [insta's documentation] for more details.
## Benchmarking
`zizmor` currently uses [hyperfine](https://github.org/sharkdp/hyperfine)
`zizmor` currently uses [pytest-codspeed](https://github.com/CodSpeedHQ/pytest-codspeed)
for command-line benchmarking.
Benchmarks are stored in the top-level `bench/` directory, and can be
@ -184,27 +184,22 @@ make bench
```
We currently run offline benchmarks in the CI and report their results
to [Bencher](https://bencher.dev/). See
[our project page](https://bencher.dev/console/projects/zizmor/plots)
on Bencher for results and trends.
to [CodSpeed](https://codspeed.io). See
[our project page](https://codspeed.io/zizmorcore/zizmor)
on CodSpeed for results and trends.
There are also online benchmarks, but these don't get run automatically.
To run them, you can pass `GH_TOKEN` to the `bench/benchmark.py` script
directly:
To run them, you can set `GH_TOKEN`:
```bash
GH_TOKEN=$(gh auth token) uv run bench/benchmark.py
GH_TOKEN=$(gh auth token) make bench
```
### Adding new benchmarks
`zizmor` currently orchestrates benchmarks with `bench/benchmark.py`,
which wraps `hyperfine` to add a planning phase.
Benchmarks are currently written as pytest functions.
Take a look at `bench/benchmarks.json` for the current benchmarks.
Observe that each benchmark tells `benchmark.py` how to retrieve its
input as well as provides a `stencil` that the benchmark runner will
expand to run the benchmark.
Take a look at `bench/test_*.py` for existing benchmarks.
## Building the website

View file

@ -33,12 +33,12 @@ jobs:
actions: read # Only needed for private repos. Needed for upload-sarif to read workflow run info.
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0
uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0
```
See the action's [`inputs` documentation][inputs-documentation] for
@ -94,12 +94,12 @@ GitHub Actions setup:
actions: read # Only needed for private repos. Needed for upload-sarif to read workflow run info.
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
- name: Run zizmor 🌈
run: uvx zizmor --format=sarif . > results.sarif # (2)!
@ -107,7 +107,7 @@ GitHub Actions setup:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # (1)!
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8
with:
sarif_file: results.sarif
category: zizmor
@ -164,10 +164,10 @@ GitHub Actions setup:
contents: read # Only needed for private repos. Needed to clone the repo.
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install the latest version of uv
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7.1.5
- name: Run zizmor 🌈
run: uvx zizmor --format=github . # (2)!
@ -255,7 +255,7 @@ To do so, add the following to your `.pre-commit-config.yaml` `#!yaml repos:` se
```yaml
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.18.0 # (1)!
rev: v1.19.0 # (1)!
hooks:
- id: zizmor
```

View file

@ -9,6 +9,13 @@ of `zizmor`.
## Next (UNRELEASED)
### Enhancements 🌱
* The [excessive-permissions] audit is now aware of the `artifact-metadata`
and `models` permissions (#1461)
## 1.19.0
### New Features 🌈
* **New audit**: [archived-uses] detects usages of archived repositories in
@ -30,12 +37,37 @@ of `zizmor`.
* zizmor now produces more useful and less ambiguous spans for many findings,
particularly those from the [anonymous-definition] audit (#1416)
* zizmor now discovers configuration files named `zizmor.yaml`, in addition
to `zizmor.yml` (#1431)
* zizmor now produces a more useful error message when input collection
yields no inputs (#1439)
* The `--render-links` flag now allows users to control `zizmor`'s OSC 8 terminal
link rendering behavior. This is particularly useful in environments that
advertise themselves as terminals but fail to correctly render or ignore
OSC 8 links (#1454)
### Performance Improvements 🚄
* The [impostor-commit] audit is now significantly faster on true positives,
making true positive detection virtually as fast as true negative detection.
In practice, true positive runs are over 100 times faster than before
(#1429)
### Bug Fixes 🐛
* Fixed a bug where the [obfuscation] audit would crash if it encountered
a CMD shell that was defined outside of the current step block (i.e.
as a job or workflow default) (#1418)
* Fixed a bug where the `opentofu` ecosystem was not recognized in
Dependabot configuration files (#1452)
* `--color=always` no longer implies `--render-links=always`, as some
environments (like GitHub Actions) support ANSI color codes but fail
to handle OSC escapes gracefully (#1454)
## 1.18.0
@ -1173,7 +1205,7 @@ This is one of `zizmor`'s bigger recent releases! Key enhancements include:
### What's Changed
* fix(cli): remove '0 ignored' from another place by @woodruffw in #157
* perf: speed up impostor-commit's fast path by @woodruffw in #158
* perf: speed up [impostor-commit]'s fast path by @woodruffw in #158
* fix(cli): fixup error printing by @woodruffw in #159
## v0.3.1
@ -1326,5 +1358,6 @@ This is one of `zizmor`'s bigger recent releases! Key enhancements include:
[dependabot-cooldown]: ./audits.md#dependabot-cooldown
[concurrency-limits]: ./audits.md#concurrency-limits
[archived-uses]: ./audits.md#archived-uses
[impostor-commit]: ./audits.md#impostor-commit
[exit code]: ./usage.md#exit-codes

View file

@ -28,6 +28,8 @@ Options:
Don't show progress bars, even if the terminal supports them
--format <FORMAT>
The output format to emit. By default, cargo-style diagnostics will be emitted [default: plain] [possible values: plain, json, json-v1, sarif, github]
--render-links <RENDER_LINKS>
Whether to render OSC 8 links in the output [env: ZIZMOR_RENDER_LINKS=] [default: auto] [possible values: auto, always, never]
--show-audit-urls <SHOW_AUDIT_URLS>
Whether to render audit URLs in the output, separately from any URLs embedded in OSC 8 links [env: ZIZMOR_SHOW_AUDIT_URLS=] [default: auto] [possible values: auto, always, never]
--color <MODE>

View file

@ -24,6 +24,14 @@
- aio-libs/aiobotocore#1355
- ![](https://github.com/altair-graphql.png?size=40){ width="40" loading=lazy align=left } altair-graphql
---
??? example "Examples"
- altair-graphql/altair@4aa5679f89528c183321a07e387567c13be29f26
- ![](https://github.com/anchore.png?size=40){ width="40" loading=lazy align=left } anchore
---
@ -357,6 +365,15 @@
- docker/compose#12737
- ![](https://github.com/dubzzz.png?size=40){ width="40" loading=lazy align=left } dubzzz
---
??? example "Examples"
- dubzzz/fast-check#6369
- dubzzz/fast-check#6370
- ![](https://github.com/earthobservations.png?size=40){ width="40" loading=lazy align=left } earthobservations
---
@ -1106,6 +1123,10 @@
---
??? example "Examples"
- psf/black#4901
- psf/black#4905
- psf/black#4906
- psf/black#4911
- psf/cachecontrol#345

View file

@ -8,6 +8,7 @@ adafruit/circuitpython#9785
ag2ai/faststream#2398
aio-libs/aiobotocore#1344
aio-libs/aiobotocore#1355
altair-graphql/altair@4aa5679f89528c183321a07e387567c13be29f26
anchore/vunnel#832
ansible/ansible-documentation#3188
apache/airflow#45408
@ -75,6 +76,8 @@ django/django@86b8058b40145fb5ba4fd859676225f533eca986
django-commons/django-tasks-scheduler#272
Diaoul/subliminal#1190
docker/compose#12737
dubzzz/fast-check#6369
dubzzz/fast-check#6370
earthobservations/wetterdienst#1440
edgelesssys/contrast#1604
EFForg/rayhunter#711
@ -231,6 +234,10 @@ praetorian-inc/noseyparker#228
prettytable/prettytable#339
privacyidea/privacyidea#4854
prometheus/prometheus#16530
psf/black#4901
psf/black#4905
psf/black#4906
psf/black#4911
psf/cachecontrol#345
pubgrub-rs/pubgrub#389
pyca/service-identity#75

View file

@ -102,7 +102,7 @@ If you run into this issue, you have two options:
in your repository secrets, you could do:
```yaml title="example/repoA/.github/workflows/ci.yml" hl_lines="3"
- uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0
- uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0
with:
token: ${{ secrets.ZIZMOR_GH_TOKEN }}
```

View file

@ -16,4 +16,9 @@ manifest-path = "crates/zizmor/Cargo.toml"
include = [{ path = "README.md", format = "sdist" }, { path = "LICENSE", format = "sdist" }]
[dependency-groups]
bench = [
"pytest>=9.0.2",
"pytest-codspeed>=4.2.0",
"urllib3>=2.6.2",
]
docs = ["zensical"]

236
uv.lock generated
View file

@ -2,6 +2,88 @@ version = 1
revision = 3
requires-python = ">=3.10"
[[package]]
name = "cffi"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" },
{ url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" },
{ url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" },
{ url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" },
{ url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" },
{ url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" },
{ url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" },
{ url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" },
{ url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" },
{ url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" },
{ url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" },
{ url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" },
{ url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
{ url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
{ url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
{ url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
{ url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
{ url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" },
{ url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" },
{ url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
{ url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
{ url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
{ url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
{ url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
{ url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
{ url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
{ url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
{ url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
{ url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
{ url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
{ url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
{ url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
{ url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
{ url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
{ url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
{ url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
]
[[package]]
name = "click"
version = "8.3.1"
@ -32,6 +114,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "markdown"
version = "3.10"
@ -41,6 +144,54 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pycparser"
version = "2.23"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
@ -63,6 +214,50 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/93/78/b93cb80bd673bdc9f6ede63d8eb5b4646366953df15667eb3603be57a2b1/pymdown_extensions-10.17.2-py3-none-any.whl", hash = "sha256:bffae79a2e8b9e44aef0d813583a8fea63457b7a23643a43988055b7b79b4992", size = 266556, upload-time = "2025-11-26T15:43:55.162Z" },
]
[[package]]
name = "pytest"
version = "9.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
]
[[package]]
name = "pytest-codspeed"
version = "4.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
{ name = "pytest" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e2/e8/27fcbe6516a1c956614a4b61a7fccbf3791ea0b992e07416e8948184327d/pytest_codspeed-4.2.0.tar.gz", hash = "sha256:04b5d0bc5a1851ba1504d46bf9d7dbb355222a69f2cd440d54295db721b331f7", size = 113263, upload-time = "2025-10-24T09:02:55.704Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/b8/d599a466c50af3f04001877ae8b17c12b803f3b358235736b91a0769de0d/pytest_codspeed-4.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:609828b03972966b75b9b7416fa2570c4a0f6124f67e02d35cd3658e64312a7b", size = 261943, upload-time = "2025-10-24T09:02:37.962Z" },
{ url = "https://files.pythonhosted.org/packages/74/19/ccc1a2fcd28357a8db08ba6b60f381832088a3850abc262c8e0b3406491a/pytest_codspeed-4.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23a0c0fbf8bb4de93a3454fd9e5efcdca164c778aaef0a9da4f233d85cb7f5b8", size = 250782, upload-time = "2025-10-24T09:02:39.617Z" },
{ url = "https://files.pythonhosted.org/packages/b9/2d/f0083a2f14ecf008d961d40439a71da0ae0d568e5f8dc2fccd3e8a2ab3e4/pytest_codspeed-4.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2de87bde9fbc6fd53f0fd21dcf2599c89e0b8948d49f9bad224edce51c47e26b", size = 261960, upload-time = "2025-10-24T09:02:40.665Z" },
{ url = "https://files.pythonhosted.org/packages/5f/0c/1f514c553db4ea5a69dfbe2706734129acd0eca8d5101ec16f1dd00dbc0f/pytest_codspeed-4.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95aeb2479ca383f6b18e2cc9ebcd3b03ab184980a59a232aea6f370bbf59a1e3", size = 250808, upload-time = "2025-10-24T09:02:42.07Z" },
{ url = "https://files.pythonhosted.org/packages/81/04/479905bd6653bc981c0554fcce6df52d7ae1594e1eefd53e6cf31810ec7f/pytest_codspeed-4.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d4fefbd4ae401e2c60f6be920a0be50eef0c3e4a1f0a1c83962efd45be38b39", size = 262084, upload-time = "2025-10-24T09:02:43.155Z" },
{ url = "https://files.pythonhosted.org/packages/d2/46/d6f345d7907bac6cbb6224bd697ecbc11cf7427acc9e843c3618f19e3476/pytest_codspeed-4.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:309b4227f57fcbb9df21e889ea1ae191d0d1cd8b903b698fdb9ea0461dbf1dfe", size = 251100, upload-time = "2025-10-24T09:02:44.168Z" },
{ url = "https://files.pythonhosted.org/packages/de/dc/e864f45e994a50390ff49792256f1bdcbf42f170e3bc0470ee1a7d2403f3/pytest_codspeed-4.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72aab8278452a6d020798b9e4f82780966adb00f80d27a25d1274272c54630d5", size = 262057, upload-time = "2025-10-24T09:02:45.791Z" },
{ url = "https://files.pythonhosted.org/packages/1d/1c/f1d2599784486879cf6579d8d94a3e22108f0e1f130033dab8feefd29249/pytest_codspeed-4.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:684fcd9491d810ded653a8d38de4835daa2d001645f4a23942862950664273f8", size = 251013, upload-time = "2025-10-24T09:02:46.937Z" },
{ url = "https://files.pythonhosted.org/packages/0c/fd/eafd24db5652a94b4d00fe9b309b607de81add0f55f073afb68a378a24b6/pytest_codspeed-4.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50794dabea6ec90d4288904452051e2febace93e7edf4ca9f2bce8019dd8cd37", size = 262065, upload-time = "2025-10-24T09:02:48.018Z" },
{ url = "https://files.pythonhosted.org/packages/f9/14/8d9340d7dc0ae647991b28a396e16b3403e10def883cde90d6b663d3f7ec/pytest_codspeed-4.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0ebd87f2a99467a1cfd8e83492c4712976e43d353ee0b5f71cbb057f1393aca", size = 251057, upload-time = "2025-10-24T09:02:49.102Z" },
{ url = "https://files.pythonhosted.org/packages/4b/39/48cf6afbca55bc7c8c93c3d4ae926a1068bcce3f0241709db19b078d5418/pytest_codspeed-4.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbbb2d61b85bef8fc7e2193f723f9ac2db388a48259d981bbce96319043e9830", size = 267983, upload-time = "2025-10-24T09:02:50.558Z" },
{ url = "https://files.pythonhosted.org/packages/33/86/4407341efb5dceb3e389635749ce1d670542d6ca148bd34f9d5334295faf/pytest_codspeed-4.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:748411c832147bfc85f805af78a1ab1684f52d08e14aabe22932bbe46c079a5f", size = 256732, upload-time = "2025-10-24T09:02:51.603Z" },
{ url = "https://files.pythonhosted.org/packages/25/0e/8cb71fd3ed4ed08c07aec1245aea7bc1b661ba55fd9c392db76f1978d453/pytest_codspeed-4.2.0-py3-none-any.whl", hash = "sha256:e81bbb45c130874ef99aca97929d72682733527a49f84239ba575b5cb843bab0", size = 113726, upload-time = "2025-10-24T09:02:54.785Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.3"
@ -127,6 +322,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]]
name = "rich"
version = "14.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
]
[[package]]
name = "tomli"
version = "2.3.0"
@ -176,6 +384,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "urllib3"
version = "2.6.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
]
[[package]]
name = "zensical"
version = "0.0.10"
@ -210,6 +436,11 @@ name = "zizmor"
source = { editable = "." }
[package.dev-dependencies]
bench = [
{ name = "pytest" },
{ name = "pytest-codspeed" },
{ name = "urllib3" },
]
docs = [
{ name = "zensical" },
]
@ -217,4 +448,9 @@ docs = [
[package.metadata]
[package.metadata.requires-dev]
bench = [
{ name = "pytest", specifier = ">=9.0.2" },
{ name = "pytest-codspeed", specifier = ">=4.2.0" },
{ name = "urllib3", specifier = ">=2.6.2" },
]
docs = [{ name = "zensical" }]