diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6990cbb..736b092 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,8 +14,6 @@ on: permissions: contents: read - pages: write - id-token: write concurrency: group: "pages" @@ -27,6 +25,8 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 @@ -55,6 +55,9 @@ jobs: deploy: needs: build if: github.ref == 'refs/heads/main' + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index b8e1e0b..bfc9505 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -13,6 +13,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: @@ -29,3 +31,8 @@ jobs: - name: Structural checks (manifests + frontmatter + guards) run: make test + + - name: GHA security (zizmor) + run: make zizmor + env: + GH_TOKEN: ${{ github.token }} diff --git a/Makefile b/Makefile index 7362771..296775c 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ ## that techne itself documents at docs/conventions.md. ## -.PHONY: help check-env setup manifests frontmatter fix lint shellcheck guards test validate build ci clean docs +.PHONY: help check-env setup manifests frontmatter fix lint shellcheck guards zizmor test validate build ci clean docs .DEFAULT_GOAL := help check-env: ## Verify required tools are on PATH @@ -41,9 +41,12 @@ guards: ## Stale-path + legacy-name + action-pin guards fi @bash scripts/check_action_pins.sh +zizmor: ## zizmor GHA security scan (.github/workflows/) + @uv run zizmor .github/workflows/ + test: manifests frontmatter guards ## Structural checks (manifests + frontmatter + guards) -validate: lint shellcheck test ## Fast pre-push gate +validate: lint shellcheck zizmor test ## Fast pre-push gate build: ## Build docs site (strict; mirrors docs.yml deploy) @uv run zensical build --clean --strict diff --git a/ROADMAP.md b/ROADMAP.md index d9aac40..e56f7cb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -117,6 +117,23 @@ wild. Detail lives in git history (`git log`) and the live skill code. This log is pruned once work is durably shipped. +- 2026-05-26 — **zizmor GHA static analysis (techne dogfood).** Adopted + [zizmor](https://github.com/zizmorcore/zizmor) as a dev dep + `make zizmor` + target, wired into `make validate` and the validate.yml gate — extending the + GHA-security layer beyond `check_action_pins.sh` (pinning-only) to zizmor's + security audits (template injection, excessive-permissions, artipacked, + unpinned-uses, …). Fixed what it surfaced in techne's own workflows: + least-privilege per-job permissions on docs.yml (`pages: write` + `id-token: + write` moved off the workflow level onto the deploy job only — build needs + just `contents: read`, since `configure-pages` defaults to `enablement: false` + and Pages is already enabled), plus `persist-credentials: false` on every + checkout (artipacked). research(2026-05): Trail of Bits "We hardened zizmor" + (2026-05-22); zizmor audit docs; zizmor + actionlint are complementary + (security vs correctness). Remaining: propagate to the sisters — every FL/docs + sister carries the same docs.yml workflow-level permission over-grant + + artipacked, each needing its findings triaged (separate PRs). actionlint and + zizmor SARIF→code-scanning upload are later enhancements. + - 2026-05-25 — **GitHub Actions SHA-pinning (fleet hardening).** Reversed the prior deferral after re-checking May-2026 best practice. Every workflow `uses:` ref is pinned to a full commit SHA (`# vX.Y.Z` comment preserved), enforced by `make diff --git a/pyproject.toml b/pyproject.toml index fa4d259..0314c0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ dev = [ "ruff>=0.15.14", "zensical>=0.0.43", "shellcheck-py>=0.11", + "zizmor>=1.25", ] [tool.uv] diff --git a/uv.lock b/uv.lock index 94bf340..d3dc981 100644 --- a/uv.lock +++ b/uv.lock @@ -191,6 +191,7 @@ dev = [ { name = "ruff" }, { name = "shellcheck-py" }, { name = "zensical" }, + { name = "zizmor" }, ] [package.metadata] @@ -200,6 +201,7 @@ dev = [ { name = "ruff", specifier = ">=0.15.14" }, { name = "shellcheck-py", specifier = ">=0.11" }, { name = "zensical", specifier = ">=0.0.43" }, + { name = "zizmor", specifier = ">=1.25" }, ] [[package]] @@ -258,3 +260,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/c6/1688ec6e5be15e3ab367d7804753291bfbdff3109b06e20c19ce30a7129c/zensical-0.0.43-cp310-abi3-win32.whl", hash = "sha256:8a75ddd4bb3cd3c4a8e71d2ebae44c5611fd636c1d355c6124dd96e2f9c52838", size = 12256740, upload-time = "2026-05-19T09:44:02.102Z" }, { url = "https://files.pythonhosted.org/packages/ca/a8/d967e70eac810a7e9eb8c5150d6d02848a1f42260f42977c71debed3cb02/zensical-0.0.43-cp310-abi3-win_amd64.whl", hash = "sha256:03a9d1744a6394ad66c355d6f1de04cfd92efa525b0b94bf6dbf6971c5cd2c6b", size = 12496166, upload-time = "2026-05-19T09:44:04.915Z" }, ] + +[[package]] +name = "zizmor" +version = "1.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/41/8987d546e3101cc76748b2f1b0ccda58e244773ef5124d39e7e749e3d6e4/zizmor-1.25.2.tar.gz", hash = "sha256:f26ffeb16659c8922c7b08203ca5a4f8bf5e1a7e8d190734961c40877cf778ea", size = 517794, upload-time = "2026-05-16T06:28:43.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/bd/84108a92ccbfda0d28efc11f382997c7a767b58863bf4a550634b8cf0211/zizmor-1.25.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17cc8cfd9d472e8b11945a869c198d25cfdf4a33f36fa7a1f9674099f5fb509d", size = 9115548, upload-time = "2026-05-16T06:28:33.591Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c0/66453a2553a66286a96ca32d75e3e6bcc94ce7f907cd5f8c2c3fce55315e/zizmor-1.25.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3e301eb4465e2da77857cf01ab4ef0184cf3818e826800b270ab01ae7338977", size = 8665071, upload-time = "2026-05-16T06:28:30.861Z" }, + { url = "https://files.pythonhosted.org/packages/52/3e/d60939d1cc4907c0d021a7c46362aab5e8045550bb09157d56c070e43568/zizmor-1.25.2-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:cf64374149b567c9373228b76c8e77a389b4071899f84b82c36ee50fab894e79", size = 8842884, upload-time = "2026-05-16T06:28:26.041Z" }, + { url = "https://files.pythonhosted.org/packages/46/82/f3e8d9b6d941194f2558591b449c106d46a16ea566b95eccff3a83bf6acc/zizmor-1.25.2-py3-none-manylinux_2_28_armv7l.whl", hash = "sha256:0beba1601be08bd00c9277e6ed4b026e125b26b379d86d6d98eb708409b3050d", size = 8449741, upload-time = "2026-05-16T06:28:45.424Z" }, + { url = "https://files.pythonhosted.org/packages/4b/13/445bc98acc2c976d6b8f8ca59b9c09f055adb5ffb3445d99af8ff7efcb4f/zizmor-1.25.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:c4246f1344d8dbeffc044d7bb11b131773a7db7eb57d9073c45942dfd3543a1f", size = 9285184, upload-time = "2026-05-16T06:28:39.21Z" }, + { url = "https://files.pythonhosted.org/packages/cf/78/fc7717c706bde7531b2fde12003994fbc04c47ab4f91aa6ca9b3b24b30fd/zizmor-1.25.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dbb1b5c85b8de8eaa0227c6620f06c8e4fbd0a4da2086e218bc225c0bef0923d", size = 8886579, upload-time = "2026-05-16T06:28:51.384Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bc/a46f11377cdc145c625d62d88c30fead56f9d29bc31652069a1a0eaed6c2/zizmor-1.25.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d670a1e2f00b3cd56febd145bc1a0b2c4caf1cbe5dad8128721843fa877e2d2e", size = 8413576, upload-time = "2026-05-16T06:28:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/2b/3b/0fd93b77171c8f229e8e1304eecc9931bf3009f722c57967d545d9f151b6/zizmor-1.25.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b75c84d7387389f95edadbe859fb2aaf0a360c5b080932cc53e92ae1db6f09ef", size = 9378162, upload-time = "2026-05-16T06:28:41.999Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3f/dcb85fb9a0d87794847f9043f9db9bb4d274cf4b8077604bc13850c8fdb4/zizmor-1.25.2-py3-none-win32.whl", hash = "sha256:aa9f4c43b499c55339c3ef2e885133c5017cd9a18d76d9335541203cfa5ae1e7", size = 7548509, upload-time = "2026-05-16T06:28:28.828Z" }, + { url = "https://files.pythonhosted.org/packages/d2/81/1cb088098bd53f9b910098b0c19d06dc587acf328a170ef8afd1cd93b482/zizmor-1.25.2-py3-none-win_amd64.whl", hash = "sha256:af55bd9bd119ea8cbce2a7addc3922503019de32c1fe31106d70b3dc77d77908", size = 8609822, upload-time = "2026-05-16T06:28:48.078Z" }, +]