mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
ci: Add poutine custom rule for unpinned GitHub Actions detection (#24577)
This commit is contained in:
parent
2b4596eb66
commit
924817d9fd
9 changed files with 65 additions and 8 deletions
43
.github/poutine-rules/unpinned_action.rego
vendored
Normal file
43
.github/poutine-rules/unpinned_action.rego
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# METADATA
|
||||||
|
# title: Unpinned GitHub Action
|
||||||
|
# description: |-
|
||||||
|
# GitHub Action not pinned to full commit SHA.
|
||||||
|
# Pin actions to SHA for supply chain security.
|
||||||
|
# custom:
|
||||||
|
# level: error
|
||||||
|
package rules.unpinned_action
|
||||||
|
|
||||||
|
import data.poutine
|
||||||
|
import rego.v1
|
||||||
|
|
||||||
|
rule := poutine.rule(rego.metadata.chain())
|
||||||
|
|
||||||
|
# Match 40-character hex SHA (Git) or 64-character sha256 digest (Docker)
|
||||||
|
is_sha_pinned(uses) if {
|
||||||
|
regex.match(`@(sha256:[a-f0-9]{64}|[a-f0-9]{40})`, uses)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if it's a local action (starts with ./)
|
||||||
|
is_local_action(uses) if {
|
||||||
|
startswith(uses, "./")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if it's a reusable workflow call
|
||||||
|
is_reusable_workflow(uses) if {
|
||||||
|
contains(uses, ".github/workflows/")
|
||||||
|
}
|
||||||
|
|
||||||
|
results contains poutine.finding(rule, pkg.purl, {
|
||||||
|
"path": workflow.path,
|
||||||
|
"job": job.id,
|
||||||
|
"step": i,
|
||||||
|
"details": sprintf("Action '%s' should be pinned to a full commit SHA", [step.uses]),
|
||||||
|
}) if {
|
||||||
|
pkg := input.packages[_]
|
||||||
|
workflow := pkg.github_actions_workflows[_]
|
||||||
|
job := workflow.jobs[_]
|
||||||
|
step := job.steps[i]
|
||||||
|
step.uses
|
||||||
|
not is_sha_pinned(step.uses)
|
||||||
|
not is_local_action(step.uses)
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Validate user permissions and collect PR data
|
- name: Validate user permissions and collect PR data
|
||||||
id: check_permissions
|
id: check_permissions
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|
|
||||||
4
.github/workflows/ci-manual-unit-tests.yml
vendored
4
.github/workflows/ci-manual-unit-tests.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Create pending check run on PR
|
- name: Create pending check run on PR
|
||||||
id: create
|
id: create
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|
@ -88,7 +88,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Update check run on PR (if triggered from PR comment)
|
- name: Update check run on PR (if triggered from PR comment)
|
||||||
if: inputs.pr_number != ''
|
if: inputs.pr_number != ''
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Validate inputs
|
- name: Validate inputs
|
||||||
|
|
|
||||||
10
.github/workflows/sec-poutine-reusable.yml
vendored
10
.github/workflows/sec-poutine-reusable.yml
vendored
|
|
@ -27,6 +27,16 @@ jobs:
|
||||||
- name: Run Poutine Security Scanner
|
- name: Run Poutine Security Scanner
|
||||||
uses: boostsecurityio/poutine-action@84c0a0d32e8d57ae12651222be1eb15351429228 # v0.15.2
|
uses: boostsecurityio/poutine-action@84c0a0d32e8d57ae12651222be1eb15351429228 # v0.15.2
|
||||||
|
|
||||||
|
- name: Fail on error-level findings
|
||||||
|
run: |
|
||||||
|
# Check SARIF for error-level findings
|
||||||
|
if jq -e '.runs[].results[] | select(.level == "error")' results.sarif > /dev/null 2>&1; then
|
||||||
|
echo "::error::Poutine found error-level security findings:"
|
||||||
|
jq -r '.runs[].results[] | select(.level == "error") | " - \(.ruleId): \(.message.text)"' results.sarif
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "No error-level findings detected"
|
||||||
|
|
||||||
- name: Upload SARIF results
|
- name: Upload SARIF results
|
||||||
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
||||||
if: always()
|
if: always()
|
||||||
|
|
|
||||||
2
.github/workflows/test-visual-chromatic.yml
vendored
2
.github/workflows/test-visual-chromatic.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
runs-on: blacksmith-2vcpu-ubuntu-2204
|
runs-on: blacksmith-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Determine changed files
|
- name: Determine changed files
|
||||||
uses: tomi/paths-filter-action@v3.0.2
|
uses: tomi/paths-filter-action@32c62f5ca100c1110406e3477d5b3ecef4666fec # v3.0.2
|
||||||
id: changed
|
id: changed
|
||||||
if: github.event_name == 'pull_request_review'
|
if: github.event_name == 'pull_request_review'
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Validate User, Get PR Details, and React
|
- name: Validate User, Get PR Details, and React
|
||||||
id: pr_check_and_details
|
id: pr_check_and_details
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|
|
||||||
4
.github/workflows/util-claude.yml
vendored
4
.github/workflows/util-claude.yml
vendored
|
|
@ -25,12 +25,12 @@ jobs:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run Claude PR Action
|
- name: Run Claude PR Action
|
||||||
uses: anthropics/claude-code-action@beta
|
uses: anthropics/claude-code-action@de8e0b9c42c6cb58e904c857f164aa072244c1ac # beta
|
||||||
with:
|
with:
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
# Or use OAuth token instead:
|
# Or use OAuth token instead:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
# This file defines skip rules for known-safe patterns.
|
# This file defines skip rules for known-safe patterns.
|
||||||
# Add new entries only after security review.
|
# Add new entries only after security review.
|
||||||
|
|
||||||
|
# Custom rules for additional security checks
|
||||||
|
include:
|
||||||
|
- path: .github/poutine-rules
|
||||||
|
|
||||||
skip:
|
skip:
|
||||||
# === SELF-HOSTED RUNNERS ===
|
# === SELF-HOSTED RUNNERS ===
|
||||||
# We use Blacksmith (trusted CI provider) for self-hosted runners.
|
# We use Blacksmith (trusted CI provider) for self-hosted runners.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue