Skip to main content

Supply-chain scanning

cyscan supply walks your tree, finds every supported lockfile, and emits findings from three independent matchers:

  1. OSV advisory match against a bundled snapshot
  2. Typosquat heuristic (Levenshtein ≤ 2 against popular packages)
  3. Policy rules authored in YAML with a dependency: block

All three run in one pass; findings are merged, deduped by severity, and printed like any other scan.

Quick run

cyscan supply .

Example output:

[crit] CBR-SUPPLY-GHSA-mh6f-8j2x-4483 package-lock.json:0:0
event-stream was compromised with malicious code
│ event-stream@3.3.6

[high] CBR-SUPPLY-GHSA-vqm2-6jp7-jqvx requirements.txt:0:0
urllib3 Proxy-Authorization header leaks across redirects
│ urllib3@1.26.5

[low ] CBR-SUPPLY-TYPOSQUAT package-lock.json:0:0
reakt resembles popular package react
│ reakt@0.1.0

3 finding(s)

Supported lockfiles

EcosystemFiles parsed
RustCargo.lock
Node.jspackage-lock.json (v1 + v2/v3), yarn.lock (classic)
Gogo.sum
Pythonrequirements.txt (pinned with ==), poetry.lock, Pipfile.lock

Lockfiles are preferred over manifests because they carry exact pinned versions. A manifest with ranges ("axios": "^1.6.0") depends on resolver state cyscan doesn't replicate; if a project has no lockfile, nothing is scanned — commit the lock.

Advisory matcher

The OSV snapshot ships in rules/advisories/*.jsonl alongside the binary and is refreshed nightly by the release pipeline. Every release picks up the newest cut.

Match logic:

  1. (ecosystem, name) lookup by hash.
  2. For each advisory's affected entry, check every range.events stream against the dep's version using semver.
  3. For non-semver versions (Go +incompatible pseudo-versions, git SHAs) falls back to exact-list match.

Skipping OSV

cyscan supply . --no-advisories # policy + typosquat only

Useful when your org maintains its own advisory feed and doesn't want OSV noise.

Offline environments

The default is already offline — cyscan never phones home. Pass --offline to suppress the "snapshot is N days old" warning that otherwise fires on air-gapped builds.

cyscan supply . --offline

Typosquat heuristic

For each dependency, cyscan computes Levenshtein distance to every name in the bundled popular-packages list for that ecosystem. A distance ≤ 2 triggers a low-severity finding — it's advisory, not a gate.

[low ] CBR-SUPPLY-TYPOSQUAT package-lock.json
reakt resembles popular package react

False positives happen (e.g. a fork called requests2). Suppress with a custom rule or filter in your CI step.

Writing policy rules

A rule with a dependency: block matches against Dependency objects rather than source code:

id: CBR-DEP-NO-GPL-NPM
title: "Disallow GPL/AGPL licenses in Node dependencies"
severity: high
dependency:
ecosystem: npm
name_pattern: ".+" # everything — filter by license via another rule
message: |
License policy bans GPL-family licenses. Find a permissive alternative
or negotiate an exception with Legal.

dependency: fields

FieldTypeNotes
ecosystemnpm / pypi / crates.io / goCase-insensitive
nameexact name (case-insensitive)
name_patternregexMutually exclusive with name
version.minsemverInclusive lower bound
version.maxsemverInclusive upper bound

All fields are AND-ed within one rule.

Example — block a specific compromised release

id: CBR-DEP-EVENT-STREAM-MALWARE
title: "event-stream 3.3.6 was trojaned"
severity: critical
dependency:
ecosystem: npm
name: event-stream
version: { min: "3.3.6", max: "3.3.6" }
cwe: [CWE-506]
message: |
This exact release was compromised. Upgrade past the fix.

SARIF output

cyscan supply . --format sarif produces a SARIF 2.1.0 document where each supply-chain finding carries:

"properties": {
"packageCoordinate": "event-stream@3.3.6"
}

The Cybrium platform consumes this field to join findings back to its reachability index — see Integrating with the platform.

Updating the advisory snapshot

The snapshot is pinned to the release you installed. To pick up a newer one:

brew upgrade cyscan
# or for cargo / raw binary:
# re-run the install steps for the latest release

Enterprises that scan thousands of repos typically pin a specific release internally and bump it monthly — the snapshot updates are included in patch releases.

Next step