Search

Search the docs, blog, and marketplace.

How it works

Detectors

How Bomly discovers projects and turns evidence into a dependency graph.

Last updated July 30, 2026View source (v0.21.1)

Detectors turn a project, container, or SBOM into a dependency graph. Every scan starts with a detector.

A detector knows one or more package managers. Given evidence on disk — a lockfile, a manifest, a workflow file, an SBOM document — it produces packages, versions, and the edges between them. Bomly ships native detectors for the most common ecosystems and falls back to Syft for broad coverage of everything else.

When detectors run

  • bomly scan runs detectors to build the graph.
  • bomly explain reuses the same detector planning before walking dependency paths.
  • bomly diff runs detectors for each side of the comparison, unless you pass --sbom to diff two SBOM files directly.
  • Plugin detectors participate in the same planning flow when they declare package-manager evidence.

Detector Chains

A detector chain is the ordered list Bomly tries for a package manager. The first entry is preferred. Later entries are fallbacks Bomly uses when the preferred detector is not ready, is not applicable, or cannot produce graph data.

For example, the npm chain is npm-detectorsyft-detector:

  1. npm-detector parses package-lock.json directly and resolves the full transitive graph.
  2. syft-detector runs only if the native detector cannot produce graph data (for example, no lockfile present), and emits a flat package list.

Per-detector chains are listed in the detector reference. The full live list lives in the CLI:

bomly plugins list --detectors
bomly plugins list --detectors --json

Native vs. Syft

Native detectorsSyft-backed
Graph shapeFull transitive graph with edgesFlat package list
SourceLockfile or build-tool outputCataloger heuristics
Best forLocal source trees, monorepos, CIContainer images, ecosystems without a native detector
PerformanceFaster on supported ecosystemsRequired for many container layers

Bomly prefers native detectors because they preserve edges (needed by bomly explain, reachability, and scope filtering). It falls back to Syft transparently when no native detector applies.

Selecting detectors

Use --detectors to restrict or extend the default set with the standard selector grammar:

# Use only the native Go detector
bomly scan --detectors go-detector

# Disable Syft fallback for this run
bomly scan --detectors -syft-detector

# Add an external plugin detector
bomly scan --detectors +bomly.examples.detector.bun-lock

Pass the bare detector name to filter to only that detector, +name to add it on top of defaults, or -name to remove it.

Remediation hints

After vulnerability enrichment, supporting detectors may describe package-manager strategies for the dependency occurrences they found. Each detector declares its own capability and owns its package-manager advice. Registry wiring does not add remediation support for it. Bomly's central remediation component validates those read-only hints and chooses the final suggestion. Detectors never choose a fix version or change manifests and lockfiles.

Each package-manager page lists the strategies its built-in detectors understand. External detector plugins can advertise the same optional capability. Older protocol-v1 plugins keep working without it.

Resolution warnings

A detector can resolve a perfectly good graph from a project whose package-manager configuration will still break an install somewhere else — a lockfile format the pinned manager will not keep, an install policy that rejects freshly published versions. Detectors record those as resolution warnings on the manifest they resolved, so the graph is still produced and the problem is still reported. See CI-readiness warnings for the checks and where the warnings surface.

Network behavior

Detectors differ in whether they run subprocesses, and those that do may invoke build tools that download packages from registries. Network-backed matchers make no requests without --enrich. Detectors have separate behavior and may invoke build tools that download packages during normal graph resolution.

Detector classExamplesNetwork during normal scan
Lockfile parsernpm-detector, pnpm-detector, bundler-detector, composer-detector, nuget-detector, github-actions-detector, SBOM ingestNone — pure file parse
Lockfile-first hybridcargo-detector, poetry-detector, uv-detectorNone when the lockfile is present; the build-tool fallback uses --locked / --no-sync to stay offline
pip inspectpip-detector, pipenv-detectorNone when a lockfile is present; otherwise may download while populating the isolated environment it inspects
Build-tool primarygo-detector, maven-detector, gradle-detector, sbt-native-detectorMay download uncached artifacts during normal resolution

The build-tool-primary detectors invoke commands you would already run locally (go list, mvn dependency:tree, gradle dependencies, sbt dependencyTree). Whether they hit the network is a property of those tools and your local cache state, not a Bomly choice. To keep these scans fully offline, pre-warm the local cache (go mod download, mvn dependency:go-offline, etc.) or commit a lockfile when the ecosystem supports one.

Each page under detectors/ documents the exact command that detector runs and whether it touches the network.

--install-first {#install-first}

Pass --install-first to let supporting detectors run their normal dependency-install (or cache-warming) command before resolving the graph. The exact command depends on the package manager:

Package managerInstall-first command
gomodgo mod download
npmnpm install
pnpmpnpm install
yarnyarn install
pippython -m pip install -r <requirements> (plus requirements-dev.txt if present)
pipenvpipenv install
poetrypoetry install --no-root
uvuv sync
bundlerbundle install
composercomposer install
mavenmvn dependency:resolve (uses ./mvnw if present)
gradlegradle dependencies --console=plain (uses ./gradlew if present)
cargocargo fetch --locked

⚠️ --install-first downloads packages from each ecosystem's registry and modifies the filesystem (writes to node_modules/, the active virtualenv, vendor/, ~/.m2/repository/, etc.). It is opt-in for that reason. Use it when:

  • You're scanning in CI on a clean checkout where dependencies have not been installed locally.
  • The lockfile is missing or stale and you want a fresh graph.
  • A build-tool primary detector (Go, Maven, Gradle) would otherwise fetch artifacts during the resolution step itself — --install-first is the cleaner place to bear that cost.

Detectors without an Install implementation (e.g. NuGet, GitHub Actions, SBOM ingest, Syft) silently skip the step when --install-first is set. Bomly does not install package managers themselves — only their dependencies.

Each page under detectors/ lists whether --install-first is supported and the exact command that runs.

Customizing the install command with --install-arg

The default install-first command for each detector is fine for most projects, but real builds often need extra flags (skip dev dependencies, use a legacy resolver, point at a private index, run a clean install). Pass --install-arg — repeatable — to append arguments to whatever the detector's Install() method runs.

⚠️ --install-arg requires exactly one selected detector. Bomly cannot safely apply an arbitrary flag to a chain of mixed package managers, so it rejects the run with exit 4 if zero or more than one detector is in scope. Combine with --detectors <name> to scope to a single detector.

Args are appended to the default command, not replacing it. For example, the npm detector defaults to npm install; with --install-arg --legacy-peer-deps --install-arg --no-audit it runs npm install --legacy-peer-deps --no-audit.

Recipes:

# npm: tolerate peer-dependency conflicts on a legacy project
bomly scan --install-first --detectors npm-detector \
  --install-arg --legacy-peer-deps

# pip: install from a private index in CI
bomly scan --install-first --detectors pip-detector \
  --install-arg --index-url --install-arg https://pypi.example.com/simple

# composer: skip dev dependencies for a production-shaped graph
bomly scan --install-first --detectors composer-detector \
  --install-arg --no-dev

# bundle: deployment mode (frozen lockfile, no version updates)
bomly scan --install-first --detectors bundler-detector \
  --install-arg --deployment

# go: verbose download output for CI debugging
bomly scan --install-first --detectors go-detector \
  --install-arg -x

# mvn: pass a specific Maven profile for the dependency:resolve step
bomly scan --install-first --detectors maven-detector \
  --install-arg -Pproduction

# uv: install only the locked dependencies, no editable updates
bomly scan --install-first --detectors uv-detector \
  --install-arg --frozen

--install-arg is consumed whenever Bomly runs a detector install step. Most detectors only install when --install-first is set; pip-detector may also install automatically into its isolated temp virtualenv when no readable requirements.lock is present.

Discovery and monorepos

For local source trees, Bomly discovers subprojects before running detectors. Every directory containing recognized evidence becomes a subproject; Bomly runs the matching detector chain in each one and consolidates the results into a single graph.

There is no project-root requirement. Pointing bomly scan at a monorepo will scan every workspace in one pass.

SBOM ingest

Bomly treats SPDX 2.3 and CycloneDX SBOMs as first-class input. Use --sbom to ingest an SBOM file directly without re-running ecosystem detectors:

bomly scan --sbom --path ./existing.spdx.json

This is fast and offline. See SBOM formats for the format comparison.

Container images

Bomly resolves container references via the host's registry credentials. Native detectors that work on lockfile contents inside layers still run; everything else falls through to Syft:

bomly scan --image ghcr.io/example/app:latest

See also