- pr.yml: Conventional Commit title, DCO, dependency review - security.yml: gitleaks (full history), Trivy + OSV-Scanner (malicious packages), zizmor + actionlint; all pinned by SHA or digest - codeql.yml: security-extended with a high-severity gate - this repo's CI, Scorecard, Dependabot, CODEOWNERS - templates for repository files and caller workflows - tools/Test-RepoConformance.ps1: checks K22-REPO-* and K22-CI-* via the GitHub API and a local clone, honoring the exception register Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: James Maes <james@kof22.com>
182 lines
7.4 KiB
YAML
182 lines
7.4 KiB
YAML
# Security scanning (K22-CI-20, K22-CI-21, K22-CI-23): secrets over full history,
|
|
# software composition analysis of the lock files (Trivy for CVEs, OSV-Scanner for
|
|
# malicious packages), and workflow linting. Called as:
|
|
#
|
|
# jobs:
|
|
# security:
|
|
# uses: KofTwentyTwo/standards/.github/workflows/security.yml@<sha> # vX.Y.Z
|
|
# permissions:
|
|
# contents: read
|
|
# security-events: write
|
|
#
|
|
# producing the required contexts `security / secrets`, `security / sca`, and
|
|
# `security / workflows`. Call it on pull_request, on push to main, and on a weekly
|
|
# schedule so new advisories are caught between changes.
|
|
#
|
|
# Scanners run as digest-pinned containers (K22-CI-11) rather than third-party
|
|
# actions. Repository-specific configuration is picked up from the repository root:
|
|
# .gitleaks.toml, trivy.yaml / .trivyignore, and zizmor.yml / .github/zizmor.yml.
|
|
name: security
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
trivy-skip-dirs:
|
|
description: Comma-separated directories the SCA scan skips (for example test fixtures with deliberately vulnerable manifests).
|
|
type: string
|
|
default: ""
|
|
sca-severity:
|
|
description: Severities that fail the SCA gate.
|
|
type: string
|
|
default: HIGH,CRITICAL
|
|
|
|
permissions: {}
|
|
|
|
jobs:
|
|
secrets:
|
|
name: secrets
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Checkout (full history)
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
# The container runs as root over a workspace owned by the runner user; git
|
|
# refuses such repositories unless they are marked safe, which the
|
|
# GIT_CONFIG_* variables do without a writable global config.
|
|
- name: Gitleaks (every commit)
|
|
uses: docker://ghcr.io/gitleaks/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f
|
|
env:
|
|
GIT_CONFIG_COUNT: "1"
|
|
GIT_CONFIG_KEY_0: safe.directory
|
|
GIT_CONFIG_VALUE_0: "*"
|
|
with:
|
|
args: git /github/workspace --redact --verbose --exit-code 1 --no-banner
|
|
|
|
sca:
|
|
name: sca
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: read
|
|
security-events: write # upload SARIF results to code scanning
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
# SARIF for code scanning carries MEDIUM and above for visibility; the gate
|
|
# below fails only on the configured severities (HIGH and CRITICAL by default).
|
|
- name: Trivy report (SARIF)
|
|
uses: docker://ghcr.io/aquasecurity/trivy:0.75.0@sha256:af6acf9a6b85dfe389a1941505c0ce9efef52a4719635e1a962f022a3d855daa
|
|
with:
|
|
args: >-
|
|
fs --scanners vuln --severity MEDIUM,HIGH,CRITICAL
|
|
--cache-dir /github/workspace/.trivycache
|
|
--skip-dirs .trivycache${{ inputs.trivy-skip-dirs != '' && format(',{0}', inputs.trivy-skip-dirs) || '' }}
|
|
--format sarif --output /github/workspace/trivy.sarif --exit-code 0
|
|
/github/workspace
|
|
|
|
- name: Upload SARIF to code scanning
|
|
# Fork pull requests get a read-only token, so only same-repository runs upload.
|
|
if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
|
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
|
with:
|
|
sarif_file: trivy.sarif
|
|
category: trivy-sca
|
|
|
|
# Malicious packages (OpenSSF malicious-packages feed, OSV `MAL-` IDs) are a
|
|
# different threat from CVEs and are not in Trivy's database, so OSV-Scanner
|
|
# checks the same lock files for them. Any MAL- finding fails the job.
|
|
- name: OSV-Scanner (malicious packages)
|
|
env:
|
|
OSV_IMAGE: ghcr.io/google/osv-scanner:v2.6.0@sha256:afd838850ac1a0fcc15ff4a041dc9ba11123c3f0d2666217a5f0fcf9222b55fa
|
|
run: |
|
|
set -euo pipefail
|
|
status=0
|
|
docker run --rm -v "$PWD:/src:ro" "$OSV_IMAGE" scan source --recursive --format json /src > osv.json || status=$?
|
|
# 0: nothing found, 1: vulnerabilities found, 128: no packages found.
|
|
case "$status" in
|
|
0|1|128) ;;
|
|
*) echo "::error title=OSV-Scanner failed::exit code $status"; exit 1 ;;
|
|
esac
|
|
malicious=$(jq -r '
|
|
[ .results[]?.packages[]?
|
|
| .package as $p
|
|
| .vulnerabilities[]?
|
|
| select((.id | startswith("MAL-")) or ((.aliases // []) | any(startswith("MAL-"))))
|
|
| "\(.id) \($p.ecosystem)/\($p.name)@\($p.version)" ]
|
|
| unique | .[]' osv.json)
|
|
if [[ -n "$malicious" ]]; then
|
|
while IFS= read -r finding; do
|
|
echo "::error title=Malicious package::$finding"
|
|
done <<< "$malicious"
|
|
exit 1
|
|
fi
|
|
echo "No dependency is flagged as malicious."
|
|
|
|
- name: Gate on vulnerable dependencies
|
|
uses: docker://ghcr.io/aquasecurity/trivy:0.75.0@sha256:af6acf9a6b85dfe389a1941505c0ce9efef52a4719635e1a962f022a3d855daa
|
|
with:
|
|
args: >-
|
|
fs --scanners vuln --severity ${{ inputs.sca-severity }}
|
|
--cache-dir /github/workspace/.trivycache --skip-db-update
|
|
--skip-dirs .trivycache${{ inputs.trivy-skip-dirs != '' && format(',{0}', inputs.trivy-skip-dirs) || '' }}
|
|
--format table --exit-code 1
|
|
/github/workspace
|
|
|
|
workflows:
|
|
name: workflows
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
security-events: write # upload SARIF results to code scanning
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up uv
|
|
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
|
with:
|
|
enable-cache: false
|
|
|
|
- name: zizmor (SARIF)
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: uvx zizmor@1.30.1 --format sarif --min-severity low . > zizmor.sarif || true
|
|
|
|
- name: Upload SARIF to code scanning
|
|
if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
|
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
|
with:
|
|
sarif_file: zizmor.sarif
|
|
category: zizmor
|
|
|
|
# GH_TOKEN enables zizmor's online audits (impostor commits, known-vulnerable
|
|
# actions, ref confusion); the read-only job token is enough.
|
|
- name: zizmor gate (medium and above)
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: uvx zizmor@1.30.1 --min-severity medium .
|
|
|
|
- name: actionlint
|
|
env:
|
|
ACTIONLINT_VERSION: 1.7.12
|
|
ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
|
|
run: |
|
|
set -euo pipefail
|
|
archive="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
|
|
curl -fsSLO "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/${archive}"
|
|
echo "${ACTIONLINT_SHA256} ${archive}" | sha256sum --check --strict
|
|
tar -xzf "${archive}" actionlint
|
|
./actionlint -color
|