Skip to content

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

How we use AISend a correction

A free GitHub account could reach DuckDuckGo's CI secrets How a workflow on pull_request_target exposed CI secrets to code from fork PRs, per Faizan Akhtar's dev.to writeup.

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.

A free GitHub account could reach DuckDuckGo's CI secrets
WhoHowKindClaim
Any GitHub userNeeded no privileges beyond a free GitHub account to carry out the attackcapability18
DuckDuckGo's CI runnerChecked out and ran fork PR code with no fork detection or approval gate, secrets in its environmentexposure8
Anthropic API keyHeld in the runner's environment, which ran with the base repository's privilegesexposure14
GITHUB_TOKENAutomatic token in the runner's environment, carrying pull-requests: write permissionexposure14
Other DuckDuckGo workflowsTen workflows across the org use the same trigger; the report also found secret exposure via sibling workflowsexposure15

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
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [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. [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. [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

Sources

1 independent publisher whose own reporting we read for this story.

  1. dev.to

    1 article · October 11, 2026

    DuckDuckGo CI/CD RCE: How a Single GitHub Workflow Could Have Backdoored Every DuckDuckGo Browser — Writeup Analysis

Share your take

Let Clarity write the post for you.

Signed-in readers get a short post drafted on this story in the register they choose — narrative, analytical, or a direct position — editable to the last word before it goes anywhere. The share buttons at the top of this story work without an account.

Topics and entities

Follow any of these and your For You feed starts watching them — no settings page required.

Loading related stories