Skip to content

Prevent recursive workflows triggered by the Project GitHub App - #188

Merged
gjkim42 merged 1 commit into
mainfrom
fix/job-token-trigger-suppression
Oct 1, 2026
Merged

gjkim42 merged 1 commit into
mainfrom
fix/job-token-trigger-suppression

Conversation

@gjkim42

@gjkim42 gjkim42 commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

What type of PR is this?

/kind api

What this PR does / why we need it:

A workflow that posts an issue comment using the built-in job token can trigger another Open Actions run and keep repeating. Apply the recursive-trigger policy centrally during authenticated webhook delivery processing, before workflow discovery or WorkflowRun creation.

  • Identify the Project App through GitHub's authenticated App endpoint and suppress its comment, push, issue, release, review, and other repository events. Human and unrelated App events continue normally. Identity lookup failures retry without creating runs.
  • Allow the App's pull_request opened, synchronize, and reopened events with an immutable spec.approval requirement. Reuse the authenticated Console approval operation, validate the pinned head before planning, and supersede pending runs when another revision arrives. Fork credential restrictions remain independent.
  • Preserve manual Console/Kubernetes dispatch, schedules, and reusable calls. For workflow_run notifications sent by the Project App, allow only native runs triggered by workflow_dispatch or repository_dispatch; suppress push-triggered runs and other or missing origins. Suppress derived pull_request_target events from the Project App.

The policy follows GitHub's documented GITHUB_TOKEN rules for supported triggers.

Which issue(s) this PR is related to:

Related to #114. This addresses recursive triggers for dedicated Project Apps; token lifetime/refresh, token-level attribution for shared Apps, and GitHub API dispatch support remain open.

Special notes for your reviewer:

The Project App must be reserved for Open Actions. GitHub webhook payloads identify the App actor, not the individual installation token, so separately issued tokens for the same App receive the same policy. Other automation that should trigger workflows must use another App or a PAT. This restriction is documented in the quickstart and reference. The filter affects Open Actions only, not GitHub's native workflow engine. App-sent lifecycle notifications for native PR runs and native workflow chains are suppressed because their payloads do not establish approval or the original trigger; remaining compatibility work is tracked in #114.

Apply the updated WorkflowRun CRD with the controller upgrade. The approval requirement is independent of forkPullRequest, so approved same-repository runs retain their normal token permissions and secret access. The Console reuses its existing approval control and authentication/CSRF path.

Validation:

  • make test
  • make verify
  • make test TEST_FLAGS='-tags=e2e -run=^$' (compiles the end-to-end suite locally).
  • Conformance coverage for self-trigger suppression and exceptions, positive human/other-App events, identity failures, approval immutability, replay, reruns, superseded revisions, anonymous approval rejection, and planning after approval.
  • End-to-end coverage sends signed issue-comment webhooks through the deployed controller and verifies completed delivery processing: Project App comments create no runs or jobs, while human and other-App comments create jobs. Native workflow_run cases cover suppression after an App push, allowed explicit dispatch, and allowed human push. A signed App pull request persists its approval requirement in Kubernetes and creates a job only after authenticated Console approval.
  • The end-to-end suite uses the GitHub fixture and a real Kubernetes control plane. All 20 end-to-end cases passed on the squashed commit in PR CI, along with build, verification, and race-enabled unit tests.

Does this PR introduce a user-facing change?

Open Actions prevents recursive workflow runs from its Project GitHub App, including issue-comment loops and loops through native GitHub workflow notifications. App-triggered pull requests opened, synchronized, or reopened require administrator approval before jobs run. Reserve the Project App for Open Actions; use a different App or PAT for automation that should trigger workflows.

@gjkim42

gjkim42 commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

/kelos review

@gjkim42 gjkim42 added triage-accepted Accepted for triage priority/important-soon High-priority work to address soon labels Oct 1, 2026
@kelos-bot

kelos-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🤖 Kelos Task Status

Task open-actions-reviewer-issue-comment-4a165a2d2532 has succeeded. ✅

@kelos-bot

kelos-bot Bot commented Oct 1, 2026

Copy link
Copy Markdown

🤖 Open Actions Reviewer Agent @gjkim42

Review Summary

