Skip to content

Supply Chain Scanning

Samuele Giampieri edited this page Aug 7, 2026 · 4 revisions

Supply-Chain Scanning

The Supply-Chain Scanner detects known-malicious (MAL-) and known-vulnerable (CVE / GHSA) software packages across a target's dependency surface, verdicted fully offline against a local copy of the OSV database. It ships as three layers that share one engine and one graph model, so a repository scan, a live-target harvest, and an on-demand agent lookup all dedup into the same Package and MalPackageFinding nodes.

This page is the operator guide. For the full technical reference (container topology, the DIRTY/CLEAN security split, the artifact schema, and the graph writer internals), read README.SUPPLY_CHAIN.md in the repository.


The three layers

Layer What it is How you start it Writes graph nodes?
L1 Standalone audit of an uploaded SBOM / lockfile or a GitHub repository Other Scans modal, Red Zone toolbar Yes: Package, MalPackageFinding, Vulnerability{source:'osv'}
L2 Black-box harvest of a live target's served packages Recon pipeline (GROUP 5.5), or a partial-recon run Yes: same nodes, anchored to BaseURL
L3 On-demand lookups the AI agent calls mid-engagement execute_osv_scanner / execute_guarddog agent tools No: returns text to the agent

A vulnerability id starting with MAL- is a terminal malicious verdict (the package itself is malware, for example a typosquat). CVE- / GHSA- ids are ordinary known-vulnerable findings and are never written as malicious. A GuardDog behavioural hit is always suspicious, never malicious; only an OSV MAL- hit is malicious.


The tools

Tool Role Version Runtime
OSV-Scanner Verdict engine: is a package MAL- (malicious) or CVE / GHSA (vulnerable)? v2.4.0 Go static binary, offline
GuardDog Behavioural analysis: does the package behave like malware (install hooks, obfuscation, exfil, typosquat)? v3.0.1 Python (+ semgrep, YARA)
retire.js Black-box JS library + version harvest (L2 only) v5.4.3 Node CLI

SBOMs are synthesized in-process as CycloneDX 1.5 from whatever packages a layer harvested; RedAmon does not run syft, trivy, or cdxgen. The OSV verdict path makes zero network calls. GuardDog and retire.js need registry / signature-file egress and run inside a hardened, isolated analyzer container.


One-time setup: the offline OSV database

The verdict path reads a local copy of the OSV database from the shared Docker volume redamon-osv-db. It is not downloaded at install time (the container images are eager, the data is lazy). Populate it once per ecosystem:

./redamon.sh supply-chain-sync npm            # ~208 MB, first run only
./redamon.sh supply-chain-sync npm PyPI Go    # add more ecosystems

Supported ecosystems: npm, PyPI, Go, Maven, crates.io, Packagist, RubyGems, NuGet.

Automatic refresh. OSV publishes new MAL- / CVE advisories daily. Once an ecosystem has been synced, the orchestrator refreshes it lazily on the scan-spawn path, TTL-guarded (default 24h). A cold database (never synced) is not bootstrapped on the scan path, because the first download is large and the feature is off by default. Run supply-chain-sync yourself the first time. A refresh failure (offline host) never blocks a scan; it proceeds against the existing database.

The refresh behaviour is controlled by four orchestrator environment variables, all wired in docker-compose.yml:

Knob Default Meaning
OSV_DB_AUTO_REFRESH true Set false for a strictly air-gapped deploy (manual sync only)
OSV_DB_ECOSYSTEMS npm (auto-refresh set) Ecosystems kept fresh automatically
OSV_DB_TTL_SECONDS 86400 Freshness window (24h)
OSV_DB_REFRESH_TIMEOUT 900 Ceiling so a slow download cannot stall a scan spawn

Layer L1: the Supply Chain Scanner (Other Scans)

L1 is a standalone scan you launch from the Red Zone. It audits a dependency manifest you provide, with no live target required.

The Supply Chain Scanner in the Other Scans modal

Step by step:

  1. Open a project's graph (Red Zone). In the toolbar, click Other Scans.
  2. Scroll to the full-width Supply Chain Scanner card (below GitHub Secret Hunt and TruffleHog).
  3. Pick an input source with the toggle:
    • Uploaded SBOM / lockfile (default), or
    • GitHub repository.
  4. Provide the input for the selected source (details below).
  5. Click Start. The logs drawer opens and streams the scan live. Use Pause / Resume / Stop as needed.
  6. When the scan completes, the Package / MalPackageFinding / Vulnerability nodes are merged into the live graph, and Download yields the raw result JSON.

Upload mode

Click Upload file and choose a manifest. Accepted types: CycloneDX / SPDX SBOMs and lockfiles, extensions .json, .xml, .txt, .lock, .toml, .mod, .sum, .yaml, .yml (for example package-lock.json, yarn.lock, poetry.lock, go.sum, Gemfile.lock, bom.cdx.json). Max 10 MB. One file per project: a new upload replaces the current one. No API key is required for an upload.

File naming matters. OSV-Scanner picks its parser from the file basename, so keep the recognized lockfile / SBOM name (for example package-lock.json, not deps.json).

GitHub repository mode

Enter a repository as owner/repo or https://github.com/owner/repo, and optionally a branch or tag. The repository is cloned shallowly inside the scan sandbox and its lockfiles are audited; the checkout is discarded afterwards.

  • Public repositories clone anonymously (no token needed).
  • Private repositories use the GitHub Access Token from Global Settings > API Keys (the same token shared with GitHub Secret Hunt and TruffleHog).

An L1 GitHub-repo scan anchors its packages to a GithubRepository node; an uploaded SBOM anchors to an SbomDocument node. Both anchors hang off the project's Domain when one exists, so the scan is part of the graph rather than a detached island.


Layer L2: Supply-Chain Recon (pipeline)

Against a live target with no manifest, L2 harvests the npm package set the target actually serves, verdicts it offline, and merges the same node types, anchored to the target's BaseURL nodes. It runs as GROUP 5.5 of the recon pipeline, immediately after JS Recon (whose downloaded JS and detected technologies it consumes), and is also runnable on its own as a partial-recon tool.

The harvest is pure parsing of data JS Recon already downloaded; it makes no new network request. Sources:

  1. Source-map mining extracts node_modules/<pkg> names from source maps (exact names, usually no version).
  2. Import mining collects bare specifiers from import / require.
  3. Technology to purl maps http_probe technologies (for example React:18.2.0) to versioned npm purls.
  4. retire.js (inside the hardened analyzer) reads a library name and version straight out of the served JavaScript, so it can verdict libraries the technology table misses.

Names harvested without a version are recorded as Package inventory but cannot be OSV-verdicted (OSV needs a version to match a version-specific advisory); they surface in the SCA table as unverdictable.

Configure it in the project form's JS Recon tab, under Supply Chain Recon:

Setting Field Default Meaning
Enable supplyChainReconEnabled Off Runs GROUP 5.5 during a full scan
Ecosystems supplyChainReconEcosystems npm Which ecosystems to verdict
Deep analysis supplyChainReconDeepAnalysisEnabled Off Runs GuardDog behavioural analysis on OSV-flagged packages (opt-in)

You can also run just this module with Run partial recon on the section (tool id SupplyChainRecon), which fetches the served JS from the graph's BaseURL / Endpoint URLs (plus any you supply) and runs the harvest, verdict, and graph write standalone.

GuardDog deep analysis is opt-in and flagged-package-only. After the offline OSV pass, it takes the packages OSV already flagged (capped, malicious first) and runs guarddog <eco> scan inside the hardened analyzer image. Results become suspicious findings that attach to the existing Package node. A download failure becomes a soft_error finding (rendered as not analysed), never a silent clean.


Layer L3: agent tools

Two MCP tools the AI agent calls mid-engagement, exposed on the network_recon server inside the kali-sandbox. Neither writes graph nodes; each returns a compact text summary the agent reasons over. See the full entries in the AI Agent Guide.

  • execute_osv_scanner is passive and fully offline. It accepts a purl (for example pkg:npm/lodash@4.17.21, synthesized into a one-component SBOM), a workspace lockfile path, or an SBOM path, and returns the OSV verdict. MAL- is a terminal malicious verdict; CVE- / GHSA- are known-vulnerable. Available in all phases.
  • execute_guarddog runs GuardDog behavioural analysis of one named package (<ecosystem> <name> [version]). It is DANGEROUS (it downloads the attacker-authored tarball), so it dispatches to the hardened analyzer container rather than unpacking anything in the sandbox. A hit is suspicious, never a terminal verdict. Restricted to the informational and exploitation phases.

Reading the results: the Supply-Chain SCA table

Every layer writes into the graph; the Supply-Chain SCA table is where you read it. Open it from the Data Table tab's dropdown (PackageSearch icon, flagged NEW). It is the only view that joins the three node types, and it has three sheets:

Sheet One row per Answers
Verdicts MalPackageFinding What is on fire right now
Packages Package, with rolled-up counts What am I running, and how much of it was actually checked
Advisories Vulnerability{source:'osv'} The CVE / GHSA half

Three things the table derives rather than reads:

  • Verdict is three-state: malicious / suspicious / not analysed. A finding with soft_error is a package GuardDog never verdicted, rendered as unchecked rather than as a low-severity suspicious hit.
  • unverdictable is a first-class package status and a headline count. A short verdict list next to a large unversioned count means "mostly unchecked", not "mostly clean".
  • Origin (L1 repo / L1 SBOM / L2 live) is inferred from the anchor node.

Each sheet is deep-linkable (/graph?table=supplyChainSca&sheet=advisories) and exports per sheet to XLSX / JSON / MD. The table is empty until an L1 scan has run from Other Scans or a recon scan has run with Supply-Chain Recon enabled, and it needs the offline OSV database populated first (./redamon.sh supply-chain-sync npm). See the Red Zone table reference.


Graph model

Two node types are shared by all layers, so a repo scan, an SBOM upload, and a live harvest of the same project dedup onto the same nodes. See the full schema on Attack Surface Graph.

flowchart LR
    DOM[Domain]
    GR[GithubRepository - L1 repo]
    SB[SbomDocument - L1 upload]
    BU[BaseURL - L2 live]
    PK[Package]
    MF[MalPackageFinding]
    VU[Vulnerability source=osv]

    DOM -->|HAS_REPOSITORY| GR
    DOM -->|HAS_SBOM_DOCUMENT| SB
    GR -->|DEPENDS_ON| PK
    SB -->|DEPENDS_ON| PK
    BU -->|DEPENDS_ON| PK
    PK -->|FLAGGED_AS| MF
    PK -->|HAS_VULNERABILITY| VU
Loading
  • Package is a discovered dependency (purl, ecosystem, name, version, source, source_path). Merge key (purl, user_id, project_id).
  • MalPackageFinding is a verdict about a package (verdict = malicious / suspicious, source_tool = osv / guarddog, advisory_id, severity, soft_error, aliases). Merge key (finding_id, user_id, project_id).
  • All writes MERGE (idempotent), every key is tenant-scoped, and only OSV MAL- ids become verdict=malicious.

Security posture

The feature separates the code that touches untrusted bytes from the code that holds secrets. Package tarballs, target-served JS, and registry metadata are all attacker-influenceable; the Neo4j password and the GitHub token are not.

  • DIRTY zone (the analyzer container): cap_drop=ALL, read-only rootfs + tmpfs, non-root, memory / pids / cpu caps, no secrets, on an isolated network. It runs GuardDog and retire.js and emits only a schema-validated JSON artifact.
  • CLEAN zone (the L1 scan writer and the recon container): holds the Neo4j credentials, validates the artifact (charset + caps + shape) before it crosses the boundary, and writes the graph.
  • Offline by default: the OSV verdict path makes zero network calls. GuardDog's registry egress is opt-in and fails closed.
  • NO-INSTALL invariant: RedAmon never runs npm / pip install on a target manifest (lifecycle scripts are RCE); it parses statically only.

For the container topology, the broker allowlist, and the full artifact schema, see README.SUPPLY_CHAIN.md.

Clone this wiki locally