-
Notifications
You must be signed in to change notification settings - Fork 1
Configuration
Three layers, narrowest-wins:
- CLI flags — set explicitly when starting the binary. Highest priority.
- Environment variables — when supported.
-
config/orchestrator.yaml— loaded once at startup. - Hardcoded defaults — what you get when nothing else is set.
The config file is OPTIONAL. A fresh checkout with no -config flag
still runs with hardcoded fallbacks for every section.
Default path: config/orchestrator.yaml (relative to the
orchestrator's cwd). Override with -config /etc/fangs/orchestrator.yaml.
Missing file is silently OK. Missing sections within the file fall back to hardcoded defaults section-by-section.
Restart the orchestrator to pick up changes — SIGHUP reload is not wired.
The filesystem prefixes the eBPF sensor reports for every sandbox
scan. Stamped onto every job whose watched_paths arrived empty: the
autonomous watcher, fangs scan submit, the kickoff scan
fangs package add triggers.
watched_paths:
- prefix: "/etc/" # observe but don't tag
- prefix: "/etc/shadow"
cred: true # match → EVENT_TAG_CRED_ACCESS
- prefix: "/root/.ssh/"
cred: true
- prefix: "/tmp/"
- prefix: "/usr/"Semantics:
- The kernel-side LPM trie picks the longest match per event. A path
tagged
cred: truealways wins over its parent prefix. -
cred: trueevents render with a red row +CREDbadge in the UI; severity bumps tohighautomatically when one becomes a deviation. - Per-job customization remains possible — HTTP
POST /v1/scanscallers can include their ownwatched_pathsand the orchestrator passes those through unchanged.
Global allowlist entries — IPs, SNIs, and path exclusions. The Differ
uses these to suppress noise BEFORE it becomes a deviation. The
hardcoded DefaultCDNAllowlist (Cloudflare/GitHub/Google/Fastly/CloudFront)
applies underneath — entries here are ADDITIVE.
Per-package scoping is intentionally NOT in YAML — that's what
fangs allow add -package P is for. Config is global only.
allow:
cidrs:
- value: "10.0.0.0/8"
note: "internal RFC1918"
- value: "172.16.0.0/12"
snis:
- value: "telemetry.internal.example"
note: "operator metrics intake"
paths:
- value: "/usr/lib/"
note: "shared library loads — high volume, low signal"
- value: "/usr/lib64/"
- value: "/usr/share/zoneinfo/"
note: "timezone DB reads on every process start"How each kind maps to a Differ category:
| Kind | Suppresses category | Match semantic |
|---|---|---|
cidr |
net_new_destination |
IP ∈ CIDR (lib net.IPNet.Contains) |
sni |
net_new_https_host |
strings.EqualFold(SNI, value) |
path |
fs_new_path_read, fs_new_path_write
|
path.HasPrefix(normalized, value) |
Config-managed entries get deterministic IDs:
cfg + sha256(kind + "|" + value)[:12]. Same YAML across restarts
always touches the same rows, so re-apply is idempotent.
CLI-added entries coexist; fangs allow remove cfg… works but the
entry comes back on next restart unless you also delete the YAML line.
fangs-orchestrator [flags]| Flag | Default | Purpose |
|---|---|---|
-config |
config/orchestrator.yaml |
path to YAML config; missing file OK |
-addr |
127.0.0.1:8443 |
HTTP listen address |
-id |
fangs-orchestrator |
identifier returned to runners on register |
-storage |
sqlite |
sqlite | postgres | none
|
-sqlite-path |
var/lib/fangs/fangs.db |
sqlite DB file path |
-postgres-dsn |
— | postgres DSN; also reads $FANGS_PG_DSN
|
-watch |
true |
enable the npm registry watcher |
-watch-interval |
5m |
how often to poll the registry per package |
-ui |
true |
serve the read-only dashboard at /ui/
|
-metrics |
true |
expose Prometheus metrics at /metrics
|
-notifiers-file |
— | optional JSON file of webhook targets; upserts into DB at startup |
-retention-days |
90 |
delete events older than this; 0 disables |
-retention-interval |
24h |
how often the pruner runs |
-tls-cert |
— | PEM server cert; with -tls-key, switches to HTTPS |
-tls-key |
— | PEM server private key |
-tls-client-ca |
— | PEM CA for verifying runner certs; enables mTLS |
fangs-runner [flags]| Flag | Default | Purpose |
|---|---|---|
-orchestrator |
http://127.0.0.1:8443 |
base URL of the orchestrator |
-runner-id |
hostname | stable identifier the orchestrator addresses jobs to |
-tls-ca |
— | PEM CA bundle to verify the orchestrator's server cert |
-tls-cert |
— | PEM client cert (for mTLS) |
-tls-key |
— | PEM private key paired with -tls-cert
|
The runner reads its heartbeat + job-poll intervals from the orchestrator's register response — no local cadence flags.
fangs [global flags] <subcommand> [args]| Flag | Default | Purpose |
|---|---|---|
-storage |
sqlite |
backend to read from |
-sqlite-path |
var/lib/fangs/fangs.db |
sqlite DB path |
-postgres-dsn |
— | postgres DSN |
-json |
false |
machine-readable output |
See CLI-Reference for every subcommand.
| Variable | Purpose |
|---|---|
FANGS_PG_DSN |
postgres DSN; overridden by -postgres-dsn if both set |
$SECRET_ENV per notifier |
HMAC secret value; per-target secret_env: NAME references this |
Optional alternative to fangs notifier add. Path passed via
-notifiers-file=/etc/fangs/notifiers.json.
{
"notifiers": [
{
"name": "soc-slack",
"url": "https://hooks.slack.com/services/T.../B.../...",
"template": "slack",
"enabled": true
},
{
"name": "siem",
"url": "https://intake.internal/fangs",
"template": "generic",
"secret_env": "FANGS_HMAC",
"min_severity": "high",
"headers": {"X-API-Key": "${INTAKE_KEY}"}
}
]
}Each entry is upserted into the notifiers table at startup. Entries
managed via fangs notifier add coexist — the file is additive, not
authoritative.
When submitting a scan via fangs scan submit (or POST /v1/scans),
each Job carries a sandbox spec. Defaults via
watcher.BuildSandboxScan:
| Field | Default | Meaning |
|---|---|---|
image |
node:20-slim |
container image (digest-pinned by the driver) |
command |
npm install <pkg>@<ver> |
shell command run as PID 1 inside |
network_mode |
bridge |
bridge | none
|
pull_policy |
missing |
missing | always
|
user |
0:0 |
UID:GID inside the container |
grace_period |
2s |
wait after main exit before SIGTERM |
duration |
60s |
max sandbox lifetime |
Non-overridable HostConfig (baked into the Docker driver):
| Setting | Value |
|---|---|
| CapDrop | ALL |
| SecurityOpt | no-new-privileges:true |
| Memory | 512 MB |
| NanoCpus | 1 |
| PidsLimit | 256 |
| Ulimits | nofile 1024/2048, nproc 256, fsize 256 MB |
| Tmpfs |
/tmp size=256m noexec, /run size=16m |
| LogConfig | json-file max-size=10m max-file=3 |
| CgroupParent |
/fangs/<run_id> (runner-managed) |
| RestartPolicy | no |
| Volumes | none |
| Devices | none |
Per-package memory / CPU overrides are a v2 item — currently the only escape is a custom sandbox spec in the scan submission.
The eBPF sensor's behavior is mostly hardcoded but a few knobs are exposed:
| Knob | Default | Where |
|---|---|---|
| Ringbuf size | 64 MB per CPU |
bpf/sensor.bpf.c RINGBUF_SIZE macro |
| Max watched cgroups | 256 |
MAX_WATCHED_CGROUPS macro |
| Max watched paths | 1024 |
MAX_WATCHED_PATHS macro |
| Ancestry depth | 5 levels |
ANCESTORS_DEPTH macro |
| Argv capture | 8 args × 64 B |
ARGV_NUM × ARGV_LEN macros |
| TLS dedup window | 5 s |
DedupWindow in sensor.Options
|
| Connect dedup window | 250 ms | hardcoded in connectDedup
|
These require a rebuild to change. See Sensor-Probes.
Run-time inspection of the orchestrator's effective config:
# UI page — shows YAML on disk + effective values + config-managed allowlist entries
http://127.0.0.1:8443/ui/config
# CLI — config-managed entries have cfg-prefixed IDs
fangs allow list
# Log line at startup
"config loaded path=config/orchestrator.yaml watched_paths=14"