A Bun security scanner that checks your dependencies against vulnerability databases before they get installed. Uses Google's OSV database by default β no API keys required.
- π Automatic scanning: runs transparently on every
bun install - β‘ Fast: per-package lockfile cache (24h by default, configurable) means repeat installs skip the network entirely
- π Two backends: OSV (free, no setup) or Snyk (commercial, broader coverage)
- π Fail-open by default: a downed API never blocks your install
- π― CVSS fallback: falls back to score-based severity when a label isn't available
- π Ignore file: suppress false positives and accepted risks with
.bun-security-ignore - βοΈ Configurable: tune behaviour via environment variables
bun add -d @nebzdev/bun-security-scannerThen register it in your project's bunfig.toml:
[install.security]
scanner = "@nebzdev/bun-security-scanner"That's it. The scanner runs automatically on the next bun install.
The scanner ships with two backends, controlled by the SCANNER_BACKEND environment variable.
Queries Google's OSV database β free, no credentials required.
[install.security]
scanner = "@nebzdev/bun-security-scanner"Queries Snyk's vulnerability database β commercial, often surfaces issues earlier. Requires a Snyk account.
# bunfig.toml
[install.security]
scanner = "@nebzdev/bun-security-scanner"# .env
SCANNER_BACKEND=snyk
SNYK_TOKEN=your-token
SNYK_ORG_ID=your-org-idWhen bun install runs, Bun calls the scanner with the full list of packages to be installed. The scanner:
- Filters non-resolvable versions β workspace, git, file, and path dependencies are skipped
- Checks the cache β packages seen within the cache TTL (24h by default) skip the network entirely
- Queries the backend for any uncached packages
- Returns advisories to Bun, which surfaces them as warnings or fatal errors
| Level | Trigger | Bun behaviour |
|---|---|---|
fatal |
CRITICAL or HIGH severity; or CVSS score >= 7.0 | Installation halts |
warn |
MODERATE or LOW severity; or CVSS score < 7.0 | User is prompted; auto-cancelled in CI |
All options are set via environment variables β in your shell, or in a .env file at the project root (Bun loads it automatically).
| Variable | Default | Description |
|---|---|---|
SCANNER_BACKEND |
osv |
Backend to use: osv or snyk |
| Variable | Default | Description |
|---|---|---|
OSV_FAIL_CLOSED |
false |
Throw on network error instead of failing open |
OSV_NO_CACHE |
false |
Always query OSV fresh, bypassing the local cache |
OSV_CACHE_FILE |
.osv.lock |
Path to the cache file |
OSV_CACHE_TTL_MS |
86400000 |
Cache TTL in milliseconds (default: 24 hours) |
OSV_TIMEOUT_MS |
10000 |
Per-request timeout in milliseconds |
OSV_API_BASE |
https://api.osv.dev/v1 |
OSV API base URL |
OSV_NO_IGNORE |
false |
Disable .bun-security-ignore processing |
| Variable | Default | Description |
|---|---|---|
SNYK_TOKEN |
β | Required. Snyk API token |
SNYK_ORG_ID |
β | Required. Snyk organization ID |
SNYK_FAIL_CLOSED |
false |
Throw on network error instead of failing open |
SNYK_NO_CACHE |
false |
Always query Snyk fresh, bypassing the local cache |
SNYK_CACHE_FILE |
.snyk.lock |
Path to the cache file |
SNYK_CACHE_TTL_MS |
86400000 |
Cache TTL in milliseconds (default: 24 hours) |
SNYK_TIMEOUT_MS |
10000 |
Per-request timeout in milliseconds |
SNYK_RATE_LIMIT |
160 |
Max requests per minute (hard cap: 180) |
SNYK_CONCURRENCY |
10 |
Max concurrent connections |
SNYK_API_BASE |
https://api.snyk.io/rest |
Regional endpoint override |
SNYK_API_VERSION |
2024-04-29 |
Snyk REST API version date |
By default the scanner fails open: if the backend is unreachable the scan is skipped and installation proceeds normally. Set OSV_FAIL_CLOSED=true or SNYK_FAIL_CLOSED=true to invert this.
# .env -- strict mode
OSV_FAIL_CLOSED=trueResults are cached per package@version in a lock file at the project root. Because a published package version is immutable, its vulnerability profile is stable within the cache window.
| Backend | Lock file | TTL env var |
|---|---|---|
| OSV | .osv.lock |
OSV_CACHE_TTL_MS |
| Snyk | .snyk.lock |
SNYK_CACHE_TTL_MS |
The default TTL is 24 hours. In CI environments where cold-start scan time is a concern, increase it:
# .env.ci
OSV_CACHE_TTL_MS=604800000 # 7 days
SNYK_CACHE_TTL_MS=604800000 # 7 daysThe lock files are designed to be committed to git. Like a lockfile, committing them means your team and CI share the cache from day one without waiting for a warm-up scan.
git add .osv.lock # or .snyk.lockTo force a fresh scan:
OSV_NO_CACHE=true bun install
# or
SNYK_NO_CACHE=true bun installNot every advisory is actionable. A vulnerability may affect a code path your project doesn't use, have no fix available yet, or be a false positive. The .bun-security-ignore file lets you acknowledge these cases without blocking installs permanently.
# .bun-security-ignore
[[ignore]]
package = "lodash"
advisories = ["GHSA-35jh-r3h4-6jhm"]
reason = "Only affects the cloneDeep path, which we do not use."
expires = "2026-12-31" # optional -- re-surfaces automatically after this date
[[ignore]]
package = "minimist"
advisories = ["*"] # wildcard -- suppress all advisories for this package
reason = "Transitive only, no direct usage, no fix available."| Advisory level | Session type | Effect |
|---|---|---|
fatal matched |
Interactive (no CI=true, stdin is a TTY) |
Downgraded to warn β visible in output but no longer blocks the install |
fatal matched |
CI / non-interactive | Suppressed entirely β logged to stderr but not returned |
warn matched |
Any | Suppressed entirely β logged to stderr but not returned |
All suppressions are logged to stderr so they remain visible in CI output. Ignored advisories are never silently swallowed.
expires-- entries re-activate at UTC midnight on the given date, so you're reminded when to reassessadvisories = ["*"]-- wildcard suppresses all advisories for the packagereason-- encouraged but not required; a notice is printed to stderr if omittedOSV_NO_IGNORE=true-- disables all ignore file processing for strict environmentsBUN_SECURITY_IGNORE_FILE-- override the default.bun-security-ignorepath
| Variable | Default | Description |
|---|---|---|
BUN_SECURITY_IGNORE_FILE |
.bun-security-ignore |
Path to the ignore file |
OSV_NO_IGNORE |
false |
Disable all ignore file processing |
The ignore file should be committed alongside your lockfile. It documents deliberate risk-acceptance decisions for your whole team and CI.
git clone https://github.com/muneebs/bun-security-scanner.git
cd bun-security-scanner
bun install
bunx lefthook installPoint bunfig.toml directly at the entry file using an absolute or relative path:
[install.security]
scanner = "../bun-security-scanner/src/index.ts"bun test # Run all tests
bun run lint # Lint source files
bun run format # Check formatting
bun run format:write # Auto-fix formatting
bun run check # Lint + format check together
bun run check:write # Lint + format, auto-fix what it canbun-security-scanner/
βββ src/
β βββ __tests__/ # Test suite (bun:test)
β βββ snyk/ # Snyk backend
β βββ cache.ts # Lockfile cache (configurable TTL)
β βββ client.ts # OSV API client
β βββ config.ts # OSV constants and env vars
β βββ display.ts # TTY progress spinner
β βββ ignore.ts # .bun-security-ignore loader and matcher
β βββ index.ts # Entry point -- dispatches to OSV or Snyk
β βββ osv.ts # OSV scanner implementation
β βββ scanner.ts # Shared scanner factory (cache + ignore orchestration)
β βββ severity.ts # OSV level classification
βββ dist/ # Compiled output (published to npm)
βββ bunfig.toml
βββ package.json
| OSV | Snyk | |
|---|---|---|
| API key required | No | Yes |
| Batch endpoint | Yes (1000/req) | No (per-package, 180 req/min) |
| Coverage | Community feeds + GitHub Advisory | Snyk's proprietary database |
| Cache file | .osv.lock |
.snyk.lock |
- Only scans npm packages with concrete semver versions.
workspace:,file:,git:, and range-only specifiers are skipped. - OSV aggregates GitHub Advisory, NVD, and other feeds, so coverage may lag slightly behind a vulnerability's public disclosure.
- The OSV batch API has a hard limit of 1,000 queries per request. Larger projects are split across multiple requests automatically.
- Snyk's per-package endpoint is rate-limited to 180 req/min. At that rate, a project with 2,000+ packages will take several minutes on the first scan.
MIT Β© Muneeb Samuels