bitcollector
bitcollector reads a host and publishes what it finds. It never writes to the host.
It answers "what is on this machine, and what state is it in" — continuously, across a fleet, in a form you can hand to an auditor and defend.
| Release | 0.1.0-ga, commit 8819759, built with go1.25.12 |
| Platforms | Linux amd64/arm64, macOS amd64/arm64, Windows amd64 — see platforms |
| Supply chain | Reproducible build, CycloneDX SBOM, cosign signature, SBOM attestation, signed SHA256SUMS |
| Inbound network surface | None. The only listener is loopback-only |
| Writes to the host | Its own data directory and log file — plus any file you explicitly ask export-pubkey to write |
Before you run it, verify the download.
What it collects and what it costs to run are below. Two things it does are big enough to have pages of their own: the evidence chain — the signing, chaining and offline verification that make the data defensible — and privacy and local accounts, which is what makes it deployable in France without an argument.
What it collects
Nine collectors, each independently enabled and each on its own interval:
| Collector | What it reports |
|---|---|
process | Running processes. Command lines are off by default — see Privacy |
port | Listening ports |
software | Installed packages |
system | Hardware and operating system |
metrics | Host resource metrics |
network | Interfaces and established peers |
posture | Live security posture — see Posture |
accounts | Privileged and dormant local accounts — see Accounts |
file | Log and file inputs (opt-in; disabled in the shipped configuration) |
Two rules run through all of them:
- Every record carries its collection timestamp and the collector that produced it.
- "Absent" and "not allowed to look" are never the same answer. A collector that cannot
run reports why, as a first-class value. An unprivileged agent that cannot read
/etc/shadowreportsunknownwith the reason — it never reports a clean bill of health it did not observe.
That second rule is not a nicety. An inventory that silently reports "no findings" when it was actually refused permission is worse than no inventory, because you will act on it.
Posture: observed, not assumed
The posture collector is the input that turns thousands of findings into the handful that
matter: "CVE present, but control Y is verified enforced."
Everything it reports is observed host state, never a config file:
| Control | Read from |
|---|---|
| SELinux | /sys/fs/selinux/enforce — the running kernel |
| AppArmor | /sys/module/apparmor/…, /sys/kernel/security/apparmor/… |
| Firewall | The live nftables/iptables/ip6tables ruleset, both address families |
| sshd | sshd -T (falling back to sshd -G) — the daemon's own parse, which follows Include |
| Sysctls | /proc/sys/… — not /etc/sysctl.conf |
The distinction is the product. A config file says what someone intended. sshd -T says
what sshd will actually do. Those differ more often than anyone is comfortable with, and it
is the gap between them that fails audits.
It is read-only: six fixed-argv listing commands through an allowlist, no shell, every
file opened O_RDONLY. It runs unprivileged and reports, per control, whether the control
was absent or whether the agent was not allowed to look. Running as root (or with
CAP_NET_ADMIN) additionally yields the firewall ruleset and the AppArmor profile
inventory.
The change feed
Full-state telemetry every cycle is what makes a DBA veto your rollout. The change feed (A4) sends only what changed.
Measured on a real reference host, published with the host shape that produced it:
| Full-state baseline | 284.57 KB/cycle (mean of 4 consecutive 60s cycles, 0.53 % spread) |
| Shipped delta | 6.55 KB mean, 7.84 KB p95, 10.57 KB worst — inside budget on all 21 cycles |
| Host shape | 558 processes, 714 packages, 45 listening ports, 71 established connections, 32 interfaces, 6 collectors, command_line: off, non-root |
86.36 % of the bytes in the list-shaped collectors repeat verbatim every cycle, and about 13 % of process records "change" every cycle on CPU and memory sample values alone — which is why this is a schema split (estate facts vs samples) rather than a diff algorithm. A naive record-level diff measured only 7.4×, still far over budget.
A delta stream reconstructs to exactly the same state as a full snapshot, and a full
snapshot re-anchors the stream on a schedule (snapshot_interval: 6h by default) so a lost
delta cannot silently desynchronise the platform's view of a host.
delta.enabled: false in the shipped configuration, and turning it on is necessary but
not sufficient: the agent additionally requires the control plane to advertise that it
understands the protocol, and keeps sending full state until it does.
That second gate is in code rather than in a runbook because the failure mode is silent. A delta sent to a receiver that does not understand it is forwarded downstream as if it were a full payload — which does not error, it quietly corrupts the platform's picture of your estate. Enable it when your Cert-IX tenant supports it, not as a side effect of another change.
Two domains do not travel on the delta path at all: posture and accounts are
published on the full path only, so an agent with the change feed on would not send them.
That limit is stated in the shipped configuration file, not left to be discovered.
Safe to run on production
The point of this section is permission to install on a machine that matters.
Resource ceilings are enforced, not documented.
agent:
max_memory_mb: 50
max_cpu_percent: 80
max_memory_mbis applied as a Go runtime memory limit, so the garbage collector works progressively harder to stay underneath it. It covers the Go heap, goroutine stacks and runtime structures. It is not an OOM killer: exceeding it makes the agent slower, never dead — an agent that kills itself under memory pressure stops being evidence exactly when something interesting is happening.max_cpu_percentis a ceiling on the agent's own duty cycle as a percentage of one core (80= 0.8 cores). Enforcement is by deferral: when the sliding-window average is over the ceiling, the next collection tick is skipped and counted. Delivery, heartbeats, the replay queue and the local health endpoint are never deferred — a host under load must still be able to ship what it already has.
The deferral is visible when it happens:
Collection tick DEFERRED: this agent is above agent.max_cpu_percent. Delivery and
heartbeats are unaffected; the deferral is counted on the local health endpoint so a
permanently throttled agent cannot pass for a quiet host
Zero inbound network surface. The only listener is a local health/metrics endpoint, and
it binds loopback only — 127.0.0.1 and [::1], as two explicit listeners, with the bind
addresses as compile-time constants. There is no configuration key to widen it, because
a listener an operator can widen eventually gets widened.
curl -s 127.0.0.1:9713/status
A watchdog restarts a wedged collector without restarting the agent. Slow and wedged are
distinguished by measurement, not guesswork: slow means the collector returns late (already
reported as a timeout); wedged means its context was cancelled and it still has not
returned. Log rotation prunes by both size and age, so the agent cannot fill your /var and
hand you an outage.
Store-and-forward. Telemetry that could not be delivered is retried with backoff and
delivered when the gateway returns. A 401 on the telemetry path refreshes the token and
retries. What could not be delivered is attributable — a gap is never indistinguishable
from "no data existed".
Running it
bitcollector is a single static binary. Verify it first
(how), then:
# Check what you have
bitcollector --version
# bitcollector 0.1.0-ga (commit: 8819759, built: 2026-08-09T12:08:13Z)
# Run with a configuration file
bitcollector -config /etc/bitcollector/bitcollector.yaml
# Turn up the detail while you are setting it up
bitcollector -config /etc/bitcollector/bitcollector.yaml -log-level debug
Environment variables override configuration file values:
| Variable | Purpose |
|---|---|
CERTIX_TENANT_ID | Your tenant, from Settings → Organization |
CERTIX_ENROLLMENT_TOKEN | Single-use enrolment token, minted in the dashboard. Required unless mTLS client certificates are configured |
CERTIX_GATEWAY_URL | Agent Gateway (registration, token refresh, policy) |
CERTIX_INGEST_URL | Agent Ingestion Gateway (heartbeat, telemetry) |
CERTIX_TLS_CA_CERT / CERTIX_TLS_CLIENT_CERT / CERTIX_TLS_CLIENT_KEY | mTLS materials |
The agent refuses to start rather than run without an identity it can prove:
ERROR: Failed to load configuration: missing required configuration:
control_plane.enrollment_token (CERTIX_ENROLLMENT_TOKEN) required when mTLS client
certificates are not configured
Tuning what it costs
Per-collector interval: and timeout: values are honoured. Each enabled collector runs on
its own interval and fills a cache; a separate publish tick at agent.collection_interval
sends one batch carrying the domains that produced new data.
Measured per-collector share of a batch, so you can tune against real numbers rather than a
guess (5 cycles at 60s, non-root, command_line: off):
| Collector | Share of batch |
|---|---|
process | 89.79 % |
software | 6.85 % |
port | 1.86 % |
system | 1.20 % |
network | 0.27 % |
metrics | 0.01 % |
The collector to lengthen if you want fewer bytes is process, not software.
timeout: 0s means "not configured": the built-in default is used and the agent logs a
warning naming the key. It is not a way to say "take as long as you need" — every collector
shares one scheduler goroutine, so a run nothing can cancel also stops publishing, the
heartbeat and every other collector. Set the longest a healthy run takes on this host, with
headroom.
Platforms
0.1.0-ga publishes five signed binaries, and nothing is withheld — bitcollector is the
only bit that ships on every platform the family targets:
| Platform | Published | Notes |
|---|---|---|
Linux amd64 | Yes | |
Linux arm64 | Yes | |
macOS amd64 | Yes | |
macOS arm64 | Yes | |
Windows amd64 | Yes | Ships as bitcollector-windows-amd64.exe |
It can do that because it reads, and every collector has a per-OS implementation with an explicit "not available here" answer where a platform has no equivalent. The other three bits each withhold at least one platform, and each of their pages says which and why — bitscanner, bitenforcer, bitmapper.
Shipping on a platform is not the same as observing everything there. Controls the agent does not read on a given OS are reported with a distinct status — not supported by the agent on this platform — carrying the platform as its source and an explicit instruction not to score the control as absent. It is a statement about the agent, never a finding about the host.
That is the same rule as everywhere else on this page: absent, not allowed to look, and not observed on this platform are three different answers, and none of them is a clean bill of health.
Next steps
- The evidence chain — how a batch is signed, chained, and verified offline by someone who does not trust us.
- Privacy and local accounts — what is collected about people, what is not, and what you have to opt into.
- Verifying your downloads — do this before the first install.
- bitenforcer — the other half of the pair.
- Asset Management — where the telemetry lands.
Cette page vous a-t-elle été utile ?