zizmor 1.2.0__tar.gz → 1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of zizmor might be problematic. Click here for more details.

Files changed (198) hide show
  1. {zizmor-1.2.0 → zizmor-1.2.2}/.github/workflows/ci.yml +14 -2
  2. {zizmor-1.2.0 → zizmor-1.2.2}/.github/workflows/site.yml +1 -1
  3. {zizmor-1.2.0 → zizmor-1.2.2}/.github/workflows/zizmor.yml +3 -1
  4. {zizmor-1.2.0 → zizmor-1.2.2}/Cargo.lock +3 -3
  5. {zizmor-1.2.0 → zizmor-1.2.2}/Cargo.toml +2 -2
  6. {zizmor-1.2.0 → zizmor-1.2.2}/Makefile +4 -4
  7. {zizmor-1.2.0 → zizmor-1.2.2}/PKG-INFO +3 -1
  8. {zizmor-1.2.0 → zizmor-1.2.2}/docs/release-notes.md +21 -0
  9. {zizmor-1.2.0 → zizmor-1.2.2}/docs/usage.md +1 -1
  10. zizmor-1.2.2/pyproject.toml +17 -0
  11. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/bot_conditions.rs +2 -3
  12. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/excessive_permissions.rs +39 -22
  13. {zizmor-1.2.0 → zizmor-1.2.2}/src/github_api.rs +11 -1
  14. {zizmor-1.2.0 → zizmor-1.2.2}/src/main.rs +28 -15
  15. {zizmor-1.2.0 → zizmor-1.2.2}/src/models.rs +28 -15
  16. {zizmor-1.2.0 → zizmor-1.2.2}/src/registry.rs +53 -18
  17. {zizmor-1.2.0 → zizmor-1.2.2}/src/render.rs +5 -1
  18. {zizmor-1.2.0 → zizmor-1.2.2}/src/sarif.rs +2 -1
  19. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshot.rs +19 -0
  20. zizmor-1.2.2/tests/snapshots/snapshot__excessive_permissions-10.snap +21 -0
  21. zizmor-1.2.2/tests/snapshots/snapshot__excessive_permissions-11.snap +19 -0
  22. zizmor-1.2.2/tests/snapshots/snapshot__excessive_permissions-12.snap +47 -0
  23. zizmor-1.2.2/tests/test-data/excessive-permissions/issue-472-repro.yml +23 -0
  24. zizmor-1.2.2/tests/test-data/excessive-permissions/reusable-workflow-call.yml +9 -0
  25. zizmor-1.2.2/tests/test-data/excessive-permissions/reusable-workflow-other-triggers.yml +21 -0
  26. zizmor-1.2.2/uv.lock +869 -0
  27. zizmor-1.2.0/pyproject.toml +0 -6
  28. zizmor-1.2.0/site-requirements.txt +0 -2
  29. {zizmor-1.2.0 → zizmor-1.2.2}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  30. {zizmor-1.2.0 → zizmor-1.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  31. {zizmor-1.2.0 → zizmor-1.2.2}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  32. {zizmor-1.2.0 → zizmor-1.2.2}/.github/dependabot.yml +0 -0
  33. {zizmor-1.2.0 → zizmor-1.2.2}/.github/workflows/pypi.yml +0 -0
  34. {zizmor-1.2.0 → zizmor-1.2.2}/.github/workflows/release.yml +0 -0
  35. {zizmor-1.2.0 → zizmor-1.2.2}/.gitignore +0 -0
  36. {zizmor-1.2.0 → zizmor-1.2.2}/CONTRIBUTING.md +0 -0
  37. {zizmor-1.2.0 → zizmor-1.2.2}/LICENSE +0 -0
  38. {zizmor-1.2.0 → zizmor-1.2.2}/README.md +0 -0
  39. {zizmor-1.2.0 → zizmor-1.2.2}/docs/assets/favicon48x48.png +0 -0
  40. {zizmor-1.2.0 → zizmor-1.2.2}/docs/assets/rainbow.svg +0 -0
  41. {zizmor-1.2.0 → zizmor-1.2.2}/docs/assets/zizmor-demo.gif +0 -0
  42. {zizmor-1.2.0 → zizmor-1.2.2}/docs/audits.md +0 -0
  43. {zizmor-1.2.0 → zizmor-1.2.2}/docs/configuration.md +0 -0
  44. {zizmor-1.2.0 → zizmor-1.2.2}/docs/development.md +0 -0
  45. {zizmor-1.2.0 → zizmor-1.2.2}/docs/index.md +0 -0
  46. {zizmor-1.2.0 → zizmor-1.2.2}/docs/installation.md +0 -0
  47. {zizmor-1.2.0 → zizmor-1.2.2}/docs/magiclink.css +0 -0
  48. {zizmor-1.2.0 → zizmor-1.2.2}/docs/quickstart.md +0 -0
  49. {zizmor-1.2.0 → zizmor-1.2.2}/docs/snippets/help.txt +0 -0
  50. {zizmor-1.2.0 → zizmor-1.2.2}/docs/snippets/render-sponsors.py +0 -0
  51. {zizmor-1.2.0 → zizmor-1.2.2}/docs/snippets/render-trophies.py +0 -0
  52. {zizmor-1.2.0 → zizmor-1.2.2}/docs/snippets/sponsors.html +0 -0
  53. {zizmor-1.2.0 → zizmor-1.2.2}/docs/snippets/sponsors.json +0 -0
  54. {zizmor-1.2.0 → zizmor-1.2.2}/docs/snippets/trophies.md +0 -0
  55. {zizmor-1.2.0 → zizmor-1.2.2}/docs/snippets/trophies.txt +0 -0
  56. {zizmor-1.2.0 → zizmor-1.2.2}/docs/trophy-case.md +0 -0
  57. {zizmor-1.2.0 → zizmor-1.2.2}/mkdocs.yml +0 -0
  58. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/artipacked.rs +0 -0
  59. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/cache_poisoning.rs +0 -0
  60. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/dangerous_triggers.rs +0 -0
  61. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/github_env.rs +0 -0
  62. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/hardcoded_container_credentials.rs +0 -0
  63. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/impostor_commit.rs +0 -0
  64. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/insecure_commands.rs +0 -0
  65. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/known_vulnerable_actions.rs +0 -0
  66. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/mod.rs +0 -0
  67. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/ref_confusion.rs +0 -0
  68. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/secrets_inherit.rs +0 -0
  69. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/self_hosted_runner.rs +0 -0
  70. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/template_injection.rs +0 -0
  71. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/unpinned_uses.rs +0 -0
  72. {zizmor-1.2.0 → zizmor-1.2.2}/src/audit/use_trusted_publishing.rs +0 -0
  73. {zizmor-1.2.0 → zizmor-1.2.2}/src/config.rs +0 -0
  74. {zizmor-1.2.0 → zizmor-1.2.2}/src/expr/expr.pest +0 -0
  75. {zizmor-1.2.0 → zizmor-1.2.2}/src/expr/mod.rs +0 -0
  76. {zizmor-1.2.0 → zizmor-1.2.2}/src/finding/locate.rs +0 -0
  77. {zizmor-1.2.0 → zizmor-1.2.2}/src/finding/mod.rs +0 -0
  78. {zizmor-1.2.0 → zizmor-1.2.2}/src/models/coordinate.rs +0 -0
  79. {zizmor-1.2.0 → zizmor-1.2.2}/src/models/uses.rs +0 -0
  80. {zizmor-1.2.0 → zizmor-1.2.2}/src/state.rs +0 -0
  81. {zizmor-1.2.0 → zizmor-1.2.2}/src/utils.rs +0 -0
  82. {zizmor-1.2.0 → zizmor-1.2.2}/tests/acceptance.rs +0 -0
  83. {zizmor-1.2.0 → zizmor-1.2.2}/tests/common.rs +0 -0
  84. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__artipacked-2.snap +0 -0
  85. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__artipacked-3.snap +0 -0
  86. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__artipacked-4.snap +0 -0
  87. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__artipacked.snap +0 -0
  88. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__bot_conditions.snap +0 -0
  89. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-10.snap +0 -0
  90. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-11.snap +0 -0
  91. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-12.snap +0 -0
  92. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-13.snap +0 -0
  93. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-14.snap +0 -0
  94. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-2.snap +0 -0
  95. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-3.snap +0 -0
  96. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-4.snap +0 -0
  97. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-5.snap +0 -0
  98. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-6.snap +0 -0
  99. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-7.snap +0 -0
  100. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-8.snap +0 -0
  101. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning-9.snap +0 -0
  102. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cache_poisoning.snap +0 -0
  103. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__cant_retrieve.snap +0 -0
  104. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__conflicting_online_options-2.snap +0 -0
  105. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__conflicting_online_options-3.snap +0 -0
  106. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__conflicting_online_options.snap +0 -0
  107. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-2.snap +0 -0
  108. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-3.snap +0 -0
  109. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-4.snap +0 -0
  110. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-5.snap +0 -0
  111. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-6.snap +0 -0
  112. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-7.snap +0 -0
  113. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-8.snap +0 -0
  114. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions-9.snap +0 -0
  115. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__excessive_permissions.snap +0 -0
  116. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__github_env-2.snap +0 -0
  117. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__github_env-3.snap +0 -0
  118. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__github_env.snap +0 -0
  119. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__insecure_commands-2.snap +0 -0
  120. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__insecure_commands-3.snap +0 -0
  121. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__insecure_commands.snap +0 -0
  122. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__secrets_inherit.snap +0 -0
  123. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted-2.snap +0 -0
  124. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted-3.snap +0 -0
  125. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted-4.snap +0 -0
  126. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted-5.snap +0 -0
  127. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted-6.snap +0 -0
  128. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted-7.snap +0 -0
  129. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted-8.snap +0 -0
  130. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__self_hosted.snap +0 -0
  131. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection-2.snap +0 -0
  132. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection-3.snap +0 -0
  133. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection-4.snap +0 -0
  134. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection-5.snap +0 -0
  135. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection-6.snap +0 -0
  136. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection-7.snap +0 -0
  137. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection-8.snap +0 -0
  138. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__template_injection.snap +0 -0
  139. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__unpinned_uses-2.snap +0 -0
  140. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__unpinned_uses-3.snap +0 -0
  141. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__unpinned_uses-4.snap +0 -0
  142. {zizmor-1.2.0 → zizmor-1.2.2}/tests/snapshots/snapshot__unpinned_uses.snap +0 -0
  143. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/artipacked/issue-447-repro.yml +0 -0
  144. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/artipacked.yml +0 -0
  145. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/bot-conditions.yml +0 -0
  146. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-disabled-by-default.yml +0 -0
  147. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-enabled-by-default.yml +0 -0
  148. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-not-configurable.yml +0 -0
  149. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-opt-in-boolean-toggle.yml +0 -0
  150. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-opt-in-boolish-toggle.yml +0 -0
  151. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-opt-in-expression.yml +0 -0
  152. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-opt-in-multi-value-toggle.yml +0 -0
  153. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/caching-opt-out.yml +0 -0
  154. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/issue-343-repro.yml +0 -0
  155. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/issue-378-repro.yml +0 -0
  156. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/no-cache-aware-steps.yml +0 -0
  157. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/publisher-step.yml +0 -0
  158. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/workflow-release-branch-trigger.yml +0 -0
  159. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning/workflow-tag-trigger.yml +0 -0
  160. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/cache-poisoning.yml +0 -0
  161. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/issue-336-repro.yml +0 -0
  162. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/jobs-broaden-permissions.yml +0 -0
  163. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/workflow-default-perms-all-jobs-explicit.yml +0 -0
  164. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/workflow-default-perms.yml +0 -0
  165. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/workflow-empty-perms.yml +0 -0
  166. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/workflow-read-all.yml +0 -0
  167. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/workflow-write-all.yml +0 -0
  168. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions/workflow-write-explicit.yml +0 -0
  169. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/excessive-permissions.yml +0 -0
  170. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/github-env/action.yml +0 -0
  171. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/github-env/github-path.yml +0 -0
  172. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/github-env/issue-397-repro.yml +0 -0
  173. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/github_env.yml +0 -0
  174. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/hardcoded-credentials.yml +0 -0
  175. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/inlined-ignores.yml +0 -0
  176. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/insecure-commands/action.yml +0 -0
  177. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/insecure-commands.yml +0 -0
  178. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/secrets-inherit.yml +0 -0
  179. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/self-hosted/issue-283-repro.yml +0 -0
  180. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/self-hosted/self-hosted-matrix-dimension.yml +0 -0
  181. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/self-hosted/self-hosted-matrix-exclusion.yml +0 -0
  182. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/self-hosted/self-hosted-matrix-inclusion.yml +0 -0
  183. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/self-hosted/self-hosted-runner-group.yml +0 -0
  184. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/self-hosted/self-hosted-runner-label.yml +0 -0
  185. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/self-hosted.yml +0 -0
  186. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/issue-22-repro.yml +0 -0
  187. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/issue-339-repro.yml +0 -0
  188. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/issue-418-repro.yml +0 -0
  189. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/pr-317-repro.yml +0 -0
  190. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/pr-425-backstop/action.yml +0 -0
  191. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/static-env.yml +0 -0
  192. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/template-injection-dynamic-matrix.yml +0 -0
  193. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection/template-injection-static-matrix.yml +0 -0
  194. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/template-injection.yml +0 -0
  195. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/unpinned-uses/action.yml +0 -0
  196. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/unpinned-uses/issue-433-repro.yml +0 -0
  197. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/unpinned-uses.yml +0 -0
  198. {zizmor-1.2.0 → zizmor-1.2.2}/tests/test-data/use-trusted-publishing.yml +0 -0
@@ -33,7 +33,7 @@ jobs:
33
33
 
34
34
  - uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
35
35
 
36
- - uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a # v5.1.0
36
+ - uses: astral-sh/setup-uv@b5f58b2abc5763ade55e4e9d0fe52cd1ff7979ca # v5.2.1
37
37
 
38
38
  - name: Test
39
39
  run: cargo test
@@ -43,9 +43,21 @@ jobs:
43
43
  make snippets
44
44
  git diff --exit-code
45
45
 
46
+ test-site:
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
50
+ with:
51
+ persist-credentials: false
52
+
53
+ - uses: astral-sh/setup-uv@b5f58b2abc5763ade55e4e9d0fe52cd1ff7979ca # v5.2.1
54
+
55
+ - name: Test site
56
+ run: make site
57
+
46
58
  all-tests-pass:
47
59
  if: always()
48
- needs: [lint, test]
60
+ needs: [lint, test, test-site]
49
61
  runs-on: ubuntu-latest
50
62
 
51
63
  steps:
@@ -30,7 +30,7 @@ jobs:
30
30
  persist-credentials: false
31
31
 
32
32
  - name: Install the latest version of uv
33
- uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a # v3
33
+ uses: astral-sh/setup-uv@b5f58b2abc5763ade55e4e9d0fe52cd1ff7979ca # v5.2.1
34
34
 
35
35
  - name: build site
36
36
  run: make site
@@ -6,6 +6,8 @@ on:
6
6
  pull_request:
7
7
  branches: ["*"]
8
8
 
9
+ permissions: {}
10
+
9
11
  jobs:
10
12
  zizmor:
11
13
  name: zizmor latest via Cargo
@@ -19,7 +21,7 @@ jobs:
19
21
  with:
20
22
  persist-credentials: false
21
23
  - name: Install the latest version of uv
22
- uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a # v4
24
+ uses: astral-sh/setup-uv@b5f58b2abc5763ade55e4e9d0fe52cd1ff7979ca # v5.2.1
23
25
  - name: Run zizmor 🌈
24
26
  run: uvx zizmor --format sarif . > results.sarif
25
27
  env:
@@ -616,9 +616,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
616
616
 
617
617
  [[package]]
618
618
  name = "github-actions-models"
619
- version = "0.21.0"
619
+ version = "0.22.0"
620
620
  source = "registry+https://github.com/rust-lang/crates.io-index"
621
- checksum = "768ea1269c648e6eb2e2fc9143e609609a1587150f0ad3342305ae2ae2d217ca"
621
+ checksum = "ea4c30fa8bf11e002d3ca72233e7a7bac33ffce4dc50877d63a8f5a161e0cd84"
622
622
  dependencies = [
623
623
  "indexmap",
624
624
  "serde",
@@ -3108,7 +3108,7 @@ dependencies = [
3108
3108
 
3109
3109
  [[package]]
3110
3110
  name = "zizmor"
3111
- version = "1.2.0"
3111
+ version = "1.2.2"
3112
3112
  dependencies = [
3113
3113
  "annotate-snippets",
3114
3114
  "anstream",
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "zizmor"
3
3
  description = "Static analysis for GitHub Actions"
4
- version = "1.2.0"
4
+ version = "1.2.2"
5
5
  edition = "2021"
6
6
  repository = "https://github.com/woodruffw/zizmor"
7
7
  homepage = "https://github.com/woodruffw/zizmor"
@@ -23,7 +23,7 @@ clap-verbosity-flag = { version = "3.0.2", features = [
23
23
  ], default-features = false }
24
24
  etcetera = "0.8.0"
25
25
  flate2 = "1.0.35"
26
- github-actions-models = "0.21.0"
26
+ github-actions-models = "0.22.0"
27
27
  http-cache-reqwest = "0.15.0"
28
28
  human-panic = "2.0.1"
29
29
  indexmap = "2.7.0"
@@ -3,12 +3,12 @@ all:
3
3
  @echo "Run my targets individually!"
4
4
 
5
5
  .PHONY: site
6
- site: site-requirements.txt
7
- uvx --with-requirements $< mkdocs build
6
+ site:
7
+ uv run --only-group docs mkdocs build
8
8
 
9
9
  .PHONY: site-live
10
- site-live: site-requirements.txt
11
- uvx --with-requirements $< mkdocs serve
10
+ site-live:
11
+ uv run --only-group docs mkdocs serve
12
12
 
13
13
  .PHONY: snippets
14
14
  snippets: trophies sponsors
@@ -1,12 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zizmor
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
+ License-File: LICENSE
4
5
  Summary: Static analysis for GitHub Actions
5
6
  Keywords: cli,github-actions,static-analysis,security
6
7
  Home-Page: https://github.com/woodruffw/zizmor
7
8
  Author: William Woodruff <william@yossarian.net>
8
9
  Author-email: William Woodruff <william@yossarian.net>
9
10
  License: MIT
11
+ Requires-Python: >=3.9
10
12
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
11
13
  Project-URL: Source Code, https://github.com/woodruffw/zizmor
12
14
 
@@ -11,6 +11,27 @@ of `zizmor`.
11
11
 
12
12
  Nothing to see here (yet!)
13
13
 
14
+ ## v1.2.2
15
+
16
+ ### Bug Fixes 🐛
17
+
18
+ * The [excessive-permissions] audit is now more precise about both
19
+ reusable workflows and reusable workflow calls (#473)
20
+
21
+ ### Improvements 🌱
22
+
23
+ * Fetch failures when running `zizmor org/repo` are now more informative (#475)
24
+
25
+ ## v1.2.1
26
+
27
+ This is a small corrective release for some SARIF behavior that
28
+ changed with v1.2.0.
29
+
30
+ ### Bug Fixes 🐛
31
+
32
+ * SARIF outputs now use relative paths again, but more correctly
33
+ than before [v1.2.0](#v120) (#469)
34
+
14
35
  ## v1.2.0
15
36
 
16
37
  This release comes with one new audit ([bot-conditions]), plus a handful
@@ -457,7 +457,7 @@ To do so, add the following to your `.pre-commit-config.yaml` `repos` section:
457
457
 
458
458
  ```yaml
459
459
  - repo: https://github.com/woodruffw/zizmor-pre-commit
460
- rev: v1.2.0 # (1)!
460
+ rev: v1.2.2 # (1)!
461
461
  hooks:
462
462
  - id: zizmor
463
463
  ```
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ # NOTE: This section is a stub; needed to prevent
6
+ # `uv run --only-group docs` from failing.
7
+ [project]
8
+ name = "zizmor"
9
+ dynamic = ["version"]
10
+ # Arbitrarily set to the oldest non-EOL Python.
11
+ requires-python = ">=3.9"
12
+
13
+ [tool.maturin]
14
+ bindings = "bin"
15
+
16
+ [dependency-groups]
17
+ docs = ["mkdocs ~= 1.6", "mkdocs-material[imaging] ~= 9.5"]
@@ -1,13 +1,12 @@
1
1
  use github_actions_models::common::{expr::ExplicitExpr, If};
2
2
 
3
+ use super::{audit_meta, Audit};
3
4
  use crate::{
4
5
  expr::{self, Expr},
5
6
  finding::{Confidence, Severity},
6
7
  models::JobExt,
7
8
  };
8
9
 
9
- use super::{audit_meta, Audit};
10
-
11
10
  pub(crate) struct BotConditions;
12
11
 
13
12
  audit_meta!(BotConditions, "bot-conditions", "spoofable bot actor check");
@@ -152,7 +151,7 @@ impl BotConditions {
152
151
  // We have a bot condition but it doesn't dominate the expression.
153
152
  (true, false) => Some(Confidence::Medium),
154
153
  // No bot condition.
155
- (_, _) => None,
154
+ (..) => None,
156
155
  }
157
156
  }
158
157
  }
@@ -57,12 +57,9 @@ impl Audit for ExcessivePermissions {
57
57
 
58
58
  let all_jobs_have_permissions = workflow
59
59
  .jobs()
60
- .filter_map(|job| {
61
- let Job::NormalJob(job) = job else {
62
- return None;
63
- };
64
-
65
- Some(&job.permissions)
60
+ .map(|job| match job {
61
+ Job::NormalJob(job) => &job.permissions,
62
+ Job::ReusableWorkflowCallJob(job) => &job.permissions,
66
63
  })
67
64
  .all(|perm| !matches!(perm, Permissions::Base(BasePermission::Default)));
68
65
 
@@ -71,17 +68,21 @@ impl Audit for ExcessivePermissions {
71
68
  Permissions::Base(BasePermission::Default)
72
69
  );
73
70
 
74
- // Top-level permissions are a minor issue if there's only one
75
- // job in the workflow, since they're equivalent to job-level
76
- // permissions in that case. Emit only pedantic findings in
77
- // that case.
78
- // Similarly, if all jobs in the workflow have their own explicit
79
- // permissions, then any permissions set at the top-level are moot.
80
- let persona = if workflow.jobs.len() == 1 || all_jobs_have_permissions {
81
- Persona::Pedantic
82
- } else {
83
- Persona::Regular
84
- };
71
+ let workflow_is_reusable_only =
72
+ workflow.has_workflow_call() && workflow.has_single_trigger();
73
+
74
+ // Top-level permissions are a pedantic finding under the following
75
+ // conditions:
76
+ //
77
+ // 1. The workflow has only one job.
78
+ // 2. All jobs in the workflow have their own explicit permissions.
79
+ // 3. The workflow is reusable and has only one trigger.
80
+ let workflow_finding_persona =
81
+ if workflow.jobs.len() == 1 || all_jobs_have_permissions || workflow_is_reusable_only {
82
+ Persona::Pedantic
83
+ } else {
84
+ Persona::Regular
85
+ };
85
86
 
86
87
  // Handle top-level permissions.
87
88
  let location = workflow.location().primary();
@@ -93,20 +94,35 @@ impl Audit for ExcessivePermissions {
93
94
  Self::finding()
94
95
  .severity(severity)
95
96
  .confidence(confidence)
96
- .persona(persona)
97
+ .persona(workflow_finding_persona)
97
98
  .add_location(perm_location)
98
99
  .build(workflow)?,
99
100
  );
100
101
  }
101
102
 
102
103
  for job in workflow.jobs() {
103
- let Job::NormalJob(job) = &job else {
104
- continue;
104
+ let (permissions, job_location, job_finding_persona) = match job {
105
+ Job::NormalJob(job) => {
106
+ // For normal jobs: if the workflow is reusable-only, we
107
+ // emit pedantic findings.
108
+ let persona = if workflow_is_reusable_only {
109
+ Persona::Pedantic
110
+ } else {
111
+ Persona::Regular
112
+ };
113
+
114
+ (&job.permissions, job.location(), persona)
115
+ }
116
+ Job::ReusableWorkflowCallJob(job) => {
117
+ // For reusable jobs: the caller is always responsible for
118
+ // permissions, so we emit regular findings even if
119
+ // the workflow is reusable-only.
120
+ (&job.permissions, job.location(), Persona::Regular)
121
+ }
105
122
  };
106
123
 
107
- let job_location = job.location();
108
124
  if let Some((severity, confidence, perm_location)) = self.check_job_permissions(
109
- &job.permissions,
125
+ permissions,
110
126
  explicit_parent_permissions,
111
127
  job_location.clone(),
112
128
  ) {
@@ -114,6 +130,7 @@ impl Audit for ExcessivePermissions {
114
130
  Self::finding()
115
131
  .severity(severity)
116
132
  .confidence(confidence)
133
+ .persona(job_finding_persona)
117
134
  .add_location(job_location)
118
135
  .add_location(perm_location.primary())
119
136
  .build(workflow)?,
@@ -12,6 +12,7 @@ use github_actions_models::common::RepositoryUses;
12
12
  use http_cache_reqwest::{
13
13
  CACacheManager, Cache, CacheMode, CacheOptions, HttpCache, HttpCacheOptions,
14
14
  };
15
+ use owo_colors::OwoColorize;
15
16
  use reqwest::{
16
17
  header::{HeaderMap, ACCEPT, AUTHORIZATION, USER_AGENT},
17
18
  StatusCode,
@@ -399,7 +400,16 @@ impl Client {
399
400
  // TODO: Could probably make this slightly faster by
400
401
  // streaming asynchronously into the decompression,
401
402
  // probably with the async-compression crate.
402
- let contents = self.http.get(url).send().await?.bytes().await?;
403
+ let resp = self.http.get(&url).send().await?;
404
+
405
+ if !resp.status().is_success() {
406
+ return Err(anyhow!(
407
+ "failed to fetch {url}: {status}",
408
+ status = resp.status().red()
409
+ ));
410
+ }
411
+
412
+ let contents = resp.bytes().await?;
403
413
  let tar = GzDecoder::new(contents.deref());
404
414
 
405
415
  let mut archive = Archive::new(tar);
@@ -160,17 +160,18 @@ fn tip(err: impl AsRef<str>, tip: impl AsRef<str>) -> String {
160
160
 
161
161
  #[instrument(skip(mode, registry))]
162
162
  fn collect_from_repo_dir(
163
- repo_dir: &Utf8Path,
163
+ top_dir: &Utf8Path,
164
+ current_dir: &Utf8Path,
164
165
  mode: &CollectionMode,
165
166
  registry: &mut InputRegistry,
166
167
  ) -> Result<()> {
167
168
  // The workflow directory might not exist if we're collecting from
168
169
  // a repository that only contains actions.
169
170
  if mode.workflows() {
170
- let workflow_dir = if repo_dir.ends_with(".github/workflows") {
171
- repo_dir.into()
171
+ let workflow_dir = if current_dir.ends_with(".github/workflows") {
172
+ current_dir.into()
172
173
  } else {
173
- repo_dir.join(".github/workflows")
174
+ current_dir.join(".github/workflows")
174
175
  };
175
176
 
176
177
  if workflow_dir.is_dir() {
@@ -180,7 +181,7 @@ fn collect_from_repo_dir(
180
181
  match input_path.extension() {
181
182
  Some(ext) if ext == "yml" || ext == "yaml" => {
182
183
  registry
183
- .register_by_path(input_path)
184
+ .register_by_path(input_path, Some(top_dir))
184
185
  .with_context(|| format!("failed to register input: {input_path}"))?;
185
186
  }
186
187
  _ => continue,
@@ -192,18 +193,18 @@ fn collect_from_repo_dir(
192
193
  }
193
194
 
194
195
  if mode.actions() {
195
- for entry in repo_dir.read_dir_utf8()? {
196
+ for entry in current_dir.read_dir_utf8()? {
196
197
  let entry = entry?;
197
198
  let entry_path = entry.path();
198
199
 
199
200
  if entry_path.is_file()
200
201
  && matches!(entry_path.file_name(), Some("action.yml" | "action.yaml"))
201
202
  {
202
- let action = Action::from_file(entry_path)?;
203
+ let action = Action::from_file(entry_path, Some(top_dir))?;
203
204
  registry.register_input(action.into())?;
204
205
  } else if entry_path.is_dir() && !entry_path.ends_with(".github/workflows") {
205
206
  // Recurse and limit the collection mode to only actions.
206
- collect_from_repo_dir(entry_path, &CollectionMode::ActionsOnly, registry)?;
207
+ collect_from_repo_dir(top_dir, entry_path, &CollectionMode::ActionsOnly, registry)?;
207
208
  }
208
209
  }
209
210
  }
@@ -257,7 +258,16 @@ fn collect_from_repo_slug(
257
258
  registry.register_input(workflow.into())?;
258
259
  }
259
260
  } else {
260
- let inputs = client.fetch_audit_inputs(&slug)?;
261
+ let inputs = client.fetch_audit_inputs(&slug).with_context(|| {
262
+ tip(
263
+ format!(
264
+ "couldn't collect inputs from https://github.com/{owner}/{repo}",
265
+ owner = slug.owner,
266
+ repo = slug.repo
267
+ ),
268
+ "confirm the repository exists and that you have access to it",
269
+ )
270
+ })?;
261
271
 
262
272
  tracing::info!(
263
273
  "collected {len} inputs from {owner}/{repo}",
@@ -266,7 +276,7 @@ fn collect_from_repo_slug(
266
276
  repo = slug.repo
267
277
  );
268
278
 
269
- for input in client.fetch_audit_inputs(&slug)? {
279
+ for input in inputs {
270
280
  registry.register_input(input)?;
271
281
  }
272
282
  }
@@ -285,13 +295,13 @@ fn collect_inputs(
285
295
  for input in inputs {
286
296
  let input_path = Utf8Path::new(input);
287
297
  if input_path.is_file() {
298
+ // When collecting individual files, we don't know which part
299
+ // of the input path is the prefix.
288
300
  registry
289
- .register_by_path(input_path)
301
+ .register_by_path(input_path, None)
290
302
  .with_context(|| format!("failed to register input: {input_path}"))?;
291
303
  } else if input_path.is_dir() {
292
- // TODO: walk directory to discover composite actions.
293
- let absolute = input_path.canonicalize_utf8()?;
294
- collect_from_repo_dir(&absolute, mode, &mut registry)?;
304
+ collect_from_repo_dir(input_path, input_path, mode, &mut registry)?;
295
305
  } else {
296
306
  // If this input isn't a file or directory, it's probably an
297
307
  // `owner/repo(@ref)?` slug.
@@ -387,7 +397,10 @@ fn run() -> Result<ExitCode> {
387
397
  })?);
388
398
  Span::current().pb_inc(1);
389
399
  }
390
- tracing::info!("🌈 completed {input}", input = input.key().path());
400
+ tracing::info!(
401
+ "🌈 completed {input}",
402
+ input = input.key().best_effort_relative_path()
403
+ );
391
404
  }
392
405
  }
393
406
 
@@ -101,7 +101,7 @@ impl Workflow {
101
101
  InputKey::Local(_) => None,
102
102
  InputKey::Remote(_) => {
103
103
  // NOTE: InputKey's Display produces a URL, hence `key.to_string()`.
104
- Some(Link::new(key.path(), &key.to_string()).to_string())
104
+ Some(Link::new(key.best_effort_relative_path(), &key.to_string()).to_string())
105
105
  }
106
106
  };
107
107
 
@@ -114,11 +114,9 @@ impl Workflow {
114
114
  }
115
115
 
116
116
  /// Load a workflow from the given file on disk.
117
- pub(crate) fn from_file<P: AsRef<Utf8Path>>(p: P) -> Result<Self> {
118
- let contents = std::fs::read_to_string(p.as_ref())?;
119
- let path = p.as_ref().canonicalize_utf8()?;
120
-
121
- Self::from_string(contents, InputKey::local(path)?)
117
+ pub(crate) fn from_file<P: AsRef<Utf8Path>>(path: P, prefix: Option<P>) -> Result<Self> {
118
+ let contents = std::fs::read_to_string(path.as_ref())?;
119
+ Self::from_string(contents, InputKey::local(path, prefix)?)
122
120
  }
123
121
 
124
122
  /// This workflow's [`SymbolicLocation`].
@@ -137,7 +135,7 @@ impl Workflow {
137
135
  Jobs::new(self)
138
136
  }
139
137
 
140
- /// Whether this workflow's is triggered by pull_request_target.
138
+ /// Whether this workflow is triggered by pull_request_target.
141
139
  pub(crate) fn has_pull_request_target(&self) -> bool {
142
140
  match &self.on {
143
141
  Trigger::BareEvent(event) => *event == BareEvent::PullRequestTarget,
@@ -146,7 +144,7 @@ impl Workflow {
146
144
  }
147
145
  }
148
146
 
149
- /// Whether this workflow's is triggered by workflow_run.
147
+ /// Whether this workflow is triggered by workflow_run.
150
148
  pub(crate) fn has_workflow_run(&self) -> bool {
151
149
  match &self.on {
152
150
  Trigger::BareEvent(event) => *event == BareEvent::WorkflowRun,
@@ -154,6 +152,24 @@ impl Workflow {
154
152
  Trigger::Events(events) => !matches!(events.workflow_run, OptionalBody::Missing),
155
153
  }
156
154
  }
155
+
156
+ /// Whether this workflow is triggered by workflow_call.
157
+ pub(crate) fn has_workflow_call(&self) -> bool {
158
+ match &self.on {
159
+ Trigger::BareEvent(event) => *event == BareEvent::WorkflowCall,
160
+ Trigger::BareEvents(events) => events.contains(&BareEvent::WorkflowCall),
161
+ Trigger::Events(events) => !matches!(events.workflow_call, OptionalBody::Missing),
162
+ }
163
+ }
164
+
165
+ /// Whether this workflow is triggered by exactly one event.
166
+ pub(crate) fn has_single_trigger(&self) -> bool {
167
+ match &self.on {
168
+ Trigger::BareEvent(_) => true,
169
+ Trigger::BareEvents(events) => events.len() == 1,
170
+ Trigger::Events(events) => events.count() == 1,
171
+ }
172
+ }
157
173
  }
158
174
 
159
175
  /// Common behavior across both normal and reusable jobs.
@@ -716,13 +732,10 @@ impl Debug for Action {
716
732
 
717
733
  impl Action {
718
734
  /// Load an action from the given file on disk.
719
- pub(crate) fn from_file<P: AsRef<Utf8Path>>(p: P) -> Result<Self> {
735
+ pub(crate) fn from_file<P: AsRef<Utf8Path>>(path: P, prefix: Option<P>) -> Result<Self> {
720
736
  let contents =
721
- std::fs::read_to_string(p.as_ref()).with_context(|| "couldn't read action file")?;
722
-
723
- let path = p.as_ref().canonicalize_utf8()?;
724
-
725
- Self::from_string(contents, InputKey::local(path)?)
737
+ std::fs::read_to_string(path.as_ref()).with_context(|| "couldn't read action file")?;
738
+ Self::from_string(contents, InputKey::local(path, prefix)?)
726
739
  }
727
740
 
728
741
  /// Load a workflow from a buffer, with an assigned name.
@@ -736,7 +749,7 @@ impl Action {
736
749
  InputKey::Local(_) => None,
737
750
  InputKey::Remote(_) => {
738
751
  // NOTE: InputKey's Display produces a URL, hence `key.to_string()`.
739
- Some(Link::new(key.path(), &key.to_string()).to_string())
752
+ Some(Link::new(key.best_effort_relative_path(), &key.to_string()).to_string())
740
753
  }
741
754
  };
742
755