Skip to content

Commit e0aa0f8

Browse files
author
27Bslash6
committed
ci: exact-label runner allow-list, refuse unreadable runner forms (LAB-3500)
Kody flagged that the hosted-prefix pattern admitted a pool label renamed `ubuntu-<anything>`. Allow-list the exact labels in use instead (`^(ubuntu|macos|windows)-latest$`) — smaller than the prefix pattern and not a second, staler copy of GitHub's image table (Kody's enumerated list already omits ubuntu-26.04, macos-26-intel, windows-2025-vs2026, xcode-27). Expert panel on the delta found four shapes that set a job's runner where the guard could not read it and exited 0: flow-style `{runs-on: x}`, a matrix built by `${{ fromJSON(...) }}` (the comment claimed "no fromJSON" without enforcing it), a job calling a reusable workflow in another repo, and quoted or upper-cased keys. Each is now an error, not a skip; keys and values are lower-cased before matching. Rejected: banning `continue-on-error` repo-wide (editing the guard job is deletion-class and the ban has real cost) and YAML obfuscation forms (`? runs-on`, escapes) — deliberate acts the guard is documented not to defend against; the server-side control is LAB-1161 stage 2. Verified with the job's exact program under mawk 1.3.4: clean tree exit 0; 42 fixtures (30 bad → ::error + exit 1, 12 good → exit 0); the pre-PR main workflows fail on exactly their 19 `runs-on: cachekit` lines.
1 parent d40d3fe commit e0aa0f8

1 file changed

Lines changed: 22 additions & 8 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,13 @@ jobs:
288288
# GitHub-hosted runners — never on the org's self-hosted ARC pool (labels
289289
# `cachekit`, `cachekit-lean`). Allow-list, not deny-list: a deny-list fails
290290
# open on a new pool label, `CacheKit` casing, `${{ vars.X }}` indirection or
291-
# the block form `runs-on: {group:, labels:}`. Every `runs-on:` and matrix
292-
# `os:` value must be a hosted label or the literal `${{ matrix.os }}`, so a
293-
# runner matrix has to spell its labels out inline (no fromJSON). This is
291+
# the mapping form `runs-on: {group:, labels:}`. Every `runs-on:` and matrix
292+
# `os:` value must be one of the exact labels in the allow-list regex below
293+
# or the literal `${{ matrix.os }}`, written inline where the guard can read
294+
# it: no fromJSON matrices, no flow-style `{runs-on: x}`, no reusable
295+
# workflows from other repos (their runs-on lives outside this scan). Exact
296+
# labels, not a hosted-prefix pattern: a prefix match would admit a pool
297+
# label renamed `ubuntu-<anything>`. This is
294298
# drift protection for maintainers, NOT a fork-PR control: a fork PR runs the
295299
# fork's copy of this file and can simply delete the guard. The server-side
296300
# control is the runner group `cachekit-private` (allows_public_repositories
@@ -304,23 +308,33 @@ jobs:
304308
steps:
305309
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
306310

307-
- name: Every runs-on / matrix os value is a GitHub-hosted label
311+
- name: Every runs-on / matrix os value is an allow-listed GitHub-hosted label
308312
run: |
309313
# One awk pass, no shell pipeline: a program error exits non-zero and
310314
# fails the step (fail closed) instead of reading as "no match".
311315
# Comments are stripped before matching; `- os:` covers matrix include
312316
# entries. Same command runs locally: paste it from here.
313317
awk -f /dev/stdin .github/workflows/*.y*ml <<'AWK'
314-
{ sub(/#.*/, "") }
315-
/^[[:space:]]*(-[[:space:]]*)?(runs-on|os)[[:space:]]*:/ {
318+
{ sub(/#.*/, ""); $0 = tolower($0) }
319+
# A runner set where the rule below cannot read it is an error, not
320+
# a skip: flow-style `{runs-on: x}` / `{os: [..]}`, a matrix built by
321+
# an expression, or a job calling a reusable workflow in another repo.
322+
/[{,][[:space:]]*["']?(runs-on|os)["']?[[:space:]]*:/ ||
323+
/^[[:space:]]*(-[[:space:]]*)?(matrix|include|exclude)[[:space:]]*:[[:space:]]*\$\{\{/ ||
324+
(/^[[:space:]]*uses[[:space:]]*:/ && /\/\.github\/workflows\// && !/uses[[:space:]]*:[[:space:]]*["']?\.\//) {
325+
printf "::error file=%s,line=%d::runner set where the drift guard cannot read it — write `runs-on: <label>` / `os: [<labels>]` inline, spell matrices out (no fromJSON), call reusable workflows only from ./.github/workflows/ (LAB-3500)\n", FILENAME, FNR
326+
bad = 1
327+
}
328+
/^[[:space:]]*(-[[:space:]]*)?["']?(runs-on|os)["']?[[:space:]]*:/ {
316329
v = $0; sub(/^[^:]*:[[:space:]]*/, "", v); sub(/[[:space:]]+$/, "", v)
317330
if (v ~ /^["']?\$\{\{[[:space:]]*matrix\.os[[:space:]]*\}\}["']?$/) next
331+
if (v ~ /^\[/ && v !~ /\]$/) v = ""
318332
gsub(/[][ "']/, "", v)
319333
n = split(v, items, ",")
320334
ok = (n > 0)
321-
for (i = 1; i <= n; i++) if (items[i] !~ /^(ubuntu|macos|windows)-[a-z0-9.-]+$/) ok = 0
335+
for (i = 1; i <= n; i++) if (items[i] !~ /^(ubuntu|macos|windows)-latest$/) ok = 0
322336
if (!ok) {
323-
printf "::error file=%s,line=%d::runner value \"%s\" is not a GitHub-hosted label — this public repo never runs on the self-hosted pool (LAB-1161)\n", FILENAME, FNR, (v == "" ? "<empty / block form>" : v)
337+
printf "::error file=%s,line=%d::runner value \"%s\" is not an allow-listed GitHub-hosted label — this public repo never runs on the self-hosted pool (LAB-1161); adopting a new hosted image means extending the allow-list in this job\n", FILENAME, FNR, (v == "" ? "<value not inline on this line>" : v)
324338
bad = 1
325339
}
326340
}

0 commit comments

Comments
 (0)