diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e83b02a..cd266a9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,8 +8,7 @@ on: - "!v0.6.0" permissions: - contents: write - id-token: write + contents: read jobs: test: @@ -19,10 +18,15 @@ jobs: needs: test runs-on: ubuntu-latest environment: pypi + permissions: + contents: read # checkout + id-token: write # PyPI trusted publishing only steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" @@ -33,15 +37,20 @@ jobs: run: python -m build - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 github-release: needs: publish runs-on: ubuntu-latest + permissions: + contents: write # create the release only steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} - run: gh release create "${{ github.ref_name }}" --generate-notes --title "${{ github.ref_name }}" + TAG: ${{ github.ref_name }} + run: gh release create "$TAG" --generate-notes --title "$TAG" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b75db29..9d9bc68 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,47 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: + secret-scan: + name: Secrets / gitleaks + runs-on: ubuntu-latest + env: + GITLEAKS_VERSION: "8.30.1" + GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb" + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + # Pinned release binary, verified against a checksum kept in this file: + # no third-party action and no token involved. + - name: Install gitleaks + run: | + set -euo pipefail + tarball="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + curl -fsSL -o "$RUNNER_TEMP/$tarball" \ + "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/$tarball" + echo "${GITLEAKS_SHA256} $RUNNER_TEMP/$tarball" | sha256sum -c - + tar -xzf "$RUNNER_TEMP/$tarball" -C "$RUNNER_TEMP" gitleaks + + # Pull requests scan only their own commits; pushes scan full history. + # --redact keeps any finding out of the public log. + - name: Scan commits + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + set -euo pipefail + if [ -n "$BASE_SHA" ]; then + "$RUNNER_TEMP/gitleaks" git --redact --no-banner --log-opts="$BASE_SHA..HEAD" . + else + "$RUNNER_TEMP/gitleaks" git --redact --no-banner . + fi + portable-test: name: Portable / Python ${{ matrix.python-version }} runs-on: ubuntu-latest @@ -17,10 +57,12 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} @@ -41,10 +83,12 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python 3.12 - uses: actions/setup-python@v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" @@ -68,10 +112,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python 3.12 - uses: actions/setup-python@v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" @@ -119,7 +165,9 @@ jobs: runs-on: macos-15 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Select supported Homebrew Python shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index b154551..269033a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ Format: [Keep a Changelog](https://keepachangelog.com/). Versioning: [SemVer](ht - Runs the suite on Windows and on Linux Python 3.13/3.14, and smoke-tests the built wheel's console scripts and bundled data from a clean install (#30). +- Hardens the workflows: read-only `GITHUB_TOKEN` by default, write scopes only + in the publish and release jobs that need them, every action pinned to a full + commit SHA (`checkout` v7.0.1, `setup-python` v7.0.0, `pypi-publish` v1.14.2), + no persisted checkout credentials, and the release tag passed via env instead + of template expansion. Adds a `gitleaks` secret-scan job (checksum-verified + binary, redacted output) and documents an optional pre-commit hook. ## [0.6.0] — 2026-09-20 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e71003f..e0ab14b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,22 @@ These are guidelines, not gates. Don't let style concerns stop you from submitti 2. Return `{"name", "score", "zone", "detail", "hint"}` 3. Import and add to `DIMS` list in `commands/health.py` +## Secret Scanning (optional local hook) + +CI runs [gitleaks](https://github.com/gitleaks/gitleaks) on every pull request, +and GitHub push protection blocks known secrets at push time. To catch a key +before it ever enters your local history, add a pre-commit hook: + +```bash +cat > .git/hooks/pre-commit <<'EOF' +#!/bin/sh +exec gitleaks git --pre-commit --staged --redact --no-banner +EOF +chmod +x .git/hooks/pre-commit +``` + +Requires `gitleaks` on your PATH (`brew install gitleaks`, or a release binary). + ## PR Checklist Before submitting: