Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ for family, grp in itertools.groupby(collected.checks.items(), key=lambda x: x[1
- [`GH103`](https://learn.scientific-python.org/development/guides/gha-basic#GH103): At least one workflow with manual dispatch trigger
- [`GH104`](https://learn.scientific-python.org/development/guides/gha-wheels#GH104): Use unique names for upload-artifact
- [`GH105`](https://learn.scientific-python.org/development/guides/gha-basic#GH105): Use Trusted Publishing instead of token-based publishing on PyPI
- [`GH106`](https://learn.scientific-python.org/development/guides/gha-basic#GH106): Use zizmor to check the GitHub Actions
- [`GH200`](https://learn.scientific-python.org/development/guides/gha-basic#GH200): Maintained by Dependabot
- [`GH210`](https://learn.scientific-python.org/development/guides/gha-basic#GH210): Maintains the GitHub action versions with Dependabot
- [`GH211`](https://learn.scientific-python.org/development/guides/gha-basic#GH211): Do not pin core actions as major versions
Expand Down
21 changes: 21 additions & 0 deletions docs/pages/guides/gha_basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ run a manual check, like check-manifest, then you can keep it but just use
this one check. You can also use `needs: lint` in your other jobs to keep them
from running if the lint check does not pass.

### Linting your workflows

{rr}`GH106` GitHub Actions workflows are a common source of security issues,
such as script injection from untrusted input, overly broad token permissions,
and credentials accidentally persisted by `actions/checkout`.
[zizmor](https://docs.zizmor.sh) is a static analysis tool that audits your
workflows for these problems. The easiest way to run it is as a pre-commit hook:

```yaml
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: "v1.25.2"
hooks:
- id: zizmor
```

You can silence individual findings with `# zizmor: ignore[rule]` comments, or
collect them in a [`zizmor.yml`](https://docs.zizmor.sh/configuration/) config
file. If you'd rather keep it out of pre-commit, zizmor also ships the
[`zizmorcore/zizmor-action`](https://github.com/zizmorcore/zizmor-action)
GitHub Action, which can upload results to GitHub's code scanning dashboard.

### Unit tests

Implementing unit tests is also easy. Since you should be following best
Expand Down
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ def pc_bump(session: nox.Session) -> None:
versions = {}
pages = [
Path("docs/pages/guides/style.md"),
Path("docs/pages/guides/gha_basic.md"),
Path("{{cookiecutter.project_name}}/.pre-commit-config.yaml"),
Path(".pre-commit-config.yaml"),
]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ ignore = [
"helpers/extensions.py" = ["ANN"]

[tool.repo-review.ignore]
RTD103 = "Using Ruby instead of Python for docs"
RTD103 = "Using MystMD instead of Python for docs"

[tool.typos.default.extend-words]
nd = "nd"
Expand Down
39 changes: 39 additions & 0 deletions src/sp_repo_review/checks/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,45 @@ def check(workflows: dict[str, Any]) -> str:
return "\n".join(errors)


class GH106(GitHub):
"Use zizmor to check the GitHub Actions"

requires = {"GH100"}
url = mk_url("gha-basic")

@staticmethod
def check(precommit: dict[str, Any], workflows: dict[str, Any]) -> bool:
"""
Projects with GitHub Actions should statically analyze their workflows
with [zizmor](https://docs.zizmor.sh), which catches common security
issues such as template injection, excessive permissions, and
credential persistence. The simplest way is to add the pre-commit hook:

```yaml
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.25.2
hooks:
- id: zizmor
```

You can also run it as the `zizmorcore/zizmor-action` GitHub Action.
"""
for repo_item in precommit.get("repos", []):
if (
repo_item.get("repo", "").lower()
== "https://github.com/zizmorcore/zizmor-pre-commit"
):
return True
for workflow in workflows.values():
for job in workflow.get("jobs", {}).values():
if not isinstance(job, dict):
continue
for step in job.get("steps", []):
if step.get("uses", "").startswith("zizmorcore/zizmor-action"):
return True
return False


class GH200(GitHub):
"Maintained by Dependabot"

Expand Down
39 changes: 39 additions & 0 deletions tests/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,45 @@ def test_gh105_token_based_upload() -> None:
assert "Token-based publishing" in res.err_msg


def test_gh106_precommit() -> None:
precommit = yaml.safe_load(
"""
repos:
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.22.0
hooks:
- id: zizmor
"""
)
assert compute_check("GH106", precommit=precommit, workflows={"ci": {}}).result


def test_gh106_action() -> None:
workflows = yaml.safe_load(
"""
zizmor:
jobs:
zizmor:
steps:
- uses: zizmorcore/zizmor-action@v0.5.6
"""
)
assert compute_check("GH106", precommit={}, workflows=workflows).result


def test_gh106_missing() -> None:
precommit = yaml.safe_load(
"""
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.16
hooks:
- id: ruff-check
"""
)
assert not compute_check("GH106", precommit=precommit, workflows={"ci": {}}).result


def test_gh200() -> None:
dependabot = yaml.safe_load(
"""
Expand Down
2 changes: 2 additions & 0 deletions {{cookiecutter.project_name}}/.github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
actions:
patterns:
Expand Down
12 changes: 10 additions & 2 deletions {{cookiecutter.project_name}}/.github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
branches:
- main

permissions: {}

concurrency:
group: {% raw %}${{ github.workflow }}-${{ github.ref }}{% endraw %}
cancel-in-progress: true
Expand All @@ -21,10 +23,13 @@ jobs:
lint:
name: Format
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
{%- if cookiecutter.vcs %}
with:
persist-credentials: false
{%- if cookiecutter.vcs %}
fetch-depth: 0
{%- endif %}

Expand All @@ -45,6 +50,8 @@ jobs:
{%- if cookiecutter.__type == "compiled" %}
needs: [lint]
{%- endif %}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
Expand All @@ -57,8 +64,9 @@ jobs:

steps:
- uses: actions/checkout@v6
{%- if cookiecutter.vcs %}
with:
persist-credentials: false
{%- if cookiecutter.vcs %}
fetch-depth: 0
{%- endif %}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
types:
- published

permissions: {}

concurrency:
group: {% raw %}${{ github.workflow }}-${{ github.ref }}{% endraw %}
cancel-in-progress: true
Expand All @@ -23,10 +25,13 @@ jobs:
dist:
name: Distribution build
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0

- uses: hynek/build-and-inspect-python-package@v2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
paths:
- .github/workflows/cd.yml

permissions: {}

concurrency:
group: {% raw %}${{ github.workflow }}-${{ github.ref }}{% endraw %}
cancel-in-progress: true
Expand All @@ -22,9 +24,12 @@ jobs:
make_sdist:
name: Make SDist
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0

- name: Build SDist
Expand All @@ -38,6 +43,8 @@ jobs:
build_wheels:
name: {% raw %}Wheel on ${{ matrix.os }}{% endraw %}
runs-on: {% raw %}${{ matrix.os }}{% endraw %}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
Expand All @@ -52,9 +59,13 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0

- uses: astral-sh/setup-uv@v8.2.0
with:
# Disable caching to avoid poisoning published wheels
enable-cache: false

- uses: pypa/cibuildwheel@v4.0

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Configuration for zizmor (https://docs.zizmor.sh)
rules:
unpinned-uses:
config:
# Feel free to switch to hash pinning, then this can be removed.
policies:
"*": ref-pin
8 changes: 8 additions & 0 deletions {{cookiecutter.project_name}}/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,11 @@ repos:
- id: check-gitlab-ci
{%- endif %}
- id: check-readthedocs

{%- if cookiecutter.__ci == "github" %}

- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: "v1.25.2"
hooks:
- id: zizmor
{%- endif %}
Loading