Skip to content

npm: v3 lockfiles emit workspace paths and alias keys as registry dependencies with allow: all #16488

Description

@myevolve

Summary

With npm lockfile v3 and allow: [{ dependency-type: all }], a hosted version-update job attempts registry lookups for local workspace paths and npm alias keys. The job records workspace-path HTTP 405 and alias-key HTTP 404 registry errors and exits nonzero, even when it creates other dependency-update PRs.

The same invalid dependency identities reproduce offline through the unmodified full npm parser in the exact hosted updater image. This report uses only a synthetic fixture; no original repository files or credentials are needed.

Runtime

  • Ecosystem: npm / npm_and_yarn
  • Image: ghcr.io/dependabot/dependabot-updater-npm:3507ad510e0351d2bcd8e3975e9c323aa6d00e02
  • Pulled image digest: sha256:4beb224504e150d978da4212e06eb390ba4abb630eeedcfc7d44b265aa1e0d1e
  • Parser run: Linux amd64 container, network disabled, no credentials
  • Native control: npm generated the v3 lockfile; npm ci succeeded; Node 22.23.3 loaded the alias and resolved the workspace link correctly.

Minimal fixture

Create repro/package.json:

{
  "name": "dependabot-parser-repro",
  "version": "1.0.0",
  "private": true,
  "workspaces": ["packages/*"],
  "dependencies": {
    "is-number-alias": "npm:is-number@7.0.0"
  }
}

Create repro/packages/local/package.json:

{
  "name": "local-workspace",
  "version": "1.0.0",
  "private": true
}

From repro/, run npm install --package-lock-only --ignore-scripts --legacy-peer-deps --no-audit --no-fund. The resulting lockfile is:

{
  "name": "dependabot-parser-repro",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "dependabot-parser-repro",
      "version": "1.0.0",
      "workspaces": [
        "packages/*"
      ],
      "dependencies": {
        "is-number-alias": "npm:is-number@7.0.0"
      }
    },
    "node_modules/is-number-alias": {
      "name": "is-number",
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
      "license": "MIT",
      "engines": {
        "node": ">=0.12.0"
      }
    },
    "node_modules/local-workspace": {
      "resolved": "packages/local",
      "link": true
    },
    "packages/local": {
      "name": "local-workspace",
      "version": "1.0.0"
    }
  }
}

For hosted reproduction, configure:

version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    allow:
      - dependency-type: all

(The fixture files above belong at the hosted test repository root, without the local repro/ wrapper.)

Offline reproduction

Save this next to the local repro/ directory as reproduce.rb:

require "json"
require "dependabot/source"
require "dependabot/dependency_file"
require "dependabot/npm_and_yarn/file_parser"

directory = ARGV.fetch(0)
files = %w[package.json package-lock.json packages/local/package.json].map do |name|
  Dependabot::DependencyFile.new(name: name, content: File.read(File.join(directory, name)))
end
[false, true].each do |dealias|
  dependencies = Dependabot::NpmAndYarn::FileParser.new(
    dependency_files: files, source: nil, options: { dealias_packages: dealias }
  ).parse
  puts JSON.generate(
    dealias_packages: dealias,
    dependencies: dependencies.map { |d| { name: d.name, version: d.version, top_level: d.top_level? } }
  )
end

Run from their parent directory:

docker run --rm --platform linux/amd64 --network none \
  --cap-drop ALL --security-opt no-new-privileges \
  --mount "type=bind,source=$PWD,target=/repro,readonly" \
  --entrypoint bundle \
  ghcr.io/dependabot/dependabot-updater-npm:3507ad510e0351d2bcd8e3975e9c323aa6d00e02 \
  exec ruby /repro/reproduce.rb /repro/repro

Observed output:

{"dealias_packages":false,"dependencies":[{"name":"is-number-alias","version":"7.0.0","top_level":false},{"name":"packages/local","version":"1.0.0","top_level":false}]}
{"dealias_packages":true,"dependencies":[{"name":"is-number","version":"7.0.0","top_level":true},{"name":"local-workspace","version":"1.0.0","top_level":false}]}

With default parser options, the only two dependencies are is-number-alias and packages/local, both treated as non-top-level dependencies. The alias target is actually is-number; packages/local is an entirely local workspace.

The internal dealias_packages: true control resolves the alias but still incorrectly emits local-workspace as a registry dependency. It is not a repository-level workaround, nor does this probe establish alias-update support.

Cause and expected behavior

Local workspace records must not become registry-update candidates. Aliases must not query the registry under an unrelated alias key; correct update support must preserve the alias identity and target/range, rather than silently remove update coverage. This is about parser identity, not registry availability or npm lockfile validity.

Related alias history: #1693 and #15847. #15849 addressed a Bun manifest guard; this reproduction is npm lockfile parsing. The internal alias flag was introduced in #15070 for dependency graphing, not as a supported hosted-update switch.

No ignores, direct-only filters, package removals, or lockfile downgrades were applied to hide the errors.

Activity

  1. myevolve commented on Oct 7, 2026

    @myevolve
    Author

    Proposed repair: #16490 (commit 8c005c7).

    The fix retains alias installation keys for native npm commands and uses the real package name only at registry/metadata boundaries; canonicalizing Dependency.name alone would lose independently constrained aliases. It also removes local workspace descriptors from registry candidates and preserves target/version identity through collisions.

    Local verification on the pinned npm updater image: 836 examples, 0 failures, 19 changed Ruby files lint clean, real update/reparse plus npm ci/runtime checks for both lockfile formats and a mixed-target alias collision. Existing manifest-alias rewriting, canonical advisory matching and native workspace-update limits are explicitly unchanged. Full details and regressions are in the PR. This is a proposed code fix, not confirmation that the managed updater has been deployed.

  2. myevolve commented on Oct 7, 2026

    @myevolve
    Author

    Deeper identity-path follow-up is now in #16490, head 5c071c04aaece71e10007a520d6859e3edf4f42e.

    • Lockfile selection now compares the requested registry target’s own version, not an unrelated namesake’s minimum. A 972-case target/version/order matrix went from 384 mismatches to zero.
    • Canonical-package all_versions is separated from the full installation-slot records. Registry vulnerability checks no longer consume unrelated target versions, while installation-name blocked-version rules still cover every sibling alias. Raw parser records remain acyclic and dependency graph identities are preserved.
    • Verification: 941 npm/common examples and 14 updater-policy examples, zero failures; 24-file Ruby lint; 16 metadata/advisory/policy cases; real native lockfile updates followed by npm ci and module execution. Hosted CI caught an ecosystem-dependent test import; the corrected test also passes in the bundler-only updater image.
    • A separate native npm 12.2.0 defect allowed successful install/ci to execute ordinary ms@2.0.0 in a workspace explicitly requesting npm:is-odd@2.0.0. Minimal explicit-alias validator fix and public reproducer: fix(arborist): preserve explicit npm alias target identities npm/cli#10090 (142 assertions and native before/after runtime checks).

    No dependency exclusions, manifest rewrites, or reduced coverage. These are upstream proposals, not a hosted Dependabot rollout. Installation-name scheduling, canonical advisory matching for alias names, and native cross-workspace target isolation are not presented as solved. Current CI status and the earlier follow-redirects snapshot differences are documented in the PR.

  3. myevolve commented on Oct 7, 2026

    @myevolve
    Author

    All three upstream branches are now synchronized without rewriting published history:

    Actual npm12.2.0 source CLI checks passed independently and composed: installation-name/version collisions retain canonical identities; only the selected workspace is eagerly updated; npm ci, real module calls, manifest bytes, and ci lockfile bytes check out. No coverage threshold was relaxed and no failure was ignored.

    Maintainer gates: approve fork workflows (alias run, workspace run), review and merge the PRs, then release/adopt the native npm changes and verify hosted Dependabot rollout. Current credentials have read-only upstream access and cannot approve workflows or merge. The issue stays open. All three optional failures (npm, group-rules, group-semver) were proven identical to their respective unmodified-base controls (each two hunks, 12 changed lines); tracked separately at dependabot/smoke-tests#592. Canonical-alias scheduling/advisory gaps are not claimed resolved. Existing review requests already target the appropriate maintainer teams, so no duplicate requests were sent. The Core PR uses a non-closing issue reference to preserve the rollout gate.

    No application dependency, lockfile, or Dependabot-ignore changes were made during this continuation.

  4. sajjadfarooq768-ai commented on Oct 7, 2026

    @sajjadfarooq768-ai
      • # @
  5. myevolve commented on Oct 7, 2026

    @myevolve
    Author

    Latest Smoke diagnosis (2026-10-09)

    At unchanged discovery head 29e40163970538243917eaef8f742ebe220bf086, Smoke run 37867769858 executed 97 E2E jobs: 95 passed their test and Diff steps; 2 failed. The failures in this run are NuGet and pip-compile, not Gradle. Required-check success must not be described as an entirely green Smoke run; earlier head-specific results below remain historical.

    • NuGet aborts during clone/fetch, before registry replay (0/0 cached calls). Its unchanged ShellGitCommandHandler discards non-authentication Git failures. A host .NET 8 runtime probe linked to the production process/handler files, using real Git and a deliberately non-listening loopback proxy, observed clone exit 128, successful handler return, then fetch raising Win32Exception(2) because the checkout directory did not exist. This verifies the misleading-error mechanism, not the original CI clone error, whose stderr was discarded.
    • [INFERENCE] The CI startup log order is consistent with the existing proxy-readiness race: clone/fetch errors appear before the proxy's Listening (:1080) message. CLI #658 already addresses that startup-ordering gap and remains open. It is independent of discovery Fail smoke discovery on GitHub API and pipeline errors #16498 and replay-clock Go modules (vgo) support #668; no duplicate fix, retry, weakened assertion, or new NuGet change was added to this chain.
    • pip-compile produces the same five expected dependency updates; its diff is the five pycparser==3.0 → 3.1 lockfile changes already recorded in smoke-tests #595, with 96/130 calls cached.

    The six reviewed PR heads, review feedback, release gates and upstream cache publication are unchanged. Core/CLI latest releases remain v0.399.0/v1.94.0; matching upstream artifacts are still absent. Maintainer approval, release and cache-publication steps remain required. The previously denied upstream workflow dispatch was not retried, and no branch or fixture was changed by this investigation.

    Gradle/pip-compile follow-up (2026-10-08)

    The two residual suites are now diagnosed and their clean recordings are published separately in smoke-tests#595, head ddcf3e24cea11fcd30a7e8de4b2f62d5278b6421. Gradle's old Maven cache keys omit :443; pip's old PyPI keys include a Cache-Control header the current client no longer sends. These misses reached live registry data.

    Actual clean-cache regeneration changes only five uncapped pycparser==3.1 lines and adds real Hibernate release notes. All job inputs, source commits, ignore conditions, selected primary versions and expected PR sets are preserved. Final released-CLI v1.94.0 replays are full-YAML byte-identical: Gradle 119/119 cached; pip-compile 130/130 cached; both PASS, with no metadata/job errors. No Core/proxy workaround or weakened check was needed.

    Released CI for #595: pip-compile passes; Gradle still fails. Gradle now creates all four PRs; its sole diff is the 29-line Hibernate release-note block absent from the old cached releases response (75/116 cached). pip-compile is green at 96/130 cached. Suite discovery and yamllint pass. This confirms why matching Cache One artifacts are still required; expectations must not be weakened to accommodate stale metadata.

    Hosted proof added (23:02 UTC): the unchanged Cache One workflow passes in the writable fork at the same #595 head: Gradle and pip-compile. Both downloadable artifacts were replayed without editing responses: full-YAML byte-identical at 119/119 and 130/130 cached. #595 contains artifact links, upload digests and download commands. This is fork-hosted evidence only; the upstream consumer still requires maintainer-published artifacts in dependabot/smoke-tests. No code or constraints changed; fork workflows are inactive again.

    Upstream gate remains: a maintainer must run Cache One at refs/pull/595/head for gradle and python-pip-compile, publish both matching caches, and rerun/review the fixture PR before merging. Then rerun Core #16498 Smoke against the new main fixture blobs. #595 is now ready for review, with dependabot/maintainers already requested; matching upstream caches and passing Smoke checks remain required before merge. Fork/local proof does not turn the historical 95/97 CI result green. The independent npm #593, Core review and CLI clock-release gates remain unchanged.

    Initial investigation (historical)

    Separate baseline-smoke investigation completed: Core #16492 fixes npm transitive resolver/writer cooldown disagreement without changing the existing alias/workspace PR branches. Verification: 234 passing examples, clean Ruby lint, and four actual resolver→writer smoke scenarios with matching versions.

    The clock experiment also proved that HTTP fixture replay remains sensitive to elapsed release age; that distinct limitation is documented in smoke-tests #592. No snapshots were weakened or hand-repinned. Initial cooldown-head CI: 21/21 required checks passed; the three optional smoke failures exactly match the unmodified-base controls. Completed CI evidence. Maintainer review/merge is still required; existing upstream review/release gates remain separate.

    Review routing: requesting the same maintainers team used by the existing Core PR returned HTTP 404 from the review-request API with the current account. No approval or merge was attempted. This failed request did not establish that no existing team requests existed; see the REST-verified routing correction below.

    Replay-clock continuation: dependabot/cli#668 captures and replays Ruby/native npm clocks. Actual Core updates at Oct 2 and Oct 7 produced matching dependency/lockfile versions, and the original replay was byte-identical with 20/20 HTTP calls cached. Full local Go suite passed twice with race detection; Current CLI CI is complete after integrating v1.94.0: 104/104 checks passed, including 2/2 required checks, with no failed or pending checks (dependabot/cli#668 (comment)). Maintainer review remains required; the CLI CODEOWNERS team-review request returned HTTP 404 with this account. No existing PR branches or application code changed. Smoke fixture regeneration and the separately tracked ignored-version cap remain outstanding.

    Current integration status

    Latest runtime heads are Core #16492 97c3cac44b62df8ecf93b08f9041849e85ffa20f and Core #16496 de4a7cb8bf466b2667fdb4b8bc5948a1061d86c2, both incorporating main 0286aa0868b1f0de1b908ce3358bbf994e5272c1. The merge changes shared notice rendering and Julia stdlib notices, without weakening npm policy guards or fixture contracts.

    Full-repository Sorbet passed on all three refreshed Core branches and again after the policy spec-isolation correction. Direct shared-notice rendering confirms INFO/fallback → NOTE, WARN → WARNING, ERROR → IMPORTANT. Three complete CLI replays at cooldown 97c3cac44b62df8ecf93b08f9041849e85ffa20f + policy 260f77e6e25e0b6cbcf00882ada974398de69a82 are byte-identical, with 202/202, 200/200 and 249/249 cached calls, unchanged clocks and no metadata/job errors. Policy’s subsequent de4a7cb8bf466b2667fdb4b8bc5948a1061d86c2 commit changes only spec isolation and its README; all mounted runtime library trees are unchanged. CLI source is unchanged; no new Go-suite run is claimed.

    Test-isolation follow-up at de4a7cb8bf466b2667fdb4b8bc5948a1061d86c2: package-manager specs left npm selectors active across examples, producing 24 failures in the previous full npm run. Shared cleanup now resets both active and per-directory selectors after each example; redundant local cleanup hooks were removed. The existing two-example reproducer fails before / passes after, the complete previously failing worker passes 564 examples, 0 failures at seed 16904, all five changed Ruby files pass RuboCop, and full-repository Sorbet reports no errors. No production runtime code, cooldown/ignore guard or fixture contract changed.

    Core #16492 CI: 21/21 required checks passed; 55 successful / 4 skipped check records. Full npm/yarn specs: 2231 examples, 0 failures, 4 existing pending. No E2E jobs ran: discovery logged jq: Cannot index string with string "name" but reported success; the overall Smoke workflow concluded failure. Green check records are not E2E coverage.

    Core #16496 CI: 21/21 required checks passed; 65 successful / 3 failed / 4 skipped check records, none pending. Full npm/yarn CI at de4a7cb8bf466b2667fdb4b8bc5948a1061d86c2: 2231 examples, 0 failures, 4 existing pending, seed 28164. The previous 24 required-spec failures are resolved.

    Smoke actually ran 13 E2E jobs: 10 passed both the test and Diff steps, 3 failed. npm and rules omit the out-of-policy follow-redirects 1.16.1 / form-data 4.0.6 PRs still expected by main fixtures; cache coverage is 124/197 and 122/196. semver omits those dependencies from the expected group and has corresponding lockfile/metadata differences, with 148/223 cached calls. All three used released CLI v1.94.0 and main fixtures/official caches, not draft #593. These failures remain visible; no baseline-identical or released-toolchain-pass claim is made.

    Core #16498 at f4f87c4f4645b14edfbe7aee80c63ab38243126e: 21/21 required checks passed; 150 checks succeeded, 2 failed, 4 skipped. Smoke run discovered and executed all 97 E2E jobs: 95 passed both the actual ecosystem test and Diff steps; 2 failed. All 13 npm E2E jobs passed. The failures are Gradle (missing expected PR events after Maven metadata HTTP 404s; 95/125 calls cached) and pip-compile (pycparser 3.0 → 3.1; 96/130 cached). This is main runtime/main fixtures, not the unreleased npm policy or draft recordings; the overall Smoke run is not green. No fixture assertion or unrelated ecosystem code was changed, and no failed job was rerun.

    CLI #668 remains at 49b4c7532aa9fbeb59b300432713bd1b5ed859ea: 104/104 checks, including 2/2 required, passed. Its full local Go race suite passed previously at the same head; it was not rerun during this Core-only update. The actual released v1.94.0 binary was already shown to drop input.recorded-at despite a successful event comparison; that failed release behavior was not rerun merely to confirm it.

    Maintainer actions: (1) review/merge the independent discovery workflow fix and obtain genuine runtime-branch E2E evidence; (2) review/merge CLI #668 and Core #16492/#16496, then release the CLI clock support and an updater containing both runtime fixes; (3) run Cache One for npm, npm-group-rules, and npm-group-semver at fixture head aeed1cf2ffe310b228b69cc493de09182455372f, with read-only recording credentials and checked metadata-fetch logs; (4) run released-toolchain Smoke and obtain fixture review. CLI v1.94.0 still lacks clock replay. No upstream approval, upstream merge, release, official cache refresh, raised cap, changed source commit, or workflow/protection bypass was performed.

    Review-routing correction: REST GET /repos/{owner}/{repo}/pulls/{number}/requested_reviewers confirms that dependabot/maintainers is already requested on Core #16492, #16496, #16498 and CLI #668. CLI #668 also requests dependabot/azure-dev-ops. These requests predate this continuation: Core requests are dated October 7/8 and both CLI requests October 7. gh pr view displayed empty reviewer lists, while GraphQL reports the existing requests with requestedReviewer: null; the empty display did not mean reviews were unassigned. Core REST evidence; CLI REST evidence.

    The later individual requests for JamieMagee and jakecoffman returned HTTP 404 and added no individual reviewers. Existing team requests need review responses, not duplicate assignment. All four prerequisite PRs still report REVIEW_REQUIRED; the account remains read-only upstream. No new review request, approval, upstream merge, release, official cache refresh or protection bypass was performed.

    Draft smoke-tests #593 is unchanged at aeed1cf2ffe310b228b69cc493de09182455372f. It preserves every original policy input and recorded clock. Historical released-toolchain CI remains 2 passed / 3 failed; the complete local replay proof is not a claim of released-toolchain compatibility. Maintainer handoff.

    Independent AI review completed

    Three independent reviewer agents covered all six PRs. Four reproduced defects were fixed: legacy npm v1 policy bypass, unsafe rebinding after nested removal, in-place alias identity substitution, and inconsistent duplicate-.npmrc release-age precedence. Final re-review found no remaining actionable correctness finding.

    Core PR Current head Required checks
    Cooldown #16492 50d4e42 21/21 pass
    Policy #16496 17d417e 21/21 pass
    Discovery #16498 29e4016 21/21 pass

    Canonical npm suites: cooldown 2235 examples, 0 failures, 4 pending; final policy 2283 examples, 0 failures, 4 pending. The policy follow-up fixes old acceptance tests that tolerated a changed nested Acorn 6.4.2 under a 6.0.2 cap; production validation was not relaxed.

    Local proof: four production-Ruby before/after cases, 85 helper/parser examples, 10 cooldown examples, four actual-workflow discovery cases, and all three complete npm fixture YAMLs byte-identical with the repaired combined Core runtime and CLI 49b4c75. The final policy commits after 683673c change only tests/docs, so those runtime replay pins remain valid. Earlier head-specific results above remain historical.

    AI-authored technical reviews: clock · cacheFixtures · policy · cooldown · discovery · npmFixtures. These were submitted as COMMENT, not maintainer approval.

    Remaining external gates are unchanged: Core/CLI release and upstream cache publication, plus required maintainer approval. Direct upstream gh workflow run cache-one.yml returned 403: Must have admin rights to Repository. #595 is ready for review; #593 remains draft. Fork cache evidence is not upstream cache publication, and stale-cache smoke failures remain real.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions