Skip to content

Commit 8e2f215

Browse files
fix(ci): grant secret-scanner reusable its requested job permissions (#60)
Estate sweep: the reusable's gitleaks job requests `pull-requests: write` + `actions: read` (PR summary comments, .git-private-farm#69). A called workflow cannot exceed the caller's grant, so this wrapper died at `startup_failure` on every run — at any pin. Root cause and canonical caller shape fixed in standards#472; proven green on somethings-fishy#51. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent df32340 commit 8e2f215

33 files changed

Lines changed: 1126 additions & 327 deletions

.github/workflows/README.adoc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
= GitHub Workflows — what runs, and why some look duplicated
3+
4+
This directory holds the repo's CI/CD. A few workflows *look* like they overlap
5+
but are deliberately distinct — documented here so the apparent duplication is
6+
not "tidied away" into a real coverage gap.
7+
8+
== Hypatia runs in two places (on purpose)
9+
10+
* `hypatia-scan.yml` — the **standalone security scan** (push / PR / weekly).
11+
Independent; this is the repo's own Hypatia coverage.
12+
* `static-analysis-gate.yml` -> `hypatia-scan` job — runs Hypatia as part of the
13+
**gate**, then the `deposit-findings` job hands the results to the
14+
**gitbot-fleet learning** pipeline. This job is also a *required status check*.
15+
16+
Same tool, two different consumers (security scan vs fleet learning). Removing
17+
either loses real function — keep both.
18+
19+
== Secret scanning is single-sourced
20+
21+
`secret-scanner.yml` calls the standards reusable (gitleaks + a Rust-secrets
22+
check). An inline TruffleHog job was removed: the reusable deliberately retired
23+
TruffleHog as redundant with gitleaks, so re-adding it was duplicated work, not
24+
extra coverage.
25+
26+
== SAST tools are complementary, not redundant
27+
28+
* `codeql.yml` — CodeQL (matrix defaults to `actions`; every repo has workflows).
29+
* `sonarqube.yml` — SonarCloud (shell / JS surface; see `sonar-project.properties`).
30+
* `static-analysis-gate.yml` — panic-attack + Hypatia gate (see above).
31+
32+
Each targets a different language/surface; together they cover what no single
33+
analyser does.

.github/workflows/boj-build.yml

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
# OPTIONAL: BoJ Server Build Trigger
44
# This workflow notifies a BoJ Server instance when code is pushed.
55
# It is a no-op if BOJ_SERVER_URL is not set or the server is unreachable.
6-
# To enable: set the BOJ_SERVER_URL repository secret (e.g., http://boj-server.local:7700)
7-
# To disable: delete this file or leave the secret unset.
8-
#
6+
# To enable: set BOJ_SERVER_URL as a repository secret or variable.
7+
# To disable: delete this file or leave BOJ_SERVER_URL unset.
98
name: BoJ Server Build Trigger
109
on:
1110
push:
1211
branches: [main, master]
1312
workflow_dispatch:
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
permissions:
17+
contents: read
1418
jobs:
1519
trigger-boj:
1620
runs-on: ubuntu-latest
@@ -22,14 +26,23 @@ jobs:
2226
- name: Trigger BoJ Server (Casket/ssg-mcp)
2327
env:
2428
BOJ_URL: ${{ secrets.BOJ_SERVER_URL || vars.BOJ_SERVER_URL }}
29+
REPO_NAME: ${{ github.repository }}
30+
BRANCH_NAME: ${{ github.ref_name }}
2531
run: |
32+
set -euo pipefail
33+
2634
if [ -z "$BOJ_URL" ]; then
27-
echo "BOJ_SERVER_URL not configured skipping"
35+
echo "BOJ_SERVER_URL not configured - skipping"
2836
exit 0
2937
fi
38+
39+
payload="$(jq -cn \
40+
--arg repo "$REPO_NAME" \
41+
--arg branch "$BRANCH_NAME" \
42+
--arg engine "casket" \
43+
'{repo:$repo, branch:$branch, engine:$engine}')"
44+
3045
curl -sf -X POST "${BOJ_URL}/cartridges/ssg-mcp/invoke" \
3146
-H "Content-Type: application/json" \
32-
-d "{\"repo\": \"${{ github.repository }}\", \"branch\": \"${{ github.ref_name }}\", \"engine\": \"casket\"}" \
33-
|| echo "BoJ server unreachable — skipping (non-fatal)"
34-
permissions:
35-
contents: read
47+
--data "$payload" \
48+
|| echo "BoJ server unreachable - skipping (non-fatal)"

