Status: privately reported to Mozilla via HackerOne (report #3820002). This repository is a minimal, self-contained demonstration on the author's own repository. It deliberately contains no production-target details and no secret material — the full production-impact analysis is provided to the Mozilla security team through the private report only. No Mozilla production secret was ever accessed.
Taskcluster's GitHub service reads the pull-request trust policy from a repository's
default branch (which a fork PR cannot change), but reads the .taskcluster.yml
version that selects the scope-assignment code path from the pull-request head
(which a fork PR fully controls) — with no consistency check between the two.
On a repository whose default branch opts into policy.pullRequests: public_restricted,
a non-collaborator who sets version: 0 in their fork-PR .taskcluster.yml is
assigned the trusted …:pull-request role instead of the intended untrusted
…:pull-request-untrusted role.
The default branch (main) is version: 1 + policy.pullRequests: public_restricted
(see .taskcluster.yml). Two pull requests were opened from a
non-collaborator account; only the PR-head .taskcluster.yml version differs:
| PR | head version |
role the service computed | correct for a non-collaborator? |
|---|---|---|---|
| #1 | 0 |
…:pull-request — trusted |
� over-privileged |
| #2 | 1 |
…:pull-request-untrusted |
✅ intended |
Mozilla's community-tc-integration[bot] itself computed and posted those scopes:
- PR #1 (v0 → trusted):
commitcommentoncb43f29… - PR #2 (v1 → untrusted):
commitcommenton5ff7d23…
This is a freshly-integrated repository, so both its …:pull-request and
…:pull-request-untrusted roles are empty. Neither can satisfy queue:create-task,
so no task runs and nothing escalates here — by design; this PoC is benign.
The finding is which role the service assigned, before that check. Read the
line "Taskcluster-GitHub attempted to create a task for this event with the following
scopes" in each bot comment: it is …:pull-request (trusted) for version: 0
and …:pull-request-untrusted for version: 1. Same repository, same public_restricted
policy, same non-collaborator — only the head version differs, and it flips the
assigned role.
On a repository whose trusted role is provisioned with real scopes, the version: 0
task would instead be created under that trusted role. The impact analysis for that
case is in the private Mozilla report.
In services/github/src/tc-yaml.js:
VersionZero.completeInTreeConfigselects scopes from the GitHub event type and never consultspayload.tasks_for, so it cannot emit the untrusted role.VersionOne.createScopesdoes honourpayload.tasks_for.
The version is taken from the PR-head config
(services/github/src/intree.js)
with no comparison against the default branch.
Suggested fixes: make VersionZero honour payload.tasks_for like VersionOne;
or reject a PR-head .taskcluster.yml whose version differs from the default branch;
or retire version: 0.
The community-tc GitHub service retains the build records (independent of this repo):
GET https://community-tc.services.mozilla.com/api/github/v1/builds?organization=sys0xFF&repository=tc-poc
Reported privately to Mozilla via HackerOne #3820002 before any public artifact. Production-impact specifics (affected repositories, reachable secrets) are not in this repository; they are shared with the Mozilla security team through the report. No Mozilla production secret was accessed at any point. This PoC runs only against the author's own repository on Mozilla's in-scope community-tc and creates no task.