Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/irchaosclub/FANGS/llms.txt

Use this file to discover all available pages before exploring further.

The differ is FANGS’ core comparison engine. It doesn’t classify behavior as malicious — it asks a narrower question: is this run different from what this package has done before? Each completed run produces a set of (category, normalized_value) fingerprints. Those fingerprints are compared against the package’s rolling baseline_fingerprints table. Any pair not in the baseline becomes a Deviation row for an operator to review.

Analysis flow

1

Run completes

The runner posts a final ScanResult to POST /v1/runs/{run_id}/result. The orchestrator marks the run RunStateDone and calls Differ.AnalyzeRun(ctx, runID).
2

Load events and build the allowlist filter

AnalyzeRun calls store.ListEventsByRun to retrieve every EventRow for the run (stored as JSON in the events table). It also calls store.ListAllowEntries to load the union of global and package-scoped operator allowlist rules. A Filter is constructed from those entries plus the hardcoded CDN CIDR list.
3

Extract fingerprints

ExtractFingerprintsWith(events, filter) walks every event row, dispatches per type to a category-specific extractor, applies normalization, and deduplicates into a map[string]*Fingerprint. Each unique (category, value) key becomes one Fingerprint with a Count (number of events that collapsed to it) and a FirstEvtID (lowest events.id, used as an evidence pointer).
4

Load baseline

store.LoadBaseline(packageName) returns the current baseline_fingerprints rows for the package. If the slice is empty this is the package’s first run.
5

First run — seed baseline

If no baseline exists, seedBaseline writes every run fingerprint as a BaselineRow and marks the run is_baseline=true. AnalyzeRun returns 0 deviations. The first run always becomes the baseline; future runs are compared against it.
6

Subsequent runs — diff and write deviations

A known map is built from the baseline rows. Each run fingerprint is checked:
  • In known → bump occurrence_count via MergeBaseline.
  • Not in known → emit a DeviationRow with category, value, evidence event ID, and severity.
Prior deviation rows for this run are deleted first (DeleteDeviationsForRun) so re-analysis is idempotent — the orchestrator may call AnalyzeRun multiple times per run (once per debounced event-batch POST, once more on final result).
7

D38 auto-promotion or hold for review

  • Zero deviations: MarkRunBaseline(true) + MergeBaseline — the run’s fingerprints join the rolling baseline. Operator sees nothing.
  • Any deviations: run lands in fangs pending. Operator reviews with fangs pending or /ui/pending.

Six fingerprint categories

Each category maps to one event type and one field extracted from the event’s JSON payload. Severity is the default assigned when a deviation is first written; operators can tune it via allowlist rules.

net_new_destination

Severity: warnSource: net_connect events. Value: "ip:port" string (e.g. "1.2.3.4:443"). IPs in any CDN allowlist CIDR or operator-defined CIDR are suppressed before fingerprinting — their identity is canonically captured by SNI/DNS.

net_new_dns

Severity: warnSource: dns_query events. Value: the normalized QName (e.g. "registry.npmjs.org"). Helps catch exfiltration via DNS even when the IP side is allowlisted.

net_new_https_host

Severity: warnSource: tls_sni events. Value: the normalized SNI hostname (e.g. "malicious-c2.example.com"). Catches HTTPS connections to new hosts even when routed through a known CDN IP range.

fs_new_path_read

Severity: infoSource: file_access events where the openat flags do NOT include O_WRONLY (1), O_RDWR (2), or O_CREAT (64). Value: the normalized path.

fs_new_path_write

Severity: infoSource: file_access events where flags & (O_WRONLY | O_RDWR | O_CREAT) != 0. Value: the normalized path. Write-intent opens to credential paths (/root/.ssh/, /etc/shadow, etc.) are the highest-signal file deviations.

proc_new_exec

Severity: warnSource: exec events. Value: the normalized binary path (e.g. /usr/bin/curl). A package that suddenly spawns a shell or network binary for the first time is a strong signal.
A seventh category syscall_rare_category appears in the differ comments as a planned addition (D18) but is not yet implemented. Only the six categories above are written to the deviations table in the current release.

Normalization

Normalization collapses volatile path segments and host representations so a baseline established on one run remains stable across future runs of the same package. All normalization is applied before the fingerprint map lookup — two events that normalize to the same value produce a single fingerprint.
NormalizePath applies a sequence of regex rules in priority order. Earlier rules are more specific:
PatternReplacementExample
/proc/<number>/…/proc/<PID>/…/proc/12345/status/proc/<PID>/status
/tmp/<word>-<3+ digits>…/tmp/<RAND>/tmp/npm-2156-abc/foo/tmp/<RAND>
ISO datetime npm debug logs/<TIMESTAMP>-debug-<N>.log2026-05-22T10_00_00_000Z-debug-0.log/<TIMESTAMP>-debug-<N>.log
Generic ISO-date filenames/<DATE>.<ext>/var/log/2026-05-22.log/var/log/<DATE>.log
cacache content-addressed paths…/<SHA256>.npm/_cacache/content-v2/sha256/AA/BB/<hex>…/<SHA256>
cacache index paths…/<HASH>.npm/_cacache/index-v5/AA/BB/<hex>…/<HASH>
cacache /tmp/<hex> staging…/<HEX>.npm/_cacache/tmp/<hex>…/<HEX>
Long hex string path components/<HEX>//12abcdef34567890//<HEX>/
NormalizeBinaryPath (used for proc_new_exec) strips null bytes and surrounding whitespace only — deliberately kept minimal to avoid hiding PATH-traversal indicators.

Allowlist filter

Before any fingerprint is written, three suppression checks are applied. The Filter struct is built per-run from the hardcoded CDN CIDR list plus any operator-defined allow_entries rows (global and package-scoped).
// NewFilter merges hardcoded CDN ranges with operator entries.
filter := NewFilter(storage.EntriesForPackage(allEntries, run.PackageName), logger)

// Three suppression checks (in the per-type extractors):
filter.SuppressIP(ipStr)   // CIDR match — drops net_new_destination
filter.SuppressPath(norm)  // prefix match — drops fs_new_path_*
filter.SuppressSNI(norm)   // exact match (lowercased) — drops net_new_https_host

Hardcoded CDN allowlist

The DefaultCDNAllowlist covers IP ranges for large CDNs that round-robin their edge IPs per DNS query. Without this allowlist, every npm install would produce net_new_destination deviations as DNS returns different IPs on each run.
The CDN allowlist suppresses the IP-side signal only. A malicious package that exfiltrates data to a Cloudflare R2 bucket will still produce a net_new_https_host deviation (the bucket hostname won’t match baseline) and a net_new_dns deviation (the FQDN is new). The IP suppression is not a blind spot.
ProviderCIDR examplesCovers
Cloudflare104.16.0.0/13, 172.64.0.0/13, 13 more rangesnpm registry CDN, Discord CDN, etc.
GitHub140.82.112.0/20, 185.199.108.0/22, 2 moreraw.githubusercontent.com etc.
Google142.250.0.0/15, 172.217.0.0/16, 3 moregstatic, googleapis CDN edges
Fastly151.101.0.0/16, 199.232.0.0/16npm registry origin
Amazon CloudFront13.32.0.0/15, 52.84.0.0/15, 6 more rangesAWS CDN
Operators can add internal CIDRs via fangs allow add --kind cidr --value 10.0.0.0/8 --note "internal", or suppress specific SNIs and path prefixes similarly.

Severity defaults

CategoryDefault severityRationale
net_new_destinationwarnNew IP:port is medium-confidence signal
net_new_dnswarnNew DNS lookup often precedes exfil
net_new_https_hostwarnNew TLS hostname is the strongest network signal
fs_new_path_readinfoRead of new path is lower confidence
fs_new_path_writeinfoWrite to new path is higher confidence
proc_new_execwarnUnexpected subprocess is high signal
Credential-tagged paths (/root/.ssh/, /etc/shadow, etc.) receive EventTagCredAccess in the sensor and are rendered with a red badge in the UI regardless of the differ category severity.

Baseline lifecycle

fangs package add axios

First run → seedBaseline → is_baseline=true

axios@5.1.0 → zero deviations → auto-promote (D38)

axios@5.2.0 → 1 deviation (new SNI) → held in `fangs pending`

Operator: fangs baseline promote <run-id>   ← accepts the deviation as legitimate
    OR
Operator: fangs allow add --kind sni --value new.host.com   ← suppresses future occurrences
fangs baseline promote <run-id> accepts the run’s full fingerprint set into baseline. Use it when a new package version legitimately changes behavior (new CDN, new dependency). To suppress recurring noise across all versions of a package without promoting a specific run, prefer fangs allow add -package <pkg>.

Build docs developers (and LLMs) love