Skip to content

fix: propagate callback permissions into top-level workflow permissions - #191

Merged
joshua-temple merged 2 commits into
mainfrom
fix/callback-permissions-propagation
Jun 16, 2026
Merged

joshua-temple merged 2 commits into
mainfrom
fix/callback-permissions-propagation

Conversation

@joshua-temple

Copy link
Copy Markdown
Collaborator

Problem

Per-callback workflow permissions declared in a manifest (such as id-token: write for OIDC) were parsed into CallbackInfo.Permissions but never emitted into any generated workflow. A reusable-workflow caller job legally cannot set job-level permissions, so the calling workflow's top-level permissions block must grant everything any invoked callback requires. With the top-level block hardcoded, a callee needing a scope the caller lacks fails: GitHub rejects it with requesting id-token: write, but is only allowed id-token: none.

Fix

Compute the union of callback permissions across the dependency graph and emit it on top of each generator's existing base scopes. Base scopes keep their historical order for byte-identical output when no callback declares permissions; callback-only scopes are appended in sorted order so generation stays deterministic. A scope required as write by any callback is promoted to write.

Applied to the generators that invoke reusable-workflow callbacks: orchestrate, promote, hotfix, and rollback. Standalone generators (external-update, merge-queue, pr-preview, validate-check) invoke no reusable callbacks and are unchanged.

Verification

  • New unit tests in internal/generate/callback_permissions_test.go: union includes id-token: write plus base scopes; deterministic sorted order across multiple scopes; no-callback output byte-identical; promote and rollback covered.
  • determinism_test.go config extended with callbacks carrying permissions maps so the determinism guarantee covers the new map-ordered emission.
  • New e2e scenario e2e/scenarios/orchestrate/callback-permissions-oidc.yaml asserts a callback with id-token: write produces the top-level union and that no job-level permissions block is emitted on the caller job.
  • Dogfood regeneration produces zero diff (root manifest declares no callback permissions, union is empty).
  • go build ./... && go test ./... && golangci-lint run ./... green; e2e module builds and vets clean.

Per-callback permissions declared in the manifest (such as id-token: write
for OIDC) were parsed into CallbackInfo but never emitted. A reusable-workflow
caller job cannot set job-level permissions, so the calling workflow's
top-level permissions must grant the union of base scopes and every scope any
invoked callback requires. Without id-token: write at the caller top level,
GitHub rejects the callee with 'requesting id-token: write, but is only allowed
id-token: none'.

Collect the union of callback permissions across the dependency graph and emit
it on top of each generator's base scopes, with callback-only scopes appended
in sorted order for deterministic output. Applied to orchestrate, promote,
hotfix, and rollback generators. The no-callback-permissions path is byte
identical to prior output.

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
…dent

Use a lexicographic comparison so the union and base promotion pick the same
deterministic value when callbacks set a scope to different values, removing
the latent dependency on map iteration order. write still wins over read wins
over none, matching the monotonic permission order.

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
@joshua-temple
joshua-temple merged commit be25ea4 into main Jun 16, 2026
7 checks passed
@joshua-temple
joshua-temple deleted the fix/callback-permissions-propagation branch June 16, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant