@nebzdev/bun-security-scanner
v1.1.0
Published
Bun security scanner powered by Google's OSV vulnerability database
Maintainers
Readme
bun-security-scanner
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
📦 Installation
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.
🔀 Backends
The scanner ships with two backends, controlled by the SCANNER_BACKEND environment variable.
OSV (default)
Queries Google's OSV database — free, no credentials required.
[install.security]
scanner = "@nebzdev/bun-security-scanner"Snyk
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-id🛡️ How it works
When 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
⚠️ Advisory levels
| 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 |
⚙️ Configuration
All options are set via environment variables — in your shell, or in a .env file at the project root (Bun loads it automatically).
Shared
| Variable | Default | Description |
|----------|---------|-------------|
| SCANNER_BACKEND | osv | Backend to use: osv or snyk |
OSV backend
| 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 |
Snyk backend
| 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 |
Fail-open vs fail-closed
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=true🗄️ Cache
Results 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 install🙈 Ignore file
Not 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.
Format
# .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."Behaviour
| 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
Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| BUN_SECURITY_IGNORE_FILE | .bun-security-ignore | Path to the ignore file |
| OSV_NO_IGNORE | false | Disable all ignore file processing |
Committing the file
The ignore file should be committed alongside your lockfile. It documents deliberate risk-acceptance decisions for your whole team and CI.
🛠️ Development
Setup
git clone https://github.com/muneebs/bun-security-scanner.git
cd bun-security-scanner
bun install
bunx lefthook installLocal development
Point bunfig.toml directly at the entry file using an absolute or relative path:
[install.security]
scanner = "../bun-security-scanner/src/index.ts"Commands
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 canProject structure
bun-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.jsonBackend comparison
| | 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 |
⚠️ Limitations
- 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.
📄 License
MIT © Muneeb Samuels
