Security Scanner API

Bun's package manager can scan packages before installing them, to catch supply chain attacks and known vulnerabilities.


Quick Start#

Configure a security scanner in your bunfig.toml:

bunfig.toml
[install.security]
scanner = "@oven/bun-security-scanner" # example name, replace with your scanner's package

With a scanner configured, Bun:

  • Scans all packages before installation
  • Displays security warnings and advisories
  • Cancels installation if the scanner finds fatal advisories

How It Works#

Security scanners analyze packages during bun install, bun add, and every other command that installs packages, such as bun patch, bun link and bun dedupe. They can detect:

  • Known security vulnerabilities (CVEs)
  • Malicious packages
  • License compliance issues

Every command sends the scanner all packages of the project, with one exception. bun add <package>, bun install <package>, and bun update <package> in a project that has a lockfile and a node_modules directory send only the packages they name, the dependencies of those packages, and the packages whose version the command changes. Run bun install to send everything.

Security Levels#

Scanners report issues at two severity levels:

  • fatal - Installation stops immediately, exits with non-zero code
  • warn - In interactive terminals, prompts to continue; in CI, exits immediately

Using Pre-built Scanners#

Security companies publish Bun security scanners as npm packages.

Installing a Scanner#

Install a security scanner from npm:

terminal
bun add -d @oven/bun-security-scanner

@oven/bun-security-scanner is an example package name, not a real package. Replace it with the scanner you want to use, and consult that scanner's documentation for the exact package name and installation instructions. You install most scanners with bun add.

Configuring the Scanner#

After installation, configure it in your bunfig.toml:

bunfig.toml
[install.security]
scanner = "@oven/bun-security-scanner" # example name, replace with your scanner's package

Enterprise Configuration#

Some enterprise scanners take authentication and other configuration from environment variables:

terminal
# This might go in ~/.bashrc, for example
export SECURITY_API_KEY="your-api-key"

# The scanner will now use these credentials automatically
bun install

Consult your scanner's documentation for which environment variables to set and any other required configuration.

Authoring your own scanner#

For a complete example with tests and CI setup, see the official template: github.com/oven-sh/security-scanner-template

A scanner is a module that exports a scanner object implementing Bun.Security.Scanner. Bun calls scan() with every package it is about to install, including transitive dependencies, and cancels the install if any returned advisory is fatal:

scanner.ts
export const scanner: Bun.Security.Scanner = {
  version: "1",
  async scan({ packages }) {
    const advisories: Bun.Security.Advisory[] = [];

    for (const pkg of packages) {
      if (pkg.name === "event-stream" && Bun.semver.satisfies(pkg.version, ">=3.3.6 <4.0.0")) {
        advisories.push({
          level: "fatal",
          package: pkg.name,
          url: "https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident",
          description: "event-stream 3.3.6 shipped a compromised dependency",
        });
      }
    }

    return advisories;
  },
};

Each entry in packages describes one resolved package:

FieldDescription
nameThe package name.
versionThe exact version (4.1.2) of a registry package. For other sources, the resolved specifier as in bun.lock: github:user/repo#<commit>, https://example.com/pkg.tgz, ./pkg.tgz, file:pkg, link:pkg.
requestedRangeWhat package.json or the command line asked for: ^4.0.0, latest, github:user/repo, file:./pkg.
tarballThe URL (or local path) of the tarball. For github: dependencies, the GitHub API tarball URL of the resolved commit. Empty for git+ dependencies and local directories.

Packages from every source are included: registry, git, GitHub, remote and local tarballs, local directories. Workspace packages are part of the project and are not included. Outside a registry version is not semver; Bun.semver.satisfies() returns false for it.

The user:password@ part and the query string of a URL are removed from all three fields. The tarball of a registry package keeps its query string, because some registries sign their tarball URLs.