Unclaimed Internal Package Scope on a Consumer Review Platform Dashboard
The Risk
The company's business-side dashboard pulled in internal code modules whose names nobody owned on the public software registry. Any outsider could register the matching name and publish code under those module names. Depending on how the company's build pipeline was configured, that attacker code could be pulled into the production dashboard the next time it built, exposing every business customer that uses it.
The Vulnerability
The business-side dashboard at business-dashboard.<company> shipped production code bundles whose public source maps disclosed imports from three internal first-party packages under the company's unclaimed registry scope:
<scope>/apis- used for backend API client middleware and reducers, including insights endpoints, feedback, review spotlight, and review insights.<scope>/shared-utils- used for shared dashboard primitives like chart types, panels, period selectors, hooks, and feedback components.<scope>/types- used for shared TypeScript types, e.g. appshell props.
The source maps also mapped these names to sibling workspace folders under ../../packages/ in a monorepo, confirming they were internal first-party packages and not a third-party dependency.
At the time of discovery, the scope organization was not registered on the public registry and none of the three package names existed there. This is the standard precondition for a supply-chain confusion attack: an outsider can claim the scope for free and publish packages under the same names that the production bundle imports.
The downstream exploitability depended on the company's internal build configuration (scope-pinned registry mapping, lockfile integrity, install-script handling). Those settings are not externally observable, so the report focused on the externally-confirmable primitive: the claimable namespace combined with production-build evidence that the names were actively consumed.
The Attack
- Fetch the dashboard's webpack chunk index from a stable URL. Parse out every current chunk id and hash pair (hashes rotate on every deploy, so this discovery step is dynamic).
- Fetch every
.chunk.js.mapin parallel and grep for the internal scope. Two carrier chunks reference it at the time of capture. - Extract every distinct import statement against the scope across all carriers. Eleven distinct imports were observable.
- Confirm the monorepo origin: the largest carrier source map's
sourcesarray exposes the webpack project name and maps the scoped packages to sibling workspace folders. - Probe each package name on the public registry. At time of testing every endpoint returned
404, confirming the names were unclaimed and freely publishable. - (Not executed.) A real attacker would now claim the scope, publish each name with a non-empty install-time script, and wait for the next build that resolves from the public registry.
The researcher took non-destructive defensive action while the report was in triage: the matching scope was registered under their account on the public registry on 2026-05-14. Zero packages were published. Ownership transferable to the company on request.
The Impact
The reachable attack surface mirrors the import graph:
- The
apispackage wraps the business dashboard's data fetching, including insights, feedback, and review-spotlight endpoints. A hijack would land attacker code inside the data layer of every authenticated business session. - The
shared-utilspackage provides chart and panel primitives used across the dashboard. Anything rendered on the business UI is a candidate observation point. - Build-time install scripts, if not disabled in CI, run with whatever environment the build pipeline carries (deploy credentials, monitoring keys, source-map upload tokens).
Every business customer of the platform is in scope of the resulting impact, with downstream consequences including credential theft from reviewers and business administrators, fraudulent review responses published under the business identity, and abuse of the verified-business trust signal.
Remediation
- Accept ownership transfer of the defensively-claimed scope from the researcher. The standard registry org-transfer flow is invite-the-target, target accepts, researcher leaves.
- After transfer, publish placeholder versions for each package name with no install scripts to permanently squat the names on the public registry.
- Pin the scope to the private registry in repo-root and CI-level configuration so the public registry is never a fallback for these names, regardless of squat state.
- Re-scan all production source maps for any scoped import pattern and verify every internal scope is registered to a company-controlled account. Apply the same scope-pin defense to each.
- Add
--ignore-scriptsto all CI install invocations to neutralize install-time lifecycle scripts even if a confused-name package is resolved. - Strip embedded source content from production source maps to remove the leaked dependency-name inventory.