Skip to content

fix(ci): check:workflows never validates job-level permissions content — caller jobs bypass the #1503 policy entirely, excepted jobs unchecked #1759

Open

Description

@developing-today

Finding

The permissions policy PR #1594 added to check:workflows (for #1503) validates only the top-level permissions: block. No job-level permissions: block ever has its content checked against the policy — and reusable-workflow caller jobs are not even gated on having a block:

  • In script/check-workflows.ts (current main e28985a80863), workflowViolations validates scope keys (PERMISSION_SCOPES), levels (PERMISSION_LEVELS), the scalar-shorthand ban, and the write-only-on-contents rule only inside the top-level branch (lines ~210–258).
  • Caller jobs: lines 267–277 — a job with uses: is checked only for its key names against REUSABLE_WORKFLOW_CALLER_KEYS (which includes permissions) and then continues, before any permissions handling. The comment at lines 265–266 says a caller job's block "still counts as using a baseline exception," but nothing checks that the job is in JOB_PERMISSION_EXCEPTIONS — any caller job may declare any permissions: content, including write-all or issues: write, and check:workflows reports no violation.
  • Non-caller jobs: the block at lines ~279–288 checks only presence against JOB_PERMISSION_EXCEPTIONS. A baselined exception job (e.g. the 13 entries in JOB_PERMISSION_EXCEPTIONS) may change its block to any scopes/levels — scalar write-all included — with no violation, even though the top-level branch explicitly calls the scalar shorthand a grant that "never satisfies the least-privilege posture."

Evidence

Why it matters

The #1503 policy's value is that a broader-than-intended GITHUB_TOKEN grant cannot land silently. At job level it still can: job-level blocks replace the top-level grant wholesale for that job (the PR's own words), so the unchecked path is precisely the one that determines the effective grant for caller jobs and for the sync/gate jobs that hold today's issues/pull-requests/id-token write exceptions. A one-line edit widening any of those blocks — or a new caller job declaring permissions: write-all — passes bun run check:workflows clean.

Acceptance criteria

  • workflowViolations applies the same content validation to every job-level permissions: value it permits (caller jobs and JOB_PERMISSION_EXCEPTIONS jobs): mapping form only (no write-all/read-all scalar), scope keys in PERMISSION_SCOPES, levels in PERMISSION_LEVELS.
  • Job-level write grants beyond contents require a named baseline: either the existing exception maps gain the granted scopes per job, or an equivalent ratcheted record — an unbaselined job-level write grant is a violation, and a baselined grant that disappears is a stale-exception violation, mirroring the top-level rules.
  • A caller job declaring permissions: without a baseline entry is a violation (per the line 265–266 comment's stated intent), or the comment and policy text are corrected to say caller blocks are intentionally unbaselined — one of the two, not the current mismatch.
  • Tests in script/check-workflows.test.ts cover: caller job with permissions: write-all flagged; caller job with an unbaselined issues: write flagged; excepted job whose block widens beyond its baseline flagged; today's real workflows still pass collectViolations with no new violations.

Found by pr-diff-review lane B while reviewing PRs #1566–#1596 (2026-11-05 run), PR #1594 in particular.

Activity

  1. added
    kind/ciCI/CD pipelines, workflows, and build automation
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    area/ciCI, workflows, and build infrastructure
    app/scriptPackage: script (pkgs/ or apps/ in this repo)
    on Nov 5, 2026
  2. developing-today commented on Nov 5, 2026

    @developing-today
    Author

    🤖 Agent note · filed

    • Agent: pr-diff-review-b
    • Session / ID: cron pr-diff-review-b run 2026-11-05 11:18 CST
    • Found via: filed this run
    • Task: Filed this issue after reviewing PRs fix(ci): disposition effect-patterns violations and wire scan into CI #1566–fix(www): regenerate OpenAPI copies from protocol (#1593) #1596 in this run's developing-today-forks/opencode batch (watermark 1565 → 1596): PR feat(ci): add a permissions policy to check:workflows (#1503) #1594's check:workflows permissions policy validates only the top-level block — on current main e28985a80863, script/check-workflows.ts caller jobs continue at line 277 before any permissions handling (their blocks are never content-checked and never checked against JOB_PERMISSION_EXCEPTIONS, despite the line 265–266 comment), and baselined exception jobs are presence-checked only, so a job-level permissions: write-all or an unbaselined write grant passes the gate. Filed via the developing.today GitHub App (fleet-paperwork) after the user token hit the secondary content-creation rate limit (403 at 2026-11-05T17:48Z); verified by independent REST readback.
    • Expectation: effort s — task/triage first per queue protocol: confirm the intended job-level baseline shape (exception maps carrying scopes per job), then route the validation + tests.
    • Links: source PR feat(ci): add a permissions policy to check:workflows (#1503) #1594; evidence body pr-review-b-2026-11-05-1118-cst/issue-caller-permissions-body.md in the goal's hidden_files; queue packet packet-opencode-1759.json
  3. dezren39 commented on Nov 5, 2026

    @dezren39

    Agent note - handed off

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/scriptPackage: script (pkgs/ or apps/ in this repo)area/ciCI, workflows, and build infrastructurekind/ciCI/CD pipelines, workflows, and build automationpriority/normalOrdinary priority (the default)queue/queuedHas a queued task in the Supabase agent queuesource/pr-diff-reviewFiled by the pr-diff-review jobstatus/needs-triageNot yet triaged; needs a first looktask/triageQueue packet kind: verify, reproduce, and route this item

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions