KEY TAKEAWAYS
- Record the workflow's revision separately from the code, configuration and dependencies it executes. A trusted workflow can still run untrusted PR content.
- Use pull_request for untrusted testing without privileged credentials; reserve privileged automation for a necessary, constrained operation on data.
- Two workflows do not sanitize the handoff. An artifact, cache entry or PR title remains controlled by its producer when a privileged consumer reads it.
- Keep a mandatory unknown on HOLD with an owner and next action. Approval, job success and a matching digest do not establish a data-only boundary.
Make the privilege decision before CI executes
A maintainer wants fork PR tests and an automated comment. Combining them in a job that can access a private-registry secret may look convenient. The important question is not whether a human or an agent wrote the PR: it is whether PR-controlled content can execute where privileged credentials are available. GitHub documents this risk for pull_request_target and workflow_run.[1][3]
Use this worksheet to make one bounded decision per job and per input crossing. Mark ACCEPT only when the responsible maintainer has evidence for that specific boundary; otherwise mark HOLD and name the missing evidence, owner and next action. These labels are our proposed review convention, not GitHub approval states.
This review happens before execution. A secret-scanning result or merge approval answers a different question; neither establishes that CI has isolated PR code from privileged access. Agent authorship is not an authentication boundary. Record the PR's repository origin, event actor and actual settings instead.
Choose the event by the work it must do
| Event and purpose | Workflow and code origin | Boundary to preserve |
|---|---|---|
| pull_request: test untrusted changes | For an open, mergeable PR, the workflow and default checkout use the PR merge commit, not just the head commit. | Fork PRs normally get a read-only GITHUB_TOKEN and no other secrets. Verify settings and compute isolation; do not assume same-repository PRs have these restrictions. |
| pull_request_target: necessary labeling, triage or other data-only automation | The workflow and default checkout come from the base repository's default branch. An explicit checkout ref can change the code origin without changing the workflow origin. | Do not execute untrusted PR code with secrets or a privileged token. Keep PR metadata as constrained data; inspect all fetch and interpretation paths. |
| workflow_run: a separate operation after another workflow | The consumer workflow is on the default branch and can have secrets/write tokens even if its producer could not. | Treat producer artifacts as untrusted. Bind the exact run and authorized target; validate data without executing it. Separate filenames alone are not isolation. |
Private-fork settings can send write tokens or secrets to PR workflows. Effective GITHUB_TOKEN permissions combine enterprise, organization and repository defaults, workflow/job declarations and the applicable fork adjustment. Once a permissions subset is specified, unspecified categories become none. Read the effective configuration rather than assigning trust from an event name.[5][9]
Fork-run approval permits execution; it does not prove that code is harmless. GitHub asks maintainers to inspect changes, especially workflow files, and warns about user-controlled code on self-hosted infrastructure.[5][7] For agent automation using GITHUB_TOKEN to create or update a PR, current docs describe opened, synchronize and reopened runs requiring approval. Do not assume either that every token-created PR runs automatically or that none can trigger CI.[2][8]
Copy first: the repository context
Copy these fields into your design review. Do not record token values or secrets. Use UNKNOWN when evidence is missing, NOT RUN for an unobserved runtime property, and NOT APPLICABLE only with a reason. Every mandatory unknown keeps the affected decision on HOLD.
- Repository / visibility: ____ | Platform: github.com / exact Enterprise Server version / UNKNOWN: ____
- Intended test and automation outcomes: ____ | Decision owner: ____ | Review time and timezone: ____
- PR origin: fork / same repository / UNKNOWN: ____ | Authoring account: ____ | Event actor: ____
- Head, base and default-branch resolved SHAs: ____ | Evidence location and observation time: ____
- Applicable Actions event policy and mode: ____ | Fork-run approval policy: ____ | Organization/enterprise overrides: ____
- Mandatory unknown: ____ | Owner: ____ | Next action and next check: ____
Copy once per job: execution and authority
Follow the job from input to its first execution or interpretation point. Include dependency installation hooks and executable configuration, not only obvious build commands. Checkout alone is not execution; what runs afterward can complete the dangerous crossing.[3]
- Job ID / purpose: ____ | Trigger, activity and filters: ____
- Workflow repository, path and resolved revision: ____ | Called workflows and their revisions: ____
- Checkout repository, ref and resolved code SHA: ____ | Checkout action SHA and inputs: ____ | Alternate fetch/download paths: ____
- Input objects and origins: PR title/body/ref/code/config/dependency/cache/artifact: ____ | Who can change each object? ____
- First execution or interpretation point: ____ | What input can become code, configuration or a command argument? ____
- Effective GITHUB_TOKEN permissions and evidence: ____ | Other secrets, persisted checkout credentials, OIDC or ambient authority and scopes: ____
- Runner type / environment / reuse / cleanup / internal-network access: ____ | Evidence: ____
- Cache scope / effective cache-mode / called-workflow cap / restore and save paths: ____ | Evidence: ____
- Artifacts produced or consumed / producer run and revision / target association / extraction path / consumer operation: ____
- Allowed untrusted-to-privileged crossing: ____ | Data-only validation and rejection rule: ____
- Evidence location, revision and observation time: ____ | Runtime status: NOT RUN / observed: ____ | Other UNKNOWN or NOT APPLICABLE fields and reasons: ____
- Decision: ACCEPT for this bounded job / HOLD: ____ | Mandatory unmet requirement: ____ | Owner, next action and next check: ____
GITHUB_TOKEN can be accessed through github.token even when it was not explicitly passed as an action input. Narrowing that token does not account for every other credential in the job.[1][4] For self-hosted runners, record persistent state and reachable resources: environment reviewers do not create isolation, and even one-job JIT registration does not guarantee clean reused hardware.[1]
Copy once per crossing: what reaches the privileged consumer?
Make a separate record for each artifact, restored cache, PR metadata value or downloaded file that crosses a boundary. GitHub's workflow_run example downloads from the triggering run, outside an executable workspace, and checks an integer before using it as a PR number. An integer alone does not establish that the target is the authorized PR. The association and fail-closed requirements below are our additional design recommendations.[2][3]
- Producer job → consumer job: ____ | Object and transport: ____
- Producer repository, run ID, code revision and PR association: ____ | Who controls the bytes? ____
- Consumer workflow revision and effective privileges: ____ | Extraction/restore path and permitted interpretation: ____
- Expected schema, size and path limits: ____ | Authorized target and allowed operation: ____
- Identity and target-association evidence: ____ | Validation and rejection evidence: ____ | Unexpected data handling: ____
- Can any input be executed, sourced, loaded as executable configuration or re-parsed as shell code? ____
- Evidence location and time: ____ | Runtime status / UNKNOWN / NOT APPLICABLE with reason: ____
- Decision: ACCEPT for this bounded crossing / HOLD: ____ | Missing requirement: ____ | Owner, next action and next check: ____
A digest can establish that two copies have the same bytes; it does not establish that the bytes are harmless. Likewise, an upstream success result does not sanitize an artifact. workflow_run can trigger regardless of its predecessor's conclusion unless you add an appropriate condition; such a condition is an outcome filter, not a trust upgrade.[2][3]
Metadata belongs in this record too. GitHub recommends passing a PR title through an intermediate environment variable instead of inserting the expression into generated shell code, and quoting shell variables.[1] Our additional stop rule: do not then undo that separation with eval, shell re-parsing or an unconstrained interpreter.
Worked record A: combined tests and a privileged comment — HOLD
Hypothetical design, NOT RUN. A public github.com repository wants to test a fork PR using a private-registry credential, then post a comment from the same pull_request_target job. This is a design record, not evidence from an actual repository.
- Context: public github.com repository; fork PR; account, actor and resolved head/base/default SHAs UNKNOWN. Decision owner: repository maintainer. Policy/approval/organization overrides UNKNOWN.
- Job: test-and-comment; pull_request_target on opened/synchronize/reopened; filters UNKNOWN. Workflow: trusted default-branch workflow, exact path/SHA UNKNOWN; called workflows UNKNOWN.
- Code/input: checkout overridden to fork head; action pin/other inputs and alternate fetch paths UNKNOWN. Fork author controls package scripts, test configuration and dependencies. First execution: dependency installation and tests.
- Authority: design requests a private-registry secret and PR-comment write access; effective token permissions, other credentials, persisted credentials and OIDC UNKNOWN. Runner type/reuse/internal access and effective cache scope/mode UNKNOWN; artifact paths UNKNOWN.
- Crossing: fork package/test content → privileged test-and-comment execution. Permitted interpretation is execution, not data-only reading. Validation evidence: none; runtime NOT RUN.
- Evidence: this hypothetical specification plus GitHub's documented warning; no repository configuration or runtime evidence.[3] Decision: HOLD. The stated design combines untrusted execution with privileged access; its unknowns are additional gaps, not the sole reason to stop.
- Owner/next action: maintainer separates untrusted testing from privileged automation, or changes the requirement to data-only processing. Resolve private-dependency access as an explicit design problem. Next check: review revised job and crossing records before enabling execution. Do not make allow-unsafe-pr-checkout=true the repair.
Worked record B: separated tests and a data-only comment — conditional candidate
Hypothetical design, NOT RUN. The same public github.com repository instead proposes an unprivileged fork pull_request test producer and a workflow_run comment consumer. Removing the registry-secret requirement from these tests is part of the redesign; separation does not make private access free or safe by itself. This candidate remains HOLD until the required evidence exists.
- Context: fork PR; actor, head/base/default SHAs, event policy and fork approval UNKNOWN. Decision owner: repository maintainer. Next check for both jobs: before enabling this handoff.
- Producer job: pr-tests; pull_request on opened/synchronize/reopened, additional filters UNKNOWN. Workflow and default checkout: PR merge commit; exact workflow path, merge SHA, action pin/inputs and called workflows UNKNOWN. PR author controls test code, configuration and dependencies; first execution is installation/tests.
- Producer authority: intended read-only token, no registry or other privileged credentials; effective settings/persisted credentials/OIDC UNKNOWN. Intended ephemeral isolated compute; runner/reuse/internal access UNKNOWN. Cache scope/mode and overrides UNKNOWN. Artifact: bounded numeric test report from this run; exact schema/path UNKNOWN. Execution/validation evidence: NOT RUN. Producer decision: HOLD pending evidence for the intended restrictions.
- Consumer job: comment-report; workflow_run completed for pr-tests, exact workflow-name/filter and outcome-condition configuration UNKNOWN. Workflow: default branch, exact path/SHA and called workflows UNKNOWN. No PR checkout or alternate code fetch is intended; action pins and complete fetch-path inventory UNKNOWN.
- Consumer authority: only the access needed to read the exact run's artifact and comment on its associated PR is intended. Effective token permissions, other credentials/OIDC, runner isolation and cache scope/mode/called-workflow caps UNKNOWN. First interpretation: parse bounded numeric data with trusted code; never execute or source the report.
- Crossing: pr-tests report → comment-report. Required identity: triggering repository/run/head revision/associated PR, all currently UNKNOWN. Required operation: one constrained comment on that authorized PR. Required extraction: outside executable workspace; exact path UNKNOWN. Required validation: schema/size/path limits, target association and rejection of unexpected data. Actual implementation and rejection evidence: NOT RUN.[2][3]
- Consumer and crossing decisions: HOLD. Owner/next action: maintainer establishes the run-to-PR association, narrow effective credentials, isolated compute and a fail-closed parser; records all cache/fetch paths and evidence. The candidate is eligible for bounded ACCEPT only after each mandatory field is supported. No ACCEPT is claimed here.
- Countercase: if the consumer runs an uploaded script, loads executable configuration or restores producer-controlled cache content into executable paths with privileged credentials, the decision remains HOLD. Two workflow files did not preserve the boundary.[3][6]
Read the current protections narrowly
Three controls answer different questions: an Actions event policy decides whether an event may run; fork approval permits a run; checkout protection restricts particular checkout paths. Do not use evidence for one as proof of the others.[3][5]
As checked on 5 October, GitHub describes a default pull_request_target event policy for public repositories without an existing applicable event policy. It excludes private/internal repositories and is currently in evaluate mode: runs continue. The documentation names 2 November 2026 for enforcement for affected repositories using that default policy before general availability. That is a future plan for a stated cohort, not proof that your repository is blocked now.[3]
The examined actions/checkout v6 revision is d23441a48e516b6c34aea4fa41551a30e30af803. Its action definition defaults allow-unsafe-pr-checkout to false and describes the opt-in for fork PR code under pull_request_target or workflow_run; its README documents the input.[10][11] GitHub explicitly limits these checks to fork PR refs. Unrelated repositories, manual git fetch or gh pr checkout, and downloaded artifacts are not covered.[3] The first supporting patch release and enforcement in a particular account are not established here. We did not run or audit the action's implementation.
Cache behavior also needs a narrower reading than 'every privileged event can poison the main cache.' Current docs give low-trust events resolving to the default branch, including pull_request_target and workflow_run, read-only default-branch-scope cache access unless a write-capable cache-mode is explicitly declared. A pull_request-created cache is normally merge-ref-scoped. cache-mode is a separate control from repository permissions on GITHUB_TOKEN.[6][9]
Check workflow/job overrides and called workflows. Without an explicit caller cap, a called workflow can request write even when the caller's low-trust default is read; an explicit inherited cap limits that request. Current docs describe cache-mode: read on the calling job as a read-only cap. Cache contents are not signed or verified, and readable caches must not contain credentials. A job can succeed when a cache save is skipped or fails with a warning; success does not prove a save occurred.[6] Read-only defaults reduce a write route, not the need to assess restored content or artifacts.
Close a specific boundary, not a checklist score
- HOLD if untrusted code, dependency hooks or executable configuration can run with privileged credentials. Redesign that execution path rather than using approval as a sanitizer.[3]
- HOLD if a privileged consumer cannot establish the producer run, PR association, authorized target or data-only validation. Upstream success and byte identity do not fill those fields.[2][3]
- HOLD if mandatory effective-token, other-credential, runner, cache or alternate-fetch fields remain UNKNOWN. Give each gap an owner, evidence-gathering action and next check.[1][3][6][9]
- ACCEPT only the documented job or crossing whose mandatory boundaries have evidence. Record what was observed versus inferred or NOT RUN, the reviewer and time, and the exact revisions/settings accepted. A changed workflow, action input, runner or producer requires reassessing the affected boundary.
The worksheet is a review aid, not permission to run a test with real secrets. Start with the actual design and existing evidence. Where runtime evidence is required, keep the decision on HOLD until an appropriately authorized verification path establishes it.
Evidence and limits
This resource synthesizes GitHub's current github.com documentation and the pinned checkout action definition/README, observed on 5 October 2026. The live document publication/update dates are not established; observation is not launch evidence. Enterprise Server users must verify the matching version rather than applying cloud defaults unchanged.
The worksheet, HOLD/ACCEPT convention and extra run/target-association requirements are our design recommendations. No CI run, test PR, credential exercise, installation, attack test or protection-enforcement test was performed. The examples do not report measured protection, and vendor documentation alone is not independent evidence of security effectiveness.
Sources & scope
For maintainers designing CI for outside or agent-authored pull requests. GitHub's github.com documentation was checked on 5 October 2026; Enterprise Server behavior requires documentation for the exact installed version. This is a proposed design worksheet, not an executed workflow, a security certification or proof of a new product launch. Both worked examples are hypothetical and NOT RUN.
- GitHub Docs — Secure use reference ↗
github.com documentation observed 5 October 2026. Privileged triggers, untrusted input and runner limits; publication/update date unknown.
- GitHub Docs — Events that trigger workflows ↗
Complete trigger-reference body observed 5 October 2026. PR merge refs, fork restrictions, workflow_run and artifact example; publication/update date unknown.
- GitHub Docs — Securely using pull_request_target ↗
Observed 5 October 2026. Default-branch origin, execution risk, evaluate-only public event policy and checkout-path limits. Future enforcement plan is not present account verification.
- GitHub Docs — Use GITHUB_TOKEN for authentication in workflows ↗
Observed 5 October 2026. Token access and workflow/job permission controls; no token values accessed.
- GitHub Docs — Managing GitHub Actions settings for a repository ↗
Observed 5 October 2026. Fork-run approvals, private-fork token/secret settings and self-hosted warnings; actual repository settings not inspected.
- GitHub Docs — Dependency caching reference ↗
Observed 5 October 2026. Cache scope, low-trust read-only default, cache-mode overrides/called workflows and unsigned-content limits; no cache behavior tested.
- GitHub Docs — Approving workflow runs from forks ↗
Observed 5 October 2026. Approval procedure and inspection of changed workflow files; approval is not a measured safety result.
- GitHub Docs — GITHUB_TOKEN ↗
Observed 5 October 2026. Token-created PR approval and event recursion conditions; no account-specific trigger test.
- GitHub Docs — Workflow syntax for GitHub Actions ↗
Observed 5 October 2026. Effective-token calculation, permission declarations and cache-mode syntax; publication/update date unknown.
- actions/checkout — pinned action definition ↗
Observed v6 ref resolved to this revision on 5 October 2026. allow-unsafe-pr-checkout declaration/default; source inspection, not runtime enforcement verification.
- actions/checkout — pinned README ↗
Same examined revision observed 5 October 2026. Input documentation; first protected patch release and account rollout not established.
Publication history
- — Prepared source-bound worksheet edition with blank job/crossing records and explicitly hypothetical, unexecuted design examples.
Have a correction or a different perspective? Contact Palanthos.