This commit is contained in:
Alistair Smith
2025-07-30 13:42:56 -07:00
parent db5fcf16da
commit ccc8a24081
2 changed files with 50 additions and 39 deletions

View File

@@ -1042,8 +1042,8 @@ fn performSecurityScanAfterResolution(
if (manager.update_requests.len == 0) return;
var json_buf = std.ArrayList(u8).init(manager.allocator);
defer json_buf.deinit();
var writer = json_buf.writer();
defer json_buf.deinit();
const string_buf = manager.lockfile.buffers.string_bytes.items;
const packages = manager.lockfile.packages.slice();
@@ -1061,13 +1061,22 @@ fn performSecurityScanAfterResolution(
const resolution = resolutions[package_id];
const name = names[package_id].slice(string_buf);
var version_buf: [256]u8 = undefined;
const version = switch (resolution.tag) {
.npm => resolution.value.npm.version.slice(string_buf),
.npm => std.fmt.bufPrint(&version_buf, "{}", .{resolution.value.npm.version.fmt(string_buf)}) catch "",
.local_tarball => resolution.value.local_tarball.slice(string_buf),
.remote_tarball => resolution.value.remote_tarball.slice(string_buf),
.folder => resolution.value.folder.slice(string_buf),
.git => resolution.value.git.url.slice(string_buf),
.github => resolution.value.github.url.slice(string_buf),
.git => blk: {
var stream = std.io.fixedBufferStream(&version_buf);
resolution.value.git.formatAs("git+", string_buf, "", .{}, stream.writer()) catch break :blk "";
break :blk stream.getWritten();
},
.github => blk: {
var stream = std.io.fixedBufferStream(&version_buf);
resolution.value.github.formatAs("github:", string_buf, "", .{}, stream.writer()) catch break :blk "";
break :blk stream.getWritten();
},
.workspace => "workspace",
.symlink => "link",
else => continue,
@@ -1242,16 +1251,16 @@ fn performSecurityScanAfterResolution(
const item_obj = item.data.e_object;
const name_expr = item_obj.get("name") orelse {
Output.errGeneric("Security advisory at index {d} missing required 'name' field", .{i});
const name_expr = item_obj.get("package") orelse {
Output.errGeneric("Security advisory at index {d} missing required 'package' field", .{i});
Global.exit(1);
};
const name_str = name_expr.asString(manager.allocator) orelse {
Output.errGeneric("Security advisory at index {d} 'name' field must be a string", .{i});
Output.errGeneric("Security advisory at index {d} 'package' field must be a string", .{i});
Global.exit(1);
};
if (name_str.len == 0) {
Output.errGeneric("Security advisory at index {d} 'name' field cannot be empty", .{i});
Output.errGeneric("Security advisory at index {d} 'package' field cannot be empty", .{i});
Global.exit(1);
}

View File

@@ -7,6 +7,7 @@ import {
dummyRegistry,
package_dir,
read,
root_url,
setHandler,
write,
} from "./dummy.registry.js";
@@ -19,24 +20,25 @@ afterEach(dummyAfterEach);
function run(
name: string,
options: {
scanner: Bun.Install.Security.Provider["onInstall"];
scanner: Bun.Install.Security.Provider["onInstall"] | string;
fails: boolean;
expect?: (std: { out: string; err: string }) => void | Promise<void>;
},
) {
test(name, async () => {
const urls: string[] = [];
setHandler(dummyRegistry(urls, { "0.0.5": { as: "0.0.5" } }));
setHandler(dummyRegistry(urls));
await write(
"./scanner.ts",
`
export default {
version: "1",
onInstall: ${options.scanner.toString()},
} satisfies Bun.Install.Security.Provider;
`,
);
if (typeof options.scanner === "string") {
await write("./scanner.ts", options.scanner);
} else {
const s = `export const provider = {
version: "1",
onInstall: ${options.scanner.toString()},
};`;
await write("./scanner.ts", s);
}
const bunfig = await read("./bunfig.toml").text();
await write("./bunfig.toml", bunfig + "\n" + "[install.security]" + "\n" + 'provider = "./scanner.ts"');
@@ -47,24 +49,20 @@ function run(
dependencies: {},
});
const pkg = "pkg";
const { out, err } = await runBunInstall(bunEnv, package_dir, {
packages: [pkg],
packages: ["bar"],
allowErrors: true,
allowWarnings: false,
savesLockfile: false,
expectedExitCode: 1,
});
expect(urls).toEqual([]);
expect(out).toContain(`Security scanner is checking packages: ${pkg}`);
if (options.fails) {
expect(err).toContain("Installation cancelled due to fatal security advisories");
expect(out).toContain("Installation cancelled due to fatal security issues");
}
expect(urls).toEqual([root_url + "/bar", root_url + "/bar-0.0.2.tgz"]);
await options.expect?.({ out, err });
});
}
@@ -98,18 +96,22 @@ run("expect output to contain the advisory", {
run("stdout contains all input package metadata", {
fails: true,
scanner: async ({ packages }) => [
{
package: packages[0].name,
description: "Advisory 1 description",
level: "fatal",
url: "https://example.com/advisory-1",
},
],
scanner: async ({ packages }) => {
console.log(JSON.stringify(packages));
return [
{
package: packages[0].name,
description: "Advisory 1 description",
level: "fatal",
url: "https://example.com/advisory-1",
},
];
},
expect: ({ out }) => {
expect(out).toContain('"version": "1.0.0"');
expect(out).toContain('"name": "pkg"');
expect(out).toContain('"requestedRange": "latest"');
expect(out).toContain('"registryUrl": "https://registry.npmjs.org"');
expect(out).toContain('\"version\":\"0.0.2\"');
expect(out).toContain('\"name\":\"bar\"');
expect(out).toContain('\"requestedRange\":\"^0.0.2\"');
expect(out).toContain(`\"registryUrl\":\"${root_url}/\"`);
},
});