# pnpm Bulk Audit

A workaround for `pnpm audit` failing with HTTP 410 after npm retired the legacy
audit endpoints. This tool calls the replacement
[bulk advisory endpoint](https://api-docs.npmjs.com/#tag/Audit) directly.

**Background:** In April 2026 npm retired `/-/npm/v1/security/audits` (and the
`/quick` variant). `pnpm audit` on all 10.x and early 11.x versions is broken
because it still calls the old endpoints. See
[pnpm/pnpm#11265](https://github.com/pnpm/pnpm/issues/11265). This tool bridges
the gap until pnpm ships a fix.

## Quick start

Add `@knime/utils` as a dev dependency (it is already present in the monorepo),
then call the binary directly:

```jsonc
// package.json
{
  "scripts": {
    "audit": "pnpm-bulk-audit",
  },
  "devDependencies": {
    "@knime/utils": "workspace:*",
  },
}
```

Then run:

```bash
pnpm run audit
```

## CLI options

```
Usage: pnpm-bulk-audit [options]

Options:
  --lockfile <path>     Path to pnpm-lock.yaml (default: ./pnpm-lock.yaml)
  --registry <url>      npm registry URL (default: https://registry.npmjs.org)
  --severity <level>    Minimum severity: low, moderate, high, critical (default: low)
  --prod, -P            Only report vulnerabilities in production dependencies
  --json                Output raw JSON
  --no-ignore           Skip reading ignoreCves/ignoreGhsas from config
  --help, -h            Show this help
```

## Prod vs dev classification

Each vulnerability is annotated with `[prod]` or `[dev]` based on whether the
affected package is reachable from any importer's `dependencies` (as opposed to
`devDependencies`). The classifier walks the full dependency graph in the
lockfile, following workspace links transitively.

Use `--prod` (or `-P`) to only report vulnerabilities in production
dependencies.

## Ignore lists

The tool reads `auditConfig.ignoreCves` and `auditConfig.ignoreGhsas` from two
places (both are merged):

### pnpm-workspace.yaml

```yaml
auditConfig:
  ignoreCves:
    - CVE-2026-4800
  ignoreGhsas:
    - GHSA-78h3-pg4x-j8cv
```

### package.json

```jsonc
{
  "pnpm": {
    "auditConfig": {
      "ignoreCves": ["CVE-2026-4800"],
      "ignoreGhsas": ["GHSA-78h3-pg4x-j8cv"],
    },
  },
}
```

**GHSA IDs** are matched directly from the advisory URL returned by the npm
registry.
**CVE IDs** require an extra lookup via the GitHub advisories API
(`api.github.com/advisories/{ghsa}`) because the npm bulk endpoint does not
include CVE identifiers in its response.

Use `--no-ignore` to skip all ignore-list processing.

## Programmatic usage

```ts
import { audit, formatAuditReport, readAuditConfig } from "@knime/utils/audit";

const config = readAuditConfig(".");
const result = await audit({
  lockfilePath: "./pnpm-lock.yaml",
  minSeverity: "high",
  auditConfig: config,
});
console.log(formatAuditReport(result));
```

## How it works

1. Parses the `packages:` section of `pnpm-lock.yaml` to extract every
   `name@version` pair.
2. POSTs them as `{ "pkg": ["version", …] }` to the npm bulk advisory endpoint
   (`/-/npm/v1/security/advisories/bulk`).
3. Filters the response by severity and ignore lists.
4. Exits with code 1 if any vulnerabilities remain, 0 otherwise.
