chore(deps): check the declared lockfileVersion, not just the packages key

From Copilot's fourteenth pass of #1962. `hasReadableLockShape` and the
diagnostic it drives both promise "lockfileVersion 2+", but the check only
looked for a `packages` table with a root entry. A file declaring v1 while
carrying such a table was accepted, produced an empty version map, and could
make a real skew read as aligned once only one other holder remained — the
same fail-open the shape check was added to close, through the half of the
contract that wasn't enforced.

The declared version is now verified: a finite number, 2 or greater. Absent,
non-numeric, and declared-v1-with-packages cases are all in the table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
This commit is contained in:
cliffhall 2026-08-10 22:23:32 -04:00
commit e1768e44ce
2 changed files with 21 additions and 5 deletions

View file

@ -249,7 +249,14 @@ export function topLevelLockVersions(lock) {
* cannot read it, so it must say so rather than skip the install.
*/
export function hasReadableLockShape(lock) {
const packages = lock?.packages;
// The declared version is checked, not just inferred from the presence of a
// `packages` key: this function and the diagnostic it drives both promise
// "lockfileVersion 2+", so a file claiming v1 while carrying a `packages`
// table must be rejected rather than half-trusted (Copilot, #1962).
const version = lock?.lockfileVersion;
if (typeof version !== "number" || !Number.isFinite(version) || version < 2)
return false;
const packages = lock.packages;
if (typeof packages !== "object" || packages === null) return false;
return Object.prototype.hasOwnProperty.call(packages, "");
}

View file

@ -302,11 +302,20 @@ test("hasReadableLockShape: only a v2+ packages table with a root entry (Copilot
undefined,
null,
{},
{ packages: null },
{ packages: [] }, // an array has no `""` key
{ packages: {} }, // no root entry
{ packages: { "node_modules/zod": { version: "4.4.3" } } },
{ lockfileVersion: 3, packages: null },
{ lockfileVersion: 3, packages: [] }, // an array has no `""` key
{ lockfileVersion: 3, packages: {} }, // no root entry
{
lockfileVersion: 3,
packages: { "node_modules/zod": { version: "4.4.3" } },
},
{ lockfileVersion: 1, dependencies: { zod: { version: "4.4.3" } } },
// A declared v1 carrying a `packages` table: the version is checked, not
// inferred from the key's presence, so this is rejected rather than
// half-trusted into an empty (fail-open) version map.
{ lockfileVersion: 1, packages: { "": {} } },
{ packages: { "": {} } }, // no declared version at all
{ lockfileVersion: "3", packages: { "": {} } }, // not a number
];
for (const lock of rejected)
assert.equal(hasReadableLockShape(lock), false, JSON.stringify(lock));