-
Notifications
You must be signed in to change notification settings - Fork 1
FAQ
Quick answers to common questions, gotchas, and recipes. For authoritative detail follow the Architecture / Operating / Differ-Rules links.
The Differ has nothing to compare against on first run for a new package, so it seeds the baseline from whatever that run did. Zero deviations regardless. This is also the load-bearing assumption that operators bring their own trust at watch-add time — see Threat-Model T5 for the poisoning concern + the v2 mitigation idea (K-of-N runs before auto-promotion).
The event got pruned past the retention horizon
(-retention-days, default 90). Deviation rows pin their evidence
events specifically to avoid this — the pruner explicitly excludes
events referenced by any deviation row. If your link 404s, possibilities:
- The deviation was deleted (
fangs baseline promoteclears deviation rows for the run — but the evidence event stays pinned while another deviation references it, otherwise it eligible for prune). - The retention check ran with a bug. File an issue with the deviation id + event id.
YAML is global only. Per-package goes through the CLI:
fangs allow add -kind sni -value telemetry.example -package axios -note "axios's metrics endpoint"Per-package entries live alongside global ones in the same
allowlists table; the Differ unions both at filter-build time via
storage.EntriesForPackage.
Three options, in order of preference:
-
Add a path allowlist for the specific dir:
fangs allow add -kind path -value /tmp/some-pkg-cache/ -package some-pkg
-
Improve normalization. If the
/tmp/...paths follow a regular pattern but with varying random suffix, the right fix is adding a rule tointernal/orchestrator/differ/normalize.goso the values collapse to a single fingerprint. Source edit + rebuild. - Promote a known-good run so the noise enters baseline.
Hard kill leaves an orphan cgroup at /sys/fs/cgroup/.../fangs/<run_id>/.
The container also stays orphaned (Docker keeps it running).
Clean up:
sudo docker ps -a | grep fangs
sudo docker stop <id> && sudo docker rm <id>
sudo rmdir /sys/fs/cgroup/unified/fangs/<run_id>The orchestrator's heartbeat pruner evicts the dead runner from its
in-memory map after ~90s. The run's row stays in pending state
because no /v1/runs/<id>/result ever arrived — it's effectively
stuck. Operators can either wait for retention to clear it or DELETE
manually:
DELETE FROM runs WHERE id = '<run_id>';(Future: operator command for "mark run failed and clean up.")
The orchestrator queued a scan but had no live runner to dispatch to. The job is dropped (no persistent queue today). Recovery:
- Start a runner:
sudo ./bin/fangs-runner. - The runner registers; next watcher poll will queue another scan
when the registry's
dist-tags.latestmismatcheslast_seen_version.
If the registry version IS the last_seen_version, FANGS won't
re-queue. To force a scan:
fangs scan submit -package <name> -version <ver>Two patterns:
-
Promote the latest clean run —
fangs baseline promote <run-id>. Rebuilds baseline from that run's fingerprints, clears its deviation rows. -
Manual DB cleanup — for "rebuild from scratch":
Next scan for the package will seed a fresh baseline.
-- Inside sqlite3 var/lib/fangs/fangs.db (orchestrator stopped): DELETE FROM baseline_fingerprints WHERE package_name = 'mypkg'; DELETE FROM deviations WHERE run_id IN (SELECT id FROM runs WHERE package_name = 'mypkg'); UPDATE runs SET is_baseline = 0 WHERE package_name = 'mypkg';
Not in v1 — the watcher hardcodes registry.npmjs.org. The plumbing
exists (watcher.Registry is an interface) but only the npm
implementation ships. Adding a Verdaccio/Artifactory adapter is a
v2 item.
Workaround for v1: configure your runner's Docker daemon with a
.npmrc that points at your private registry, run scans via fangs scan submit with the custom sandbox command that does whatever
auth your registry needs. The eBPF sensor still observes; the npm
client just talks to a different registry.
A process node with EventCounts but zero matching events for the
current filter shows greyed/empty. The chip counts on the row header
always reflect the FULL tally (so you keep your bearings); the
expanded list shows only events matching the filter. Empty list +
filter active says "no events from this process — see
the chip totals above for what it did do."
Stable Mermaid v10 syntax works everywhere. Newer features (sankey, gantt v2) may or may not render — when in doubt, simpler flowchart / sequenceDiagram diagrams are safe.
50 packages × weekly releases × ~500 events/run = ~125k events/week. After 90 days retention: ~1.5M rows, ~10 GB sqlite.
For a deployment of 100+ packages, Postgres is the right answer — SQLite handles it but file-locking + WAL tuning matter at that scale.
The orchestrator + CLI? Yes. The runner runs attacker-supplied code in a Docker sandbox on the host kernel. Sandbox escape via a kernel or Docker CVE is rare but not zero. For laptop dev/demo work it's fine; for "I'm watching packages a sophisticated attacker might target" it's not. See Threat-Model T1 + the "dedicated runner host" recommendation.
Configure a generic notifier target pointed at your intake URL:
fangs notifier add -name siem \
-url https://intake.example/fangs \
-template generic \
-secret-env FANGS_HMACThe generic payload is JSON with source, run_id, package_name,
version, max_severity, deviation_count, and an array of every
deviation. Set FANGS_HMAC in the orchestrator's environment to
sign each request with HMAC-SHA256 in X-FANGS-Signature.
Checklist:
-
fangs notifier list— target exists andENABLED=yes? -
fangs notifier test <name>— does the synthetic POST succeed? -
fangs notifier history -run <run-with-deviations>— any attempted deliveries? -
-min-severity highset on the target but the run's max severity wasmedium? Lower the threshold or check the deviation severity.
It got fast in late v1 — each event's JSON payload now decoded once
per request instead of multiple times across extractors. ~17ms for
1000-event runs locally. If yours is slower, check
fangs_events_received_total to see if the run is unusually large.
Yes. Each runner registers with a unique -runner-id (defaults to
hostname). The dispatcher routes jobs per runner via the
target_runner field in the scan submission. Today the
auto-watcher's SubmitFunc calls FirstRegisteredRunner() so it
always sends to the same one — that's a v1 simplification. Manual
fangs scan submit accepts an explicit -runner flag for routing.
A future enhancement: round-robin or load-aware dispatch across registered runners. The dispatcher data structure already supports multiple — just not the watcher's choice of target.
Only with Postgres backend (SQLite can't be shared across processes). With Postgres, multiple orchestrators can register runners + dispatch jobs + run the differ. The watcher should run on only ONE orchestrator (the poll cadence isn't coordinated across instances; multiple watchers would duplicate scans).
Leader-election + better watcher coordination is v2.
There isn't a CLI for this today. Stop the orchestrator, then SQL:
DELETE FROM events
WHERE run_id IN (SELECT id FROM runs WHERE package_name = 'pkg');
DELETE FROM deviations
WHERE run_id IN (SELECT id FROM runs WHERE package_name = 'pkg');
DELETE FROM baseline_fingerprints WHERE package_name = 'pkg';
DELETE FROM releases WHERE package_name = 'pkg';
DELETE FROM runs WHERE package_name = 'pkg';
-- Then optionally `DELETE FROM packages WHERE name = 'pkg'` to also stop watching.A fangs package reset <name> subcommand would be useful — v2.
PRs welcome — the codebase is small (~10k lines Go + ~700 lines BPF C). Common targets:
- New normalization rules for noisy packages you've seen
- Additional probe coverage (openat2, io_uring file ops)
- Per-package severity overrides in the Differ
- CLI subcommands for the missing operations (package reset, baseline rebuild, etc.)
- Test coverage — especially storage contract tests for new methods
make all builds + tests; CI runs lint + vet + test on every push
plus a dual-backend storage contract suite against real Postgres.