BuildNot yet confirmed elsewhere1 publisher3 min readPublished
Checkout v7 warns and exits rather than clone fork code under pull_request_target
For anyone running pull_request_target, pinning v7 starts with a workflow audit rather than a version bump, and the credential relocation underneath it carries a runner version floor that self-hosted fleets should check first.
The Engineer · Build desk

What happened
- Checkout v7 will not check out fork code unless the workflow explicitly sets allow-unsafe-pr-checkout: true, reversing v6 and earlier, which fetched fork code by default.
- Under pull_request_target or workflow_run the job holds the base repository's GITHUB_TOKEN, secrets and runner access, so fork code checked out there executes with all of it.
- Credentials no longer sit inside .git/config; they live in a job-scoped file under $RUNNER_TEMP that .git/config reaches through a git credential helper entry.
- The action was rewritten from CommonJS to ESM to follow the @actions/* packages, arriving at what dev.to puts at 362KB of TypeScript compiled to ESM.
Why it matters
- decision Each pull_request_target workflow now needs its own verdict before anyone pins v7: audit it for secret exposure, or write the unsafe flag into the YAML and defend that in code review.
- constraint A self-hosted fleet cannot safely adopt the new credential handling ahead of its runner upgrade, because the failure mode is degraded authentication rather than an error anyone will trip over.
- exposure Container actions that mount the workspace lose their cheap route to the job token, which narrows the readable surface to the runner process and whatever else was granted the helper context.
- contradiction dev.to credits the credential relocation to v7 in one paragraph and to v6 in another, and that decides whether pinning v7 changes your token handling at all or only your fork policy.
An exit is a quieter failure than a red step. When the trigger is `pull_request_target` or `workflow_run` and the flag is unset, v7 logs a warning and returns from its main function without cloning [6]. Nothing in that path fails the job, so the following step runs against a workspace with no source in it and the warning is the only signal [17]. The flag is named `allow-unsafe-pr-checkout`, which forecloses arguing in review that you did not know what you were enabling [5].
The credential change is the part worth opening the config for. The older behaviour wrote the token straight into `.git/config` as an `extraheader` line, base64 in the clear to anything with filesystem access [7], and container actions mount the workspace, so reading that file was the whole attack [15]. The replacement is a git credential helper pointed outside the workspace: `helper = store --file=/runner/_temp/credential_store` [19]. Git consults it only when it actually needs to authenticate, and post-job cleanup deletes the file [8]. Because `$RUNNER_TEMP` sits outside the workspace tree, an action that mounts `$GITHUB_WORKSPACE` never sees it [9], which leaves the runner process and anything explicitly granted access as the readers [10]. Which version shipped that is where dev.to's own account splits, with the introduction crediting v7 and the walkthrough crediting v6 [20]. The check is cheap: look at `.git/config` on an existing v6 job before you write the upgrade note.
The compatibility cost lands on container actions. According to dev.to, a Docker container action running authenticated git needs Actions Runner v2.329.0 or later to receive the helper context, and older runners fail silently or fall back to unauthenticated operations [11]. Two things have to be true for that to reach you: you run container actions that fetch or push with the job token, and you control the runner version. On GitHub's hosted runners the version moves without your involvement, and the credential file is scoped to a job temp directory that is cleaned up afterwards [13]. On a self-hosted fleet pinned for some other reason, the silent fallback is yours to find, and dev.to notes self-hosted isolation guarantees differ before its text cuts off [1].
The ESM rewrite is the weakest leg of the security argument. The `@actions/*` packages moved to ESM to support modern Node runtimes and shed unmaintained transitive dependencies, which forced a rewrite of the entry point and the credential logic [12]. Bundle size is not a security property; the patched `@actions/http-client` and `tunnel` versions are, and those you can check against a lockfile [22]. The claimed hardening from stricter module resolution against a shadowing CommonJS package [23] holds in a narrow case and is not the reason to schedule this migration.
In my context the new default is correct and nearly free. Where `pull_request_target` exists only so a bot can label and comment [16], the workflow never needed the fork's source, and the audit ends with leaving the flag unset [14].
What to watch
- Whether GitHub's own actions/checkout documentation confirms the flag spelling allow-unsafe-pr-checkout and the v2.329.0 runner floor.
- Whether a later v7 patch turns the warning into a hard step failure, which would move fork-facing pipelines from silently empty to red.
- Whether marketplace container actions start publishing a minimum runner version now that authenticated git depends on helper context being passed in.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence30
- Adoption
- Insufficient
- Hype gap+35
- Incentives55
- Confidence30
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
dev.to states that self-hosted runners have different isolation guarantees, and the article's text is truncated at that point.
- [2]
Checkout v7 refuses to check out fork code by default when workflows run with elevated privileges.
- [3]
Workflows triggered by pull_request_target or workflow_run run with the base repository's GITHUB_TOKEN, secrets and runner access, so fork code checked out by them executes with full privileges.
- [5]
v7 will only check out fork code if the workflow explicitly sets allow-unsafe-pr-checkout: true.
- [6]
The refusal is implemented as an early exit in the action's main function: if the trigger is pull_request_target or workflow_run and the flag is not set, the action logs a warning and exits without cloning the repository.
- [7]
Before v6, persist-credentials: true wrote the GITHUB_TOKEN directly into .git/config as an extraheader AUTHORIZATION line containing the base64-encoded token, readable by any process with filesystem access.
- [8]
The credential helper protocol reads the temp file only when Git needs authentication, and the file is deleted during post-job cleanup.
- [9]
$RUNNER_TEMP is ephemeral and scoped to the job, and is not part of the workspace directory tree, so actions that mount $GITHUB_WORKSPACE do not see it.
- [10]
The arrangement creates a privilege boundary in which only the runner process and actions explicitly granted access can read the credential file.
- [11]
Docker container actions that need to run authenticated Git commands now require Actions Runner v2.329.0 or later, which knows how to pass the credential helper context into the container; older runners fail silently or fall back to unauthenticated operations.
- [12]
Checkout v7 migrated from CommonJS to ECMAScript Modules because the @actions/* packages (toolkit, core, exec, io) moved to ESM to support modern Node.js runtimes and drop unmaintained transitive dependencies, forcing a rewrite of the entry point and credential handling logic.
- [13]
GitHub's hosted runners are multi-tenant, with jobs from different repositories running on the same VM separated by ephemeral job directories and process isolation, and the credential file is scoped to the job's temp directory, which is cleaned up after the job completes.
- [14]
dev.to's stated migration path is to audit the workflow for secret exposure, then set the flag if the risk is acceptable.
- [15]
Containerized actions inherit the runner's filesystem including .git/config, and credentials in .git/config are readable by any process in the container, so a malicious action running cat .git/config obtained the token.
- [16]
pull_request_target gives fork pull requests access to secrets for commenting and labeling.
- [17]
Because the refusal exits without cloning rather than failing the step, a subsequent step runs against a workspace containing no source, and the logged warning is the only signal.
- [18]
dev.to's introduction states that Checkout v7 moved credentials out of .git/config into ephemeral files under $RUNNER_TEMP.
- [19]
dev.to's walkthrough states that Checkout v6 moved credentials into a separate file under $RUNNER_TEMP, with .git/config referencing it indirectly via [credential] helper = store --file=/runner/_temp/credential_store.
- [20]
dev.to's own account is internally inconsistent about which release relocated credentials: the introduction attributes the move out of .git/config to v7, while the walkthrough attributes it to v6, so at most one attribution can describe this one-time change.
- [21]
dev.to describes actions/checkout as the most-used GitHub Action, an 8,699-star action that powers millions of CI/CD workflows daily.
- [22]
The new codebase is 362KB of TypeScript compiled to ESM, with updated dependencies that patch known vulnerabilities in older versions of @actions/http-client and tunnel.
- [23]
dev.to argues ESM's stricter module resolution prevents certain supply chain attacks in which an attacker injects a malicious CommonJS module into node_modules and relies on Node's loose resolution order to shadow a legitimate package.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toGitHub Actions Checkout v7: Why Fork Pull Request Safety Required a 362KB Credential Isolation Rewrite
1 article · August 27, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- CI/CD Supply Chain SecurityFollow
- Breaking Change MigrationFollow
- Self-Hosted Runner IsolationFollow
- GitHub Actions SecurityFollow
- Credential IsolationFollow