Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - sm-stripe/bumblebee: Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises. · GitHub
Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sm-stripe/bumblebee: Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises. · GitHub
Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sm-stripe/bumblebee: Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises. · GitHub
Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - sm-stripe/bumblebee: Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises. · GitHub
Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sm-stripe/bumblebee: Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises. · GitHub
Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sm-stripe/bumblebee: Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises. · GitHub
Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - sm-stripe/bumblebee: Read-only developer endpoint scanner for on-disk package, extension, and developer-tool metadata, built to check exposure to known software supply-chain compromises. · GitHub
Skip to content

Repository files navigation

bumblebee

Bumblebee is a read-only inventory collector for package, extension, and developer-tool metadata on macOS and Linux 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+, zero non-stdlib dependencies.
  • 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

FamilyEmitted ecosystemSources
npmnpmpackage-lock.json, npm-shrinkwrap.json, node_modules/.package-lock.json, node_modules/<pkg>/package.json
pnpmnpmpnpm-lock.yaml, .pnpm/.../package.json
Yarnnpmyarn.lock (Classic + Berry)
Bunnpmbun.lock; bun.lockb presence as diagnostic
PyPIpypi*.dist-info/METADATA, INSTALLER, direct_url.json, *.egg-info/PKG-INFO
Go modulesgogo.sum, go.mod
RubyGemsrubygemsGemfile.lock, installed *.gemspec
Composerpackagistcomposer.lock, vendor/composer/installed.json
MCPmcpJSON 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). Non-JSON configs (Codex config.toml, Continue YAML) are not parsed in v0.1.
Agent skillsagent-skillskills.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 extensionseditor-extensionVS Code, Cursor, Windsurf, VSCodium manifests
Browser extensionsbrowser-extensionChromium-family (manifest.json) and Firefox (extensions.json) per profile
HomebrewhomebrewFormula INSTALL_RECEIPT.json files and cask .metadata install markers

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

Install

Requires Go 1.25+. Zero non-stdlib dependencies.

# 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.1

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.1" -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 (2 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.

ProfileScansUse for
baselineCommon global/user package roots, language toolchains, editor extensions, browser extensions, and MCP configs.Recurring lightweight inventory via an external runner.
projectConfigured development directories, such as ~/code, ~/src, or ~/work.Recurring inventory for known project workspaces.
deepExplicit --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.

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. 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.1.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.1.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 only:

{
"schema_version": "0.1.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. Unsupported future schema_version 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. See threat_intel/README.md for the current catalog list and review guidance.

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

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages