Skip to content
 
 

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS, Linux, and Windows developer endpoints.

It answers a narrow supply-chain response question: when an advisory names a package, extension, or version, which developer machines show a match in their on-disk metadata right now?

SBOMs help answer what shipped, and EDR helps answer what ran or touched the network, but supply-chain response often needs a different view: messy local state across lockfiles, package-manager metadata, extension manifests, and supported developer-tool configs.

Bumblebee turns that scattered on-disk state into structured NDJSON component records and, when given an exposure catalog, flags exact matches for fast, read-only exposure checks when responders already know what they are looking for.

Scope

  • Single static binary, Go 1.25+, one dependency.
  • Three scan profiles (baseline, project, deep) for different populations and cadences.
  • Reads only the lockfiles, package-manager install metadata, extension manifests, and supported MCP JSON configs listed in docs/inventory-sources.md. No package manager execution (npm ls, pip show, go list, ...) and no source-file reads. MCP host configs can carry environment values and credentials in their env blocks; Bumblebee parses these configs for the server inventory it needs but does not emit those values in its records.

Coverage

Family Emitted ecosystem Sources
npm npm package-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpm npm pnpm-lock.yaml, .pnpm/.../package.json
Yarn npm yarn.lock (Classic + Berry)
Bun npm bun.lock; bun.lockb presence as diagnostic
PyPI (installed) pypi *.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
PyPI (declared) pypi requirements*.txt, Pipfile.lock, poetry.lock, uv.lock, pylock.toml (PEP 751)
Go modules go go.sum, go.mod, go.work.sum, vendor/modules.txt
RubyGems rubygems Gemfile.lock, installed *.gemspec
Composer packagist composer.lock, vendor/composer/installed.json
MCP mcp JSON host configs: mcp.json, .mcp.json, claude_desktop_config.json, mcp_config.json, mcp_settings.json, cline_mcp_settings.json, plus ~/.gemini/settings.json (Gemini CLI / Code Assist) and ~/.claude.json (Claude Code user- and project-scoped mcpServers). These are parsed as JSONC (comments and trailing commas accepted), because VS Code, Kiro and GitLab Duo all ship commented mcp.json templates. Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skills agent-skill skills.sh / vercel-labs/skills lock files: global ~/.agents/.skill-lock.json (or $XDG_STATE_HOME/skills/.skill-lock.json) and project-local skills-lock.json. Loose SKILL.md directories without a lock file are not enumerated.
Editor extensions editor-extension VS Code, Cursor, Windsurf, VSCodium manifests, including each editor's remote/SSH server tree
Browser extensions browser-extension Chromium-family (manifest.json) and Firefox (extensions.json) per profile
Homebrew homebrew Formula INSTALL_RECEIPT.json files and cask .metadata install markers
NuGet nuget ~/.nuget/packages/<id>/<version>/ cache, packages.lock.json, packages.config
Cargo cargo Cargo.lock, ~/.cargo/registry/src/<index>/<crate>-<version>/
Maven / Gradle maven *.gradle.lockfile, pom.xml (declared only), ~/.m2/repository/
SwiftPM swift Package.resolved (v1 and v2/v3 layouts)
CocoaPods cocoapods Podfile.lock (PODS: section)
Dart pub pubspec.lock
Elixir hex mix.lock

Per-ecosystem detail: docs/inventory-sources.md.

Install

Requires Go 1.25+. Exactly one dependency, pelletier/go-toml/v2 (MIT), which has no transitive dependencies of its own.

# Install the latest tagged release into $GOBIN.
go install github.com/perplexityai/bumblebee/cmd/bumblebee@latest

# Or pin a specific tag.
go install github.com/perplexityai/bumblebee/cmd/bumblebee@v0.1.5

To build from a checkout:

go build -o bumblebee ./cmd/bumblebee
go test ./...

Stamp an explicit version at build time:

go build -ldflags "-X main.Version=v0.1.5" -o bumblebee ./cmd/bumblebee

bumblebee version prints the version plus the VCS revision, build time, and Go runtime — so a record emitted in production can be traced back to a specific build. Version precedence: -ldflags override, module version recorded by go install, then the in-tree default tracked in VERSION.

Self-test

After installing, run a built-in end-to-end check against embedded fixtures:

bumblebee selftest
# selftest OK (19 findings in 1ms)

The fixtures live inside the binary, use deliberately fake package names (bumblebee-selftest-evil@0.0.0), and make no network calls. A non-zero exit means the local install can no longer detect what it should — a fast pre-deployment smoke test for fleet rollouts.

Profiles

Bumblebee is a one-shot scanner: each invocation performs a single scan and exits. Cadence is the runner's responsibility (cron, launchd, systemd, MDM, etc.). Each record carries profile and a per-root root_kind so receivers can keep populations separate.

Profile Scans Use for
baseline Common global/user package roots (including the npm -g/Yarn/pnpm/Bun prefixes, per-user Python, conda, Composer, pub, Hex), language toolchains, editor extensions, browser extensions, and MCP configs. Honours GOPATH, CARGO_HOME, NPM_CONFIG_PREFIX and similar relocations. Recurring lightweight inventory via an external runner.
project Configured development directories, such as ~/code, ~/src, or ~/work. Recurring inventory for known project workspaces.
deep Explicit --root paths, including broad roots like $HOME. On-demand incident or campaign checks, usually with --ecosystem, --exposure-catalog, and --findings-only.

baseline and project refuse bare-home roots; only deep walks them. On Windows this also covers drive roots and bare <drive>:\Users\<name> homes. Default roots are platform-aware: on Windows, MCP host configs under %APPDATA%, Chromium-family extensions under %LOCALAPPDATA%, and Firefox profiles under %APPDATA%\Mozilla are resolved in place of their macOS/Linux equivalents.

Quick start

# Baseline global inventory.
bumblebee scan --profile baseline > inventory.ndjson

# Daily project sweep with explicit roots.
bumblebee scan --profile project \
  --root "$HOME/code" \
  --root "$HOME/Developer"

# Limit a run to selected emitted ecosystems.
bumblebee scan --profile baseline \
  --ecosystem npm,pypi \
  --ecosystem go

# On-demand exposure scan against a published advisory.
bumblebee scan --profile deep \
  --root "$HOME" \
  --exposure-catalog ./catalog.json \
  --max-duration 10m

Preview the resolved roots without scanning:

bumblebee roots --profile baseline
# prints "<root_kind>\t<path>" lines

--root is a filesystem path to scan; repeatable, required for deep, optional for the other profiles. --ecosystem is repeatable and comma-separated. --exposure-catalog accepts a JSON file or a directory of *.json catalogs (merged non-recursively, all files must share schema_version). --findings-only requires --exposure-catalog and suppresses package records while keeping findings. --exclude adds a directory name or suffix path to skip (repeatable). --all-users expands per-user default roots across every real user home (/Users/<name> on macOS, <drive>:\Users\<name> on Windows). It is what makes an agent-run scan see anything: a fleet agent runs as root or SYSTEM, and SYSTEM's own profile holds no developer trees, so without it baseline and project resolve to nothing. On Linux it is accepted but has no effect and is reported as a diagnostic, since there is no single canonical home parent to enumerate. bumblebee scan --help lists every flag.

Output

Records are NDJSON, one per line. Diagnostics go to stderr as NDJSON. Each run ends with a scan_summary record; receivers use it to decide whether to promote a run to current state. See docs/transport.md for HTTPS/file output and docs/state-model.md for the receiver-side current-state model.

Package record:

Example package record
{
  "record_type": "package",
  "record_id": "package:...",
  "schema_version": "0.2.0",
  "scanner_name": "bumblebee",
  "scanner_version": "v0.1.1",
  "run_id": "9b1f0c2e4d5a6b7c8d9e0f1a2b3c4d5e",
  "scan_time": "2026-05-15T18:22:01.482Z",
  "endpoint": {
    "hostname": "alex-mbp",
    "os": "darwin",
    "arch": "arm64",
    "username": "alex",
    "uid": "501",
    "device_id": "MDM-7F4A2B"
  },
  "profile": "project",
  "ecosystem": "npm",
  "package_name": "@tanstack/query-core",
  "normalized_name": "@tanstack/query-core",
  "version": "5.59.20",
  "project_path": "/Users/alex/code/web-app",
  "root_kind": "project_root",
  "package_manager": "pnpm",
  "source_type": "pnpm-lockfile",
  "source_file": "/Users/alex/code/web-app/pnpm-lock.yaml",
  "has_lifecycle_scripts": false,
  "confidence": "high"
}

confidence:

  • high — exact identity and version came from canonical metadata.
  • medium — identity is reliable, but version or source is partial.
  • low — config/path/spec reference only; not proof of an installed exact version.

Finding record (exposure-catalog match):

Example finding record
{
  "record_type": "finding",
  "record_id": "finding:...",
  "schema_version": "0.2.0",
  "scanner_name": "bumblebee",
  "scanner_version": "v0.1.1",
  "run_id": "3a8c7d1e9f0b2a4c6d8e0f1a2b3c4d5e",
  "scan_time": "2026-05-15T18:22:01.482Z",
  "endpoint": {
    "hostname": "alex-mbp",
    "os": "darwin",
    "arch": "arm64",
    "username": "alex",
    "uid": "501",
    "device_id": "MDM-7F4A2B"
  },
  "profile": "deep",
  "finding_type": "package_exposure",
  "severity": "critical",
  "catalog_id": "advisory-2026-0042",
  "catalog_name": "example-pkg 1.2.3 (compromised release)",
  "ecosystem": "npm",
  "package_name": "example-pkg",
  "normalized_name": "example-pkg",
  "version": "1.2.3",
  "root_kind": "deep_home_root",
  "project_path": "/Users/alex/code/web-app",
  "source_type": "pnpm-lockfile",
  "source_file": "/Users/alex/code/web-app/pnpm-lock.yaml",
  "confidence": "high",
  "evidence": "exact name+version match (version=1.2.3)"
}

record_id is a content-addressed hash of a canonical identity tuple per record type, stable across runs. Per-record-type field lists and dedupe guidance: docs/state-model.md.

Exposure Catalog Format

Minimal JSON, exact (ecosystem, name, version) matching. An entry may declare "versions": ["*"] to match every version of the package:

{
  "schema_version": "0.2.0",
  "entries": [
    {
      "id": "advisory-2026-0042",
      "name": "example-pkg 1.2.3 (compromised release)",
      "ecosystem": "npm",
      "package": "example-pkg",
      "versions": ["1.2.3"],
      "severity": "critical"
    }
  ]
}

The catalog must be a JSON object with schema_version and entries keys. Bare top-level arrays are rejected. schema_version 0.1.0 catalogs are still accepted (they cannot use "*"); unsupported future values are rejected. Multiple catalog files can be loaded together by pointing --exposure-catalog at a directory; see the flag description above.

Sample exposure catalogs

The threat_intel/ directory holds maintained exposure catalogs built from public threat-intelligence reporting on recent supply-chain campaigns, assembled with Perplexity Computer and updated via PRs as new campaigns are reported. Additional auto-*.json catalogs are produced daily by cmd/threatintel-fetch from public feeds (MalExt Sentry, ExtSentry, VSXSentry, OSV malicious-packages, DataDog malicious-software-packages-dataset). See threat_intel/README.md for the current catalog list, review guidance, and the auto-source map.

Operators using a release binary can pull the current snapshot without cloning the repo:

curl -fsSLO https://github.com/perplexityai/bumblebee/releases/latest/download/threat-intel-latest.tar.gz
tar -xzf threat-intel-latest.tar.gz

The daily threat-intel-YYYY-MM-DD release carries the whole directory as a tarball plus each catalog as an individual asset and a SHA256SUMS manifest.

Documentation

Repository layout

Path What it is
cmd/bumblebee The scanner CLI (scan, roots, selftest, version).
cmd/threatintel-fetch Daily automation that builds the auto-*.json catalogs from public feeds.
tools/osvcatalog Offline generator that turns OSV data into an exposure catalog.
internal/ Ecosystem parsers, walker, exposure matching, endpoint metadata, output sinks.
threat_intel/ Shipped and auto-generated exposure catalogs.

Contributing

See CONTRIBUTING.md for the local build/test loop and the requirements for adding an exposure catalog. Report vulnerabilities privately as described in SECURITY.md.

License

Apache License 2.0. See LICENSE.

About

Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises.

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages