diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig index acd9ac8dbd..b4cbe09026 100644 --- a/src/install/extract_tarball.zig +++ b/src/install/extract_tarball.zig @@ -11,13 +11,24 @@ url: strings.StringOrTinyString, package_manager: *PackageManager, pub inline fn run(this: *const ExtractTarball, log: *logger.Log, bytes: []const u8) !Install.ExtractData { - if (!this.skip_verify and this.integrity.tag.isSupported()) { - if (!this.integrity.verify(bytes)) { + if (!this.skip_verify) { + if (this.integrity.tag.isSupported()) { + if (!this.integrity.verify(bytes)) { + log.addErrorFmt( + null, + logger.Loc.Empty, + bun.default_allocator, + "Integrity check failed for tarball: {s}", + .{this.name.slice()}, + ) catch unreachable; + return error.IntegrityCheckFailed; + } + } else if (this.resolution.tag == .npm) { log.addErrorFmt( null, logger.Loc.Empty, bun.default_allocator, - "Integrity check failed for tarball: {s}", + "Missing or unsupported integrity hash for tarball: {s}", .{this.name.slice()}, ) catch unreachable; return error.IntegrityCheckFailed; diff --git a/src/install/lockfile/bun.lock.zig b/src/install/lockfile/bun.lock.zig index de72aaf9a7..bef8b4577d 100644 --- a/src/install/lockfile/bun.lock.zig +++ b/src/install/lockfile/bun.lock.zig @@ -1868,6 +1868,10 @@ pub fn parseIntoBinaryLockfile( }; pkg.meta.integrity = Integrity.parse(integrity_str); + if (integrity_str.len > 0 and !pkg.meta.integrity.tag.isSupported()) { + try log.addError(source, integrity_expr.loc, "Unsupported integrity hash algorithm"); + return error.InvalidPackageInfo; + } }, inline .git, .github => |tag| { // .bun-tag diff --git a/src/install/migration.zig b/src/install/migration.zig index 3ca057ee54..05771c3949 100644 --- a/src/install/migration.zig +++ b/src/install/migration.zig @@ -541,13 +541,15 @@ pub fn migrateNPMLockfile( .false; } else .false, - .integrity = if (pkg.get("integrity")) |integrity| - Integrity.parse( - integrity.asString(this.allocator) orelse - return error.InvalidNPMLockfile, - ) - else - Integrity{}, + .integrity = if (pkg.get("integrity")) |integrity| blk: { + const integrity_str = integrity.asString(this.allocator) orelse + return error.InvalidNPMLockfile; + const parsed = Integrity.parse(integrity_str); + if (integrity_str.len > 0 and !parsed.tag.isSupported()) { + return error.InvalidNPMLockfile; + } + break :blk parsed; + } else Integrity{}, }, .bin = if (pkg.get("bin")) |bin| bin: { // we already check these conditions during counting diff --git a/src/install/pnpm.zig b/src/install/pnpm.zig index 12070f593f..590c8aad58 100644 --- a/src/install/pnpm.zig +++ b/src/install/pnpm.zig @@ -613,6 +613,9 @@ pub fn migratePnpmLockfile( }; pkg.meta.integrity = Integrity.parse(integrity_str); + if (integrity_str.len > 0 and !pkg.meta.integrity.tag.isSupported()) { + return invalidPnpmLockfile(); + } } } diff --git a/src/install/yarn.zig b/src/install/yarn.zig index 8bac336db0..c01672a653 100644 --- a/src/install/yarn.zig +++ b/src/install/yarn.zig @@ -997,10 +997,14 @@ pub fn migrateYarnLockfile( } else .all, .man_dir = String{}, .has_install_script = .false, - .integrity = if (entry.integrity) |integrity| - Integrity.parse(integrity) - else - Integrity{}, + .integrity = if (entry.integrity) |integrity| blk: { + const parsed = Integrity.parse(integrity); + if (integrity.len > 0 and !parsed.tag.isSupported()) { + try log.addError(null, logger.Loc.Empty, "Unsupported integrity hash algorithm in yarn.lock"); + return error.InvalidYarnLock; + } + break :blk parsed; + } else Integrity{}, }, .bin = Bin.init(), .scripts = .{}, diff --git a/test/cli/install/unsupported-integrity-hash.test.ts b/test/cli/install/unsupported-integrity-hash.test.ts new file mode 100644 index 0000000000..780b4dde1c --- /dev/null +++ b/test/cli/install/unsupported-integrity-hash.test.ts @@ -0,0 +1,126 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, tempDir } from "harness"; + +test("lockfile with unsupported integrity hash algorithm should fail", async () => { + using dir = tempDir("unsupported-integrity", { + "package.json": JSON.stringify({ + name: "test-unsupported-integrity", + dependencies: { + "is-number": "7.0.0", + }, + }), + "bun.lock": JSON.stringify( + { + lockfileVersion: 1, + configVersion: 1, + workspaces: { + "": { + name: "test-unsupported-integrity", + dependencies: { + "is-number": "7.0.0", + }, + }, + }, + packages: { + "is-number": ["is-number@7.0.0", "", {}, "md5-AAAAAAAAAA=="], + }, + }, + null, + 2, + ), + }); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "install", "--frozen-lockfile"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + stdout: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("Unsupported integrity hash algorithm"); + expect(exitCode).toBe(1); +}); + +test("lockfile with valid integrity hash algorithm should succeed", async () => { + // First, create a real lockfile by installing + using dir = tempDir("valid-integrity", { + "package.json": JSON.stringify({ + name: "test-valid-integrity", + dependencies: { + "is-number": "7.0.0", + }, + }), + }); + + // Run install to generate a valid lockfile + await using installProc = Bun.spawn({ + cmd: [bunExe(), "install"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + stdout: "pipe", + }); + + const installExitCode = await installProc.exited; + expect(installExitCode).toBe(0); + + // Now run with --frozen-lockfile to verify it works + await using proc = Bun.spawn({ + cmd: [bunExe(), "install", "--frozen-lockfile"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + stdout: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).not.toContain("Unsupported integrity hash algorithm"); + expect(exitCode).toBe(0); +}); + +test("lockfile with garbage integrity string should fail", async () => { + using dir = tempDir("garbage-integrity", { + "package.json": JSON.stringify({ + name: "test-garbage-integrity", + dependencies: { + "is-number": "7.0.0", + }, + }), + "bun.lock": JSON.stringify( + { + lockfileVersion: 1, + configVersion: 1, + workspaces: { + "": { + name: "test-garbage-integrity", + dependencies: { + "is-number": "7.0.0", + }, + }, + }, + packages: { + "is-number": ["is-number@7.0.0", "", {}, "not-a-real-hash"], + }, + }, + null, + 2, + ), + }); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "install", "--frozen-lockfile"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + stdout: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("Unsupported integrity hash algorithm"); + expect(exitCode).toBe(1); +});