diff --git a/src/install/PackageManager/install_with_manager.zig b/src/install/PackageManager/install_with_manager.zig index 86f026bb47..38a049ad09 100644 --- a/src/install/PackageManager/install_with_manager.zig +++ b/src/install/PackageManager/install_with_manager.zig @@ -1561,11 +1561,11 @@ fn handleSecurityAdvisories(manager: *PackageManager, ipc_data: []const u8, pack switch (advisory.level) { .fatal => { has_fatal = true; - Output.pretty(" fatal: {s}\n", .{advisory.package}); + Output.pretty(" FATAL: {s}\n", .{advisory.package}); }, .warn => { has_warn = true; - Output.pretty(" warning: {s}\n", .{advisory.package}); + Output.pretty(" WARN: {s}\n", .{advisory.package}); }, } diff --git a/test/cli/install/bun-install-security-provider.test.ts b/test/cli/install/bun-install-security-provider.test.ts index d686dc18bd..3e8dcb4861 100644 --- a/test/cli/install/bun-install-security-provider.test.ts +++ b/test/cli/install/bun-install-security-provider.test.ts @@ -410,18 +410,6 @@ describe("Process Behavior", () => { expect(err).toContain("Security provider exited with code 42 without sending data"); }, }); - - // run("provider async timeout", { - // testTimeout: 30_000 + 5_000, - // scanner: async () => { - // await new Promise(resolve => setTimeout(resolve, 30_000)); - // return []; - // }, - // expectedExitCode: 1, - // expect: ({ err }) => { - // expect(err).toMatchInlineSnapshot(`"Security provider timed out after 30 seconds"`); - // }, - // }); }); describe("Large Data Handling", () => { @@ -467,49 +455,6 @@ describe("Large Data Handling", () => { }); }); -// describe("Warning Level Advisories", () => { -// test("only warning level advisories", { -// scanner: async ({ packages }) => [ -// { -// package: packages[0].name, -// description: "This is just a warning", -// level: "warn", -// url: "https://example.com/warning", -// }, -// ], -// expectedExitCode: 0, // Should continue with warnings -// expect: ({ out }) => { -// expect(out).toContain("WARN: bar"); -// expect(out).toContain("This is just a warning"); -// expect(out).toContain("Security warnings found. Continuing anyway..."); -// expect(out).not.toContain("Installation cancelled"); -// }, -// }); - -// test("mixed fatal and warn advisories", { -// scanner: async ({ packages }) => [ -// { -// package: packages[0].name, -// description: "Warning advisory", -// level: "warn", -// url: "https://example.com/warning", -// }, -// { -// package: packages[0].name, -// description: "Fatal advisory", -// level: "fatal", -// url: "https://example.com/fatal", -// }, -// ], -// fails: true, -// expect: ({ out }) => { -// expect(out).toContain("WARN: bar"); -// expect(out).toContain("FATAL: bar"); -// expect(out).toContain("Installation cancelled due to fatal security issues"); -// }, -// }); -// }); - describe("Multiple Package Scanning", () => { test("multiple packages scanned", { packages: ["bar", "qux"], @@ -600,378 +545,7 @@ describe("Edge Cases", () => { }); }); -// only npm supported currently -// test("receives transitive dependencies", { -// packages: ["depends-on-monkey"], // This package depends on monkey -// expectedExitCode: 0, -// scanner: async ({ packages }) => { -// console.log("Total packages received:", packages.length); -// for (const pkg of packages) console.log("Scanning:", pkg.name); -// return []; -// }, -// expect: ({ out }) => { -// expect(out).toContain("Total packages received:"); -// expect(out).toContain("Scanning: depends-on-monkey"); -// expect(out).toContain("Scanning: monkey"); -// }, -// }); - -describe("Workspaces", () => { - test("scanner receives all workspace packages", { - scanner: async ({ packages }) => { - console.log("Workspace packages:"); - for (const pkg of packages) { - console.log(`- ${pkg.name}@${pkg.version} (${pkg.requestedRange || "direct"})`); - } - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write("package.json", { - name: "root-workspace", - version: "1.0.0", - workspaces: ["packages/*"], - }); - - await write("packages/app/package.json", { - name: "@workspace/app", - version: "1.0.0", - dependencies: { - "@workspace/lib": "workspace:*", - "bar": "^0.0.2", - }, - }); - - await write("packages/lib/package.json", { - name: "@workspace/lib", - version: "1.0.0", - dependencies: { - "qux": "^0.0.2", - }, - }); - - // The test will run install and the scanner should see all packages - expect(out).toContain("Workspace packages:"); - expect(out).toContain("bar@0.0.2"); - expect(out).toContain("qux@0.0.2"); - }, - }); - - test("install workspace package using add command", { - scanner: async ({ packages }) => { - console.log("Adding workspace package:"); - for (const pkg of packages) { - console.log(`- ${pkg.name}@${pkg.version} (${pkg.requestedRange || "direct"})`); - // Check if this package is from a workspace - if (!pkg.tarball || pkg.tarball === "") { - console.log(" ^ This appears to be a workspace package"); - } - } - return []; - }, - packages: ["@workspace/utils"], // Simulating: bun add @workspace/utils - expectedExitCode: 0, - expect: async ({ out }) => { - // Set up workspace structure - await write("../package.json", { - name: "my-monorepo", - workspaces: ["packages/*"], - }); - - await write("../packages/utils/package.json", { - name: "@workspace/utils", - version: "2.0.0", - dependencies: { - "qux": "^0.0.2", - }, - }); - - await write("../packages/app/package.json", { - name: "@workspace/app", - version: "1.0.0", - dependencies: { - "bar": "^0.0.2", - }, - }); - - // Test harness will create package.json in package_dir - // But we need to indicate we're in a workspace - await write("../../package.json", { - name: "my-monorepo", - workspaces: ["packages/*"], - }); - - expect(out).toContain("Adding workspace package:"); - expect(out).toContain("@workspace/utils"); - }, - }); - - test("scanner can flag workspace package vulnerabilities", { - scanner: async ({ packages }) => { - const workspacePkg = packages.find(p => p.name === "@workspace/lib"); - if (workspacePkg) { - return [ - { - package: workspacePkg.name, - description: "Security issue in workspace package", - level: "fatal", - url: "https://example.com/workspace-vuln", - }, - ]; - } - return []; - }, - fails: true, - expectedExitCode: 1, - expect: async ({ out }) => { - await write("package.json", { - name: "root", - workspaces: ["packages/*"], - }); - - await write("packages/lib/package.json", { - name: "@workspace/lib", - version: "1.0.0", - }); - - await write("packages/app/package.json", { - name: "@workspace/app", - dependencies: { - "@workspace/lib": "workspace:*", - }, - }); - - expect(out).toContain("FATAL: @workspace/lib"); - expect(out).toContain("Security issue in workspace package"); - }, - }); - - test("install workspace B from within workspace A", { - scanner: async ({ packages }) => { - console.log("Installing sibling workspace:"); - for (const pkg of packages) { - console.log(`Package: ${pkg.name}@${pkg.version}`); - console.log(` - registryUrl: ${pkg.registryUrl || "none"}`); - console.log(` - requestedRange: ${pkg.requestedRange}`); - - // Workspace packages may have empty registryUrl or a special workspace: protocol - if (pkg.name.includes("workspace-b")) { - console.log(` --> Found workspace B!`); - } - } - return []; - }, - packages: ["workspace-b"], // Simulating: cd workspace-a && bun add workspace-b - expectedExitCode: 0, - expect: async ({ out }) => { - // Create a monorepo with two workspaces - await write("../../package.json", { - name: "monorepo", - private: true, - workspaces: ["*"], - }); - - // Create workspace-a (we're "inside" this one) - await write("../workspace-a/package.json", { - name: "workspace-a", - version: "1.0.0", - }); - - // Create workspace-b (we're installing this) - await write("../workspace-b/package.json", { - name: "workspace-b", - version: "2.0.0", - dependencies: { - "bar": "^0.0.2", - }, - }); - - // Test simulates: cd workspace-a && bun add workspace-b - // The security scanner should see workspace-b being installed - - expect(out).toContain("Installing sibling workspace:"); - expect(out).toContain("workspace-b@2.0.0"); - expect(out).toContain("Found workspace B!"); - }, - }); -}); - -describe("Local Packages", () => { - test("scanner receives local file dependencies", { - scanner: async ({ packages }) => { - console.log("Packages from local sources:"); - for (const pkg of packages) { - if (pkg.registryUrl?.startsWith("file:")) { - console.log(`- Local: ${pkg.name} from ${pkg.registryUrl}`); - } - } - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write("local-pkg/package.json", { - name: "local-package", - version: "1.0.0", - dependencies: { - "bar": "^0.0.2", - }, - }); - - await write("package.json", { - name: "test-app", - dependencies: { - "local-package": "file:./local-pkg", - }, - }); - - expect(out).toContain("Local: local-package"); - }, - }); - - test("scanner flags vulnerabilities in local packages", { - scanner: async ({ packages }) => { - const localPkg = packages.find(p => p.name === "vulnerable-local"); - if (localPkg) { - return [ - { - package: localPkg.name, - description: "Local package contains malicious code", - level: "fatal", - url: "https://example.com/local-malware", - }, - ]; - } - return []; - }, - fails: true, - expect: async ({ out }) => { - await write("malicious/package.json", { - name: "vulnerable-local", - version: "1.0.0", - }); - - await write("package.json", { - name: "app", - dependencies: { - "vulnerable-local": "file:./malicious", - }, - }); - - expect(out).toContain("FATAL: vulnerable-local"); - expect(out).toContain("Local package contains malicious code"); - }, - }); - - test("scanner with relative path dependencies", { - scanner: async ({ packages }) => { - for (const pkg of packages) { - if (pkg.name === "sibling-package") { - console.log(`Found relative dependency: ${pkg.name}`); - } - } - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write("../sibling/package.json", { - name: "sibling-package", - version: "1.0.0", - }); - - await write("package.json", { - name: "app", - dependencies: { - "sibling-package": "file:../sibling", - }, - }); - - expect(out).toContain("Found relative dependency: sibling-package"); - }, - }); -}); - -describe("Scoped Packages", () => { - test("scanner handles scoped packages correctly", { - scanner: async ({ packages }) => { - console.log("Scoped packages:"); - for (const pkg of packages) { - if (pkg.name.startsWith("@")) { - console.log(`- Scoped: ${pkg.name} (${pkg.version})`); - } - } - return []; - }, - packages: ["@barn/moo", "@scope/package"], - expectedExitCode: 0, - expect: ({ out }) => { - expect(out).toContain("Scoped: @barn/moo"); - expect(out).toContain("Scoped: @scope/package"); - }, - }); - - test("scanner with private scoped packages", { - scanner: async ({ packages }) => { - const privatePkgs = packages.filter(p => p.name.startsWith("@private/") || p.name.startsWith("@company/")); - - if (privatePkgs.length > 0) { - console.log(`Found ${privatePkgs.length} private packages`); - for (const pkg of privatePkgs) { - console.log(`- ${pkg.name} from ${pkg.registryUrl}`); - } - } - - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write( - ".npmrc", - ` -@company:registry=https://npm.company.com -@private:registry=https://private-registry.com -`, - ); - - await write("package.json", { - name: "test-private", - dependencies: { - "@company/internal-tool": "^1.0.0", - "@private/secret-lib": "^2.0.0", - "bar": "^0.0.2", - }, - }); - - expect(out).toContain("private packages"); - }, - }); -}); - describe("Package Resolution", () => { - test("scanner receives aliased packages", { - scanner: async ({ packages }) => { - console.log("Package aliases:"); - for (const pkg of packages) { - if (pkg.requestedRange?.startsWith("npm:")) { - console.log(`- ${pkg.name}: aliased from ${pkg.requestedRange}`); - } - } - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write("package.json", { - name: "test-aliases", - dependencies: { - "my-bar": "npm:bar@^0.0.2", - "legacy-qux": "npm:qux@0.0.1", - }, - }); - - expect(out).toContain("Package aliases:"); - expect(out).toContain("aliased from npm:"); - }, - }); - test("scanner with version ranges", { scanner: async ({ packages }) => { console.log("Version ranges:"); @@ -1004,219 +578,3 @@ describe("Package Resolution", () => { }, }); }); - -describe("Private Registries", () => { - test("scanner detects packages from private registries", { - scanner: async ({ packages }) => { - console.log("Registry URLs:"); - for (const pkg of packages) { - if (pkg.registryUrl && !pkg.registryUrl.includes("registry.npmjs.org")) { - console.log(`- ${pkg.name} from private: ${pkg.registryUrl}`); - } - } - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write( - ".npmrc", - ` -@mycompany:registry=https://npm.mycompany.com -//npm.mycompany.com/:_authToken=secret-token -`, - ); - - await write("package.json", { - name: "test-private-registry", - dependencies: { - "@mycompany/internal": "^1.0.0", - "bar": "^0.0.2", - }, - }); - - expect(out).toContain("from private:"); - }, - }); - - test("scanner with multiple registries", { - scanner: async ({ packages }) => { - const registries = new Set(packages.map(p => p.registryUrl).filter(Boolean)); - console.log(`Packages from ${registries.size} different registries`); - for (const reg of registries) { - console.log(`- ${reg}`); - } - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write( - ".npmrc", - ` -@corp:registry=https://registry.corp.com -@vendor:registry=https://vendor.npmjs.com -`, - ); - - await write("package.json", { - name: "multi-registry", - dependencies: { - "@corp/lib": "^1.0.0", - "@vendor/tool": "^2.0.0", - "bar": "^0.0.2", - }, - }); - - expect(out).toContain("different registries"); - }, - }); -}); - -describe("Complex Scenarios", () => { - test("scanner with mixed dependency types", { - scanner: async ({ packages }) => { - const stats = { - registry: 0, - git: 0, - local: 0, - workspace: 0, - tarball: 0, - github: 0, - }; - - for (const pkg of packages) { - const range = pkg.requestedRange || ""; - if (range.startsWith("file:")) stats.local++; - else if (range.includes("git+") || range.includes("git@")) stats.git++; - else if (range.includes(".tgz") || range.includes(".tar.gz")) stats.tarball++; - else if (range.includes("github:") || range.includes("/")) stats.github++; - else if (range.startsWith("workspace:")) stats.workspace++; - else stats.registry++; - } - - console.log("Dependency sources:", JSON.stringify(stats)); - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write("package.json", { - name: "complex-app", - workspaces: ["packages/*"], - dependencies: { - "bar": "^0.0.2", - "git-pkg": "git+https://github.com/example/repo.git", - "local-pkg": "file:./local", - "tarball-pkg": "https://example.com/pkg.tgz", - "gh-pkg": "user/repo", - }, - }); - - await write("packages/workspace-pkg/package.json", { - name: "@app/workspace-pkg", - version: "1.0.0", - }); - - await write("local/package.json", { - name: "local-pkg", - version: "1.0.0", - }); - - expect(out).toContain("Dependency sources:"); - - expect().fail("Todo"); - }, - }); - - test("scanner handles monorepo with cross-dependencies", { - scanner: async ({ packages }) => { - const workspacePkgs = packages.filter(p => p.name.startsWith("@monorepo/")); - console.log(`Found ${workspacePkgs.length} workspace packages`); - - const deps = new Map(); - for (const pkg of workspacePkgs) { - // In real scenario, would parse package.json to find deps - console.log(`- ${pkg.name}`); - } - - return []; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write("package.json", { - name: "monorepo-root", - workspaces: ["apps/*", "libs/*"], - }); - - await write("libs/core/package.json", { - name: "@monorepo/core", - version: "1.0.0", - }); - - await write("libs/utils/package.json", { - name: "@monorepo/utils", - version: "1.0.0", - dependencies: { - "@monorepo/core": "workspace:*", - }, - }); - - await write("apps/web/package.json", { - name: "@monorepo/web", - version: "1.0.0", - dependencies: { - "@monorepo/core": "workspace:*", - "@monorepo/utils": "workspace:*", - "bar": "^0.0.2", - }, - }); - - expect(out).toContain("workspace packages"); - expect(out).toContain("@monorepo/"); - }, - }); - - test("scanner with conditional vulnerability detection", { - scanner: async ({ packages }) => { - const advisories: Bun.Security.Advisory[] = []; - - // Flag old versions - const oldPackages = packages.filter(p => p.version && p.version.startsWith("0.")); - - for (const pkg of oldPackages) { - advisories.push({ - package: pkg.name, - description: `Package ${pkg.name} is using pre-1.0 version which may be unstable`, - level: "warn", - url: "https://example.com/stability", - }); - } - - // Flag git dependencies - const gitDeps = packages.filter(p => p.requestedRange?.includes("git")); - - for (const pkg of gitDeps) { - advisories.push({ - package: pkg.name, - description: "Git dependencies bypass registry security checks", - level: "warn", - url: "https://example.com/git-deps", - }); - } - - return advisories; - }, - expectedExitCode: 0, - expect: async ({ out }) => { - await write("package.json", { - name: "test-conditional", - dependencies: { - "bar": "^0.0.2", - "git-dep": "git+https://github.com/example/repo.git", - }, - }); - - expect(out).toContain("WARN:"); - expect(out).toContain("pre-1.0 version"); - expect(out).toContain("Git dependencies"); - }, - }); -});