BuildNot yet confirmed elsewhere1 publisher2 min readPublished
A pull_request_target trigger let any forked PR run code in DuckDuckGo's CI with secrets in reach
A HackerOne report says DuckDuckGo's semver-label.yml ran code from any forked pull request with secrets in its environment. Any team can search its own workflows for pull_request_target to find the same pattern.
The Engineer · Build desk
Any GitHub user needed only a free account. DuckDuckGo's CI runner ran fork PR code with secrets in its environment. An Anthropic API key and the GITHUB_TOKEN, with pull-requests: write, sat in that environment. Ten workflows across the org use the same trigger.
- capability Any GitHub user Needed no privileges beyond a free GitHub account to carry out the attack, claim 18
- exposure DuckDuckGo's CI runner Checked out and ran fork PR code with no fork detection or approval gate, secrets in its environment, claim 8
- exposure Anthropic API key Held in the runner's environment, which ran with the base repository's privileges, claim 14
- exposure GITHUB_TOKEN Automatic token in the runner's environment, carrying pull-requests: write permission, claim 14
- exposure Other DuckDuckGo workflows Ten workflows across the org use the same trigger; the report also found secret exposure via sibling workflows, claim 15
| Who | How | Kind | Claim |
|---|---|---|---|
| Any GitHub user | Needed no privileges beyond a free GitHub account to carry out the attack | capability | 18 |
| DuckDuckGo's CI runner | Checked out and ran fork PR code with no fork detection or approval gate, secrets in its environment | exposure | 8 |
| Anthropic API key | Held in the runner's environment, which ran with the base repository's privileges | exposure | 14 |
| GITHUB_TOKEN | Automatic token in the runner's environment, carrying pull-requests: write permission | exposure | 14 |
| Other DuckDuckGo workflows | Ten workflows across the org use the same trigger; the report also found secret exposure via sibling workflows | exposure | 15 |
What happened
- The workflow fired on opened, synchronized and reopened pull request events, with no fork check, no required maintainer label and no author verification.
- Two secrets sat in the runner's environment: an Anthropic API key, and the GITHUB_TOKEN that GitHub issues automatically, scoped with pull-requests: write.
- The report holds four related findings, including secret exposure through sibling workflows and ten workflows across the org on the same trigger.
- The repository's README calls its contents shared JavaScript used within all DuckDuckGo browsers, from Android and iOS to the Chrome and Firefox extensions.
- Independent researcher 6r1ff1n filed the finding through DuckDuckGo's HackerOne program as report #3619287, and Faizan Akhtar analysed it on dev.to.
Why it matters
- exposure Anyone with a free account could read whatever the job could read, and this job held a model API key and a token that can write to pull requests.
- constraint Fixing semver-label.yml alone leaves the other pull_request_target files in place, so the inventory has to cover every workflow on that trigger.
- contradiction The writeup's headline claim is a poisoned release, but the only token permission it lists is pull-requests: write, and the text we have does not show the step between the two.
The two pull request triggers differ in whose context the job runs. The pull_request trigger runs in the fork's context with no access to the base repository's secrets [9]. The pull_request_target trigger runs in the base repository's context and does get them, because it exists for jobs such as posting comments or applying labels [10]. GitHub's documented rule, as dev.to author Faizan Akhtar relays it, is to never check out and execute untrusted code under that trigger [11].
semver-label.yml, which lives at .github/workflows/semver-label.yml in duckduckgo/content-scope-scripts [5], broke that rule at the checkout step. Its job was to auto-apply semantic-version labels to pull requests [7]. It checked out the PR's merge ref, meaning the attacker's fork code merged with the base, instead of the trusted base branch. It then ran dependency installation and a build in that directory [12]. npm executes lifecycle scripts from the checked-out code during installation [13]. Akhtar wrote: "Whoever controls package.json controls the machine." [17]
The privilege bar was low. According to Akhtar, nothing beyond a free GitHub account was required [18]. He reconstructs the chain from the disclosed report:
1. The attacker forks the repository and adds a lifecycle script to package.json. That is one file and one commit [1]. 2. The attacker opens a pull request with any trivial change. The trigger fires automatically, and the runner checks out the merge ref and runs the install step, so npm executes the planted script [2]. 3. The script reads secrets from the environment and sends them to attacker-controlled infrastructure [3].
The text we reviewed ends there. It breaks off mid-word in step 5, after announcing two prizes and starting the first, the Anthropic API key [3]. Akhtar's framing is "a straight path from a forked pull request to a poisoned release shipped inside every DuckDuckGo browser on the planet" [19]. The only token permission the text lists is pull-requests: write [14]. The part of the writeup that would connect that token to a release is not in the text we have, and it does not say whether DuckDuckGo changed the workflow or whether anyone used the hole.
In our view the audit is two questions per file. Does the workflow run on pull_request_target? If so, does it check out the PR's ref and run an install or a build? A yes to both is the shape the report describes. A yes to the first alone is still worth a look, because the secrets are in scope the moment the trigger is.
What to watch
- Whether the remainder of the writeup or the HackerOne disclosure shows the release-pipeline step that the available text cuts off before reaching.
- Whether DuckDuckGo confirms a fix to semver-label.yml and the ten other workflows on the same trigger.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence55
- Adoption
- Insufficient
- Hype gap+35
- Incentives
- Insufficient
- Confidence50
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Step 1 of the reconstructed chain: the attacker forks content-scope-scripts and edits package.json to add a lifecycle script that npm runs during installation, which takes a single file and a single commit.
ReportedSupportedSource: Faizan Akhtar, reconstructing the disclosed report2 sources— create a free account to open themView cited source - [2]
Steps 2 to 4 of the reconstructed chain: any trivial change justifies the PR; the pull_request_target trigger fires immediately with no maintainer approval, label or review; the workflow checks out the PR merge ref; and the install step executes the planted script with the workflow's full environment, including every configured secret.
ReportedSupportedSource: Faizan Akhtar, reconstructing the disclosed report2 sources— create a free account to open themView cited source - [3]
Step 5 of the reconstructed chain: the script reads secrets from the environment and sends them to attacker-controlled infrastructure, with two prizes, the first beginning "the Anthropic AP" where the available text breaks off.
ReportedSupportedSource: Faizan Akhtar, reconstructing the disclosed report2 sources— create a free account to open themView cited source - [4]
Independent researcher 6r1ff1n reported the issue through DuckDuckGo's HackerOne program as report #3619287.
- [5]
The workflow file is .github/workflows/semver-label.yml in duckduckgo/content-scope-scripts.
- [6]
The repository's README describes its contents as "shared JavaScript used within ALL DuckDuckGo browsers", covering Android, iOS, macOS, Windows, the Chrome and Firefox extensions, and autofill.
- [7]
The workflow's job was auto-applying semantic-version labels to pull requests.
- [8]
The workflow triggered on pull_request_target with no access controls, no fork detection and no approval gate, then checked out the PR's code and executed it, so any GitHub user could open a fork PR and get arbitrary code execution on DuckDuckGo's CI runner with the workflow's secrets in the environment.
- [9]
The pull_request trigger runs in the context of the fork with no access to the base repository's secrets.
- [10]
The pull_request_target trigger runs in the context of the base repository and does get secrets; it exists for workflows that need secrets while handling PRs, such as posting comments or applying labels.
- [11]
GitHub's documented rule is never to check out and execute untrusted code under the pull_request_target trigger.
- [12]
Instead of the trusted base branch, the workflow checked out the PR's merge ref (the attacker's fork code merged with the base), then ran dependency installation and a build inside that directory.
- [13]
In Node.js, package installation executes lifecycle scripts from the checked-out code.
- [14]
The runner operated with the base repository's privileges and sensitive values in its environment, including an Anthropic API key and the automatic GITHUB_TOKEN, which carried pull-requests: write permission.
- [15]
The report contained four related findings, including secret exposure via sibling workflows and ten workflows across the org using the same trigger.
- [16]
The workflow fired automatically on pull request events (opened, synchronized, reopened) with no conditions: no fork check, no required maintainer label, no author verification.
- [17]
Akhtar wrote: "Whoever controls package.json controls the machine."
- [18]
The attack required zero privileges beyond a free GitHub account.
- [19]
Akhtar describes the finding as "a straight path from a forked pull request to a poisoned release shipped inside every DuckDuckGo browser on the planet."
ReportedInsufficientSource: Faizan Akhtar, dev.to2 sources— create a free account to open themView cited source
Sources
1 independent publisher whose own reporting we read for this story.
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Software Supply Chain SecurityFollow
- Bug bounty disclosuresFollow
- CI/CD Pipeline SecurityFollow
Entities
- DuckDuckGoFollow
- content-scope-scriptsFollow
- GitHub ActionsFollow
- HackerOneFollow
- npm registryFollow
- AnthropicFollow
- 6r1ff1nFollow
- Faizan AkhtarFollow