.github/workflows/codeql.yml

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
# SPDX-License-Identifier: PMPL-1.0
1+
# SPDX-License-Identifier: MPL-2.0
22
name: CodeQL Security Analysis
33
on:
44
push:
55
branches: [main, master]
66
pull_request:
77
branches: [main, master]
88
schedule:
9-
- cron: '0 6 1 * *'
10-
# Estate guardrail: cancel superseded runs so re-pushes / rebased PR
11-
# updates do not pile up queued runs against the shared account-wide
12-
# Actions concurrency pool. Applied only to read-only check workflows
13-
# (no publish/mutation), so cancelling a superseded run is always safe.
9+
- cron: '0 6 1 * *' # monthly 1st 06:00 UTC (Actions burn cut, standards#288)
1410
concurrency:
1511
group: ${{ github.workflow }}-${{ github.ref }}
1612
cancel-in-progress: true
@@ -27,21 +23,23 @@ jobs:
2723
fail-fast: false
2824
matrix:
2925
include:
30-
# Repo has no JS/TS source — `javascript-typescript` matrix
31-
# produced "no source files" failures on every CodeQL run.
32-
# Switch to `actions` extractor (scans workflow files which
33-
# every repo has). Per hypatia rule `codeql_language_matrix_mismatch`.
26+
# Default to `actions` — scaffolded repos rarely have JS/TS
27+
# source; `javascript-typescript` produced "no source files"
28+
# failures on every CodeQL run. The `actions` extractor scans
29+
# workflow files which every repo has. Override per-repo if
30+
# the scaffolded project actually contains JS/TS code.
31+
# Per hypatia rule `codeql_language_matrix_mismatch`.
3432
- language: actions
3533
build-mode: none
3634
steps:
3735
- name: Checkout
3836
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3937
- name: Initialize CodeQL
40-
uses: github/codeql-action/init@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v3
38+
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.28.1
4139
with:
4240
languages: ${{ matrix.language }}
4341
build-mode: ${{ matrix.build-mode }}
4442
- name: Perform CodeQL Analysis
45-
uses: github/codeql-action/analyze@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v3
43+
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.28.1
4644
with:
4745
category: "/language:${{ matrix.language }}"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
name: container build
3+
on:
4+
pull_request:
5+
paths:
6+
- 'container/**'
7+
- 'build/just/container.just'
8+
- '.github/workflows/container-build.yml'
9+
push:
10+
tags: ['v*']
11+
workflow_dispatch:
12+
13+
# Scope + cancel superseded runs (estate guardrail).
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
container:
23+
# OFF by default — costs nothing in derived repos. A repo opts in by setting
24+
# the repository/organisation variable CONTAINER_CI=true. When enabled it
25+
# runs on OWNED self-hosted runners (no metered GitHub Actions minutes);
26+
# override the labels with the CONTAINER_RUNNER variable (a JSON array).
27+
if: vars.CONTAINER_CI == 'true'
28+
runs-on: ${{ fromJSON(vars.CONTAINER_RUNNER || '["self-hosted","owned","container"]') }}
29+
timeout-minutes: 30
30+
permissions:
31+
contents: read
32+
steps:
33+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
34+
35+
- name: Tooling check
36+
run: |
37+
command -v just >/dev/null 2>&1 || { echo "::error::just not found on this runner"; exit 1; }
38+
# The container recipes auto-detect the engine (podman | nerdctl | docker).
39+
for e in podman nerdctl docker; do command -v "$e" >/dev/null 2>&1 && { echo "engine: $e"; break; }; done
40+
41+
- name: Build image
42+
run: just container-build
43+
44+
- name: Verify compose configuration
45+
run: just container-verify
46+
47+
- name: Scan image (trivy, best-effort)
48+
run: |
49+
if command -v trivy >/dev/null 2>&1; then
50+
trivy image --severity HIGH,CRITICAL --exit-code 0 "${{ github.event.repository.name }}:latest" || true
51+
else
52+
echo "trivy not installed on this runner — skipping image scan"
53+
fi
54+
55+
- name: Sign & verify .ctp bundle (tags only)
56+
if: startsWith(github.ref, 'refs/tags/v')
57+
run: just container-sign # cerro-torre: build + pack + Ed25519 sign + verify

.github/workflows/dependabot-automerge.yml

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ name: Dependabot Auto-Merge
3838
on:
3939
pull_request:
4040
types: [opened, reopened, synchronize]
41+
concurrency:
42+
group: ${{ github.workflow }}-${{ github.ref }}
43+
cancel-in-progress: false
4144
permissions:
4245
contents: write # needed to enable auto-merge
4346
pull-requests: write # needed to approve
@@ -47,6 +50,7 @@ jobs:
4750
# Only run for PRs actually authored by Dependabot.
4851
if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]'
4952
runs-on: ubuntu-latest
53+
timeout-minutes: 15
5054
steps:
5155
- name: Fetch Dependabot metadata
5256
id: meta
@@ -78,26 +82,18 @@ jobs:
7882
set -euo pipefail
7983
8084
is_security=false
81-
is_patch_or_minor=false
82-
is_high_or_critical=false
83-
8485
[ -n "$GHSA_ID" ] && is_security=true
85-
case "$UPDATE_TYPE" in
86-
version-update:semver-patch|version-update:semver-minor)
87-
is_patch_or_minor=true ;;
88-
esac
89-
90-
# Dependabot adds severity labels like "severity: high",
91-
# "severity: critical". Look for those in the PR labels JSON.
92-
if echo "$PR_LABELS" | grep -qiE '"(severity: (high|critical))"'; then
93-
is_high_or_critical=true
94-
fi
9586
96-
if $is_security && $is_patch_or_minor && ! $is_high_or_critical; then
97-
echo "action=automerge" >> "$GITHUB_OUTPUT"
98-
else
99-
echo "action=skip" >> "$GITHUB_OUTPUT"
100-
fi
87+
# Owner policy (deliberate: velocity over caution). Auto-merge EVERY
88+
# Dependabot update — patch, minor AND major, security or routine.
89+
# Safe because GitHub's auto-merge only COMPLETES once the required
90+
# checks (secret-scanner, codeql, hypatia-scan, openssf-compliance,
91+
# build/test) are green: a bump that breaks fails CI and the PR stays
92+
# OPEN with an email ("oi, it broke") instead of landing on a red
93+
# main; a bump that passes lands within minutes with no chasing.
94+
# This is preferred over making Dependabot a ruleset BYPASS actor,
95+
# which would let bumps skip those very checks (no gate, no signal).
96+
echo "action=automerge" >> "$GITHUB_OUTPUT"
10197
echo "security=$is_security" >> "$GITHUB_OUTPUT"
10298
echo "update_type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT"
10399
echo "ghsa=$GHSA_ID" >> "$GITHUB_OUTPUT"

0 commit comments

Comments
 (0)