Skip to content
cyb3rjerry edited this page May 23, 2026 · 1 revision

FAQ

Quick answers to common questions, gotchas, and recipes. For authoritative detail follow the Architecture / Operating / Differ-Rules links.

Why is the first run for a package never flagged?

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).

My deviation has evidence_event_id but the event detail returns 404

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 promote clears 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.

How do I add a per-package allowlist entry?

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.

How do I stop a single package's noisy /tmp/... writes from cluttering deviations?

Three options, in order of preference:

  1. Add a path allowlist for the specific dir:
    fangs allow add -kind path -value /tmp/some-pkg-cache/ -package some-pkg
  2. Improve normalization. If the /tmp/... paths follow a regular pattern but with varying random suffix, the right fix is adding a rule to internal/orchestrator/differ/normalize.go so the values collapse to a single fingerprint. Source edit + rebuild.
  3. Promote a known-good run so the noise enters baseline.

What happens if I kill -9 the runner mid-scan?

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 watcher said no runners registered — what's the right path?

The orchestrator queued a scan but had no live runner to dispatch to. The job is dropped (no persistent queue today). Recovery:

  1. Start a runner: sudo ./bin/fangs-runner.
  2. The runner registers; next watcher poll will queue another scan when the registry's dist-tags.latest mismatches last_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>

How do I clear all deviations for a package and start fresh?

Two patterns:

  • Promote the latest clean runfangs baseline promote <run-id>. Rebuilds baseline from that run's fingerprints, clears its deviation rows.
  • Manual DB cleanup — for "rebuild from scratch":
    -- 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';
    Next scan for the package will seed a fresh baseline.

Can FANGS scan against a private npm registry?

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.

Why are some chips on the lineage view greyed out?

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."

How do I see which Mermaid version GitHub renders?

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.

How big can the events table get?

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.

Is FANGS safe to run on my laptop?

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.

How do I export findings to my SIEM?

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_HMAC

The 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.

My notifier's not firing

Checklist:

  1. fangs notifier list — target exists and ENABLED=yes?
  2. fangs notifier test <name> — does the synthetic POST succeed?
  3. fangs notifier history -run <run-with-deviations> — any attempted deliveries?
  4. -min-severity high set on the target but the run's max severity was medium? Lower the threshold or check the deviation severity.

Why is the lineage view slow?

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.

Can I run multiple runners?

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.

Can I run multiple orchestrators?

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.

How do I uninstall a package's data without removing the package?

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.

How do I contribute?

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.

Clone this wiki locally