Verdict: APPROVE
Overall correctness: patch is correct
Scope: Suppresses webhook deliveries sent by the Project GitHub App, except pull_request opened/synchronize/reopened, which create runs gated by a new immutable WorkflowRun.spec.approval that reuses the Console's fork-approval flow.

Findings Overview

Priority Count File:Line Summary
P0 0 — none
P1 0 — none
P2 1 internal/webhook/job_token.go:31 App-sent workflow_run exemption leaves a recursion loop through native GitHub workflows
P3 0 — none

Findings

GitHub Actions compatibility

  • [P2] internal/webhook/job_token.go:31 (also docs/reference.md:776-778, pinned by internal/webhook/job_token_test.go:71): allowJobTokenEvent lets workflow_run deliveries from the Project App through, on the grounds that lifecycle events are not "repository mutations authenticated with the job token". That reasoning holds on GitHub but not here. Open Actions' workflow_run trigger only consumes GitHub's webhooks for native GitHub Actions runs (docs/reference.md:1759-1761), and the reference notes that GitHub treats the job token as an ordinary App token, so it does trigger native workflows. So an App-sent workflow_run event always describes a native run that the job token caused. Here is the loop: an Open Actions workflow on: workflow_run: {workflows: [CI]} pushes a fix-up commit with the job token. The push starts native CI with the App as sender. CI completes and sends workflow_run with the App as sender. That event passes this filter, and the Open Actions workflow runs and pushes again, with no limit. On GitHub, a GITHUB_TOKEN push creates no workflow run, so there is no workflow_run and the chain stops (docs). This leaves a gap in the release note's claim that Open Actions "prevents recursive workflow runs from its Project GitHub App". Suggested fix: parse workflow_run.event from the payload. Then suppress App-sent workflow_run deliveries unless the native run came from an event GitHub would have run for GITHUB_TOKEN (workflow_dispatch/repository_dispatch). Add a conformance case for a push-triggered native run. If you keep the exemption on purpose, the reference should state that this path can still loop.

Suggestions (optional)

  • [P3] internal/controller/workflowrun_controller.go:3755: workflowRunsSupersededByPullRequestRevision now runs for every pull_request WorkflowRun create event. Before this PR it ran only for fork runs. Each call does an uncached namespace-wide APIReader.List. The informer replays create events for every existing run on each controller start. With the default of keeping WorkflowRuns indefinitely, that is N live lists of N objects at startup. The map function only decides what to enqueue, and the reconcile re-checks superseding through APIReader. So the cached client (r.List) would probably be enough here.
  • [P3] internal/webhook/job_token.go:20: AppBotLogin signs a JWT and calls GET /app for every delivery attempt from any [bot] sender: Dependabot, Renovate, and the Project App's own comments, which are the deliveries being suppressed. The App's login doesn't change for a given App ID and key, so caching it in the GitHub client would avoid an extra API round trip on every bot delivery.
  • [P3] API: spec.approval has a generic name, but its docs and the CEL rule describe it as specific to App-triggered pull requests. It also sits next to forkPullRequest.approved, and the Console approve action sets both. Consider running /kelos api-review to confirm the name, and whether the run should record why approval is required, before the field is permanent.

Key takeaways (optional)

  • The main loop is fixed: Project App issue comments, pushes, labels, releases, and reviews are dropped after webhook authentication and before discovery. Identity lookup failures retry without creating runs. The pull request approval gate follows GitHub's documented approval-required behavior, with the head pinned and superseded revisions handled.
  • One gap remains: workflow_run events for native GitHub runs that the job token triggered.
  • Coverage is good. Unit, controller, Console (including rejection of anonymous approval), CRD schema, and Kind e2e tests check the suppression path and the approve-then-plan path with real assertions.

Suppress repository mutations and native workflow notifications from the dedicated Project App, preserving explicit dispatch exceptions. Require approval for eligible App-triggered pull requests and retain fork credential restrictions independently. Cover webhook suppression and Console approval with unit, conformance, and Kubernetes end-to-end tests.
@gjkim42
gjkim42 force-pushed the fix/job-token-trigger-suppression branch from 40d2d0f to 9ec27d5 Compare October 1, 2026 14:13
@gjkim42
gjkim42 added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 21f677e Oct 1, 2026
16 checks passed
@gjkim42
gjkim42 deleted the fix/job-token-trigger-suppression branch October 1, 2026 14:35
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