build(security): add actionlint pre-commit hook for workflow files (#88) #91

Merged
james merged 1 commit from 88-actionlint-prehook into main 2026-06-18 01:29:45 +00:00
Owner

Closes #88. Sister to #89 (CI-side enforcement, still open).

What this does

Adds an actionlint entry to lefthook.yml's pre-commit: block, mirroring the shape of the existing gitleaks hook:

actionlint:
  glob: ".forgejo/workflows/*.yml"
  run: |
    if ! command -v actionlint > /dev/null 2>&1; then
      echo "✗ actionlint not installed."
      exit 1
    fi
    actionlint {staged_files}
  • glob scopes the hook to workflow files only — commits that don't touch .forgejo/workflows/ skip this hook entirely (gitleaks still runs).
  • {staged_files} template limits the lint to just the matching staged files; editing one workflow doesn't re-lint the others.
  • Fails closed if actionlint isn't on PATH, with a brew install actionlint pointer and a reference to README "Requirements".

README updated alongside:

  • New bullet under Requirements for actionlint, same shape as gitleaks.
  • Setup section now lists both hooks and the LEFTHOOK_EXCLUDE=actionlint skip pattern.

Test plan

Verified locally with two scenarios:

Scenario Expected Actual
git add README.md lefthook.yml then lefthook run pre-commit gitleaks runs, actionlint skipped (no workflows staged) ✓ matches
git add .forgejo/workflows/broken.yml (with ${{ github.no_such_context }}) then lefthook run pre-commit actionlint fails with line + column citing the undefined context ✓ fails with property "no_such_context" is not defined in object type {action: string; ...}

lefthook validate reports the config is well-formed.

Coordination with #89

This PR enforces actionlint locally. Contributors without actionlint installed, or using git commit --no-verify, can still push workflow regressions; #89 closes that gap by adding the same check to PR CI. Local hook first because it gives fast feedback without burning CI time; CI second to enforce the contract.

Note: untracked private key in the repo root

Heads up — forgejo-signing-key (private) and forgejo-signing-key.pub are sitting in the project root from the merge-commit-signing setup earlier. Not staged here, but worth shred -u forgejo-signing-key && rm forgejo-signing-key.pub once they're copied to the Forgejo host. Untracked private keys at a repo root are one accidental git add . away from leaking.

Closes #88. Sister to #89 (CI-side enforcement, still open). ## What this does Adds an `actionlint` entry to `lefthook.yml`'s `pre-commit:` block, mirroring the shape of the existing `gitleaks` hook: ```yaml actionlint: glob: ".forgejo/workflows/*.yml" run: | if ! command -v actionlint > /dev/null 2>&1; then echo "✗ actionlint not installed." … exit 1 fi actionlint {staged_files} ``` - `glob` scopes the hook to workflow files only — commits that don't touch `.forgejo/workflows/` skip this hook entirely (gitleaks still runs). - `{staged_files}` template limits the lint to just the matching staged files; editing one workflow doesn't re-lint the others. - Fails closed if `actionlint` isn't on `PATH`, with a `brew install actionlint` pointer and a reference to README "Requirements". README updated alongside: - New bullet under **Requirements** for actionlint, same shape as gitleaks. - **Setup** section now lists both hooks and the `LEFTHOOK_EXCLUDE=actionlint` skip pattern. ## Test plan Verified locally with two scenarios: | Scenario | Expected | Actual | |---|---|---| | `git add README.md lefthook.yml` then `lefthook run pre-commit` | gitleaks runs, actionlint skipped (no workflows staged) | ✓ matches | | `git add .forgejo/workflows/broken.yml` (with `${{ github.no_such_context }}`) then `lefthook run pre-commit` | actionlint fails with line + column citing the undefined context | ✓ fails with `property "no_such_context" is not defined in object type {action: string; ...}` | `lefthook validate` reports the config is well-formed. ## Coordination with #89 This PR enforces actionlint locally. Contributors without `actionlint` installed, or using `git commit --no-verify`, can still push workflow regressions; #89 closes that gap by adding the same check to PR CI. Local hook first because it gives fast feedback without burning CI time; CI second to enforce the contract. ## Note: untracked private key in the repo root Heads up — `forgejo-signing-key` (private) and `forgejo-signing-key.pub` are sitting in the project root from the merge-commit-signing setup earlier. Not staged here, but worth `shred -u forgejo-signing-key && rm forgejo-signing-key.pub` once they're copied to the Forgejo host. Untracked private keys at a repo root are one accidental `git add .` away from leaking.
build(security): add actionlint pre-commit hook for workflow files (#88)
Some checks failed
PR / Static analysis (Semgrep) (pull_request) Successful in 24s
PR / Lint (pull_request) Successful in 2m54s
PR / Typecheck (pull_request) Successful in 2m55s
PR / OSV-Scanner (pull_request) Successful in 19s
PR / npm audit (pull_request) Successful in 58s
Secrets / gitleaks (pull_request) Successful in 19s
PR / Build (pull_request) Successful in 3m17s
PR / Test (sqlite) (pull_request) Successful in 1m9s
PR / Trivy (image) (pull_request) Successful in 1m9s
PR / Test (postgres) (pull_request) Failing after 3m58s
cd08810f53
Mirrors the gitleaks hook's shape:
  - `glob: .forgejo/workflows/*.yml` scopes the hook so it only runs
    when workflow files are staged.
  - `{staged_files}` template limits the lint to just those files.
  - Fails closed if `actionlint` isn't on PATH, with a brew-install
    pointer.

Tested locally: a deliberately broken workflow with `${{ github.no_such_context }}`
fails the hook with a precise schema error citing line + column. A
commit that doesn't touch any workflow file skips the hook entirely
(gitleaks still runs).

README "Requirements" picks up the new dependency alongside gitleaks;
Setup explains both hooks and the `LEFTHOOK_EXCLUDE=actionlint` skip
shape for the rare one-off bypass.

Closes #88. CI-side enforcement tracked separately as #89.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
james merged commit 2b8fed41e2 into main 2026-06-18 01:29:45 +00:00
Sign in to join this conversation.
No description provided.