Saltar al contenido principal
Version: Next 🚧

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.

Release0.1.0-ga, commit 8819759, built with go1.25.12
PlatformsLinux amd64/arm64, macOS amd64/arm64, Windows amd64 — see platforms
Supply chainReproducible build, CycloneDX SBOM, cosign signature, SBOM attestation, signed SHA256SUMS
Inbound network surfaceNone. The only listener is loopback-only
Writes to the hostIts 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:

CollectorWhat it reports
processRunning processes. Command lines are off by default — see Privacy
portListening ports
softwareInstalled packages
systemHardware and operating system
metricsHost resource metrics
networkInterfaces and established peers
postureLive security posture — see Posture
accountsPrivileged and dormant local accounts — see Accounts
fileLog 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/shadow reports unknown with 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:

ControlRead from
SELinux/sys/fs/selinux/enforce — the running kernel
AppArmor/sys/module/apparmor/…, /sys/kernel/security/apparmor/…
FirewallThe live nftables/iptables/ip6tables ruleset, both address families
sshdsshd -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 baseline284.57 KB/cycle (mean of 4 consecutive 60s cycles, 0.53 % spread)
Shipped delta6.55 KB mean, 7.84 KB p95, 10.57 KB worst — inside budget on all 21 cycles
Host shape558 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.

The change feed ships OFF, and that is deliberate

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_mb is 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_percent is 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:

VariablePurpose
CERTIX_TENANT_IDYour tenant, from Settings → Organization
CERTIX_ENROLLMENT_TOKENSingle-use enrolment token, minted in the dashboard. Required unless mTLS client certificates are configured
CERTIX_GATEWAY_URLAgent Gateway (registration, token refresh, policy)
CERTIX_INGEST_URLAgent Ingestion Gateway (heartbeat, telemetry)
CERTIX_TLS_CA_CERT / CERTIX_TLS_CLIENT_CERT / CERTIX_TLS_CLIENT_KEYmTLS 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):

CollectorShare of batch
process89.79 %
software6.85 %
port1.86 %
system1.20 %
network0.27 %
metrics0.01 %

The collector to lengthen if you want fewer bytes is process, not software.

There is no value that means "unbounded"

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:

PlatformPublishedNotes
Linux amd64Yes
Linux arm64Yes
macOS amd64Yes
macOS arm64Yes
Windows amd64YesShips 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​

¿Te resultó útil esta página?