Docs menu ▾
How Genos works
Short version: at install, Genos takes a picture of what a server looks like. From then on, the enforcer blocks changes that don't fit that picture and reports what it saw. The fleet server collects those reports, checks they're genuine, and shows you which servers are healthy and which have gone quiet.
The life of a server
sequenceDiagram
autonumber
participant S as Scanner
participant D as Daemon
participant F as Fleet server
participant Y as You
Note over S: Install day
S->>S: Scan host
S->>F: Baseline (once)
S->>D: Start daemon
D->>F: Enroll (token)
F-->>D: agent_id
loop every 30 s
D->>F: Signed heartbeat
end
loop on a protected change
D->>F: Signed decisions
end
F->>Y: Dashboard
- Scan once. The scanner records the host's attack surface: the baseline. It never rescans the same host. After install, the baseline changes only through an authorized process, never because the host changed.
- Enforce. The daemon loads small programs into the kernel that block unauthorized changes, and it logs every decision.
- Report. The daemon sends a heartbeat every 30 seconds and forwards its decisions. Every message is signed with a key that never leaves the host.
- Watch. The fleet server verifies each message and shows the result. A server that stops reporting turns red.
The baseline
The scanner reads only metadata, such as owners, permissions, inodes and which packages are installed. It never reads file contents. It works through the catalog: 78 Linux threat vectors and 191 sub-vectors (persistence, privilege escalation, credential access and so on), each with the places on a system where it could show up.
For every place that exists on the host, the scanner records an entity: a path, its kind, how often it's expected to change, and which vectors it relates to. A typical server has tens of thousands. Two hashes summarize the result:
| Hash | Covers |
|---|---|
entities_digest |
Every entity, exactly as recorded |
canonical_hash |
The baseline's stable identity, used to tie the daemon to the scan |
The scanner keeps a small record of the scan on the host (/var/lib/genos-inventory/baseline.json).
It uploads the full report to the fleet server once.
Enforcement
The enforcer uses BPF LSM: small, kernel-verified programs attached to Linux security hooks. When a process tries to create, rename or link a file inside a protected directory, the hook asks two questions:
Is this directory protected? no → allow
Is there a live, approved grant? yes → allow
no → deny (EPERM)
Every answer goes into a ring buffer, and the daemon drains it and reports it.
Today the protected set is one directory, /etc/sudoers.d, where a single new file can give an
attacker full root. Approved changes go through grants: time-limited permissions that pass a
policy (two different people, a path inside the directory, at most 15 minutes). The policy runs
in a WebAssembly sandbox with no access to anything but the request. A grant request must be
signed with the approver's key (/etc/genos/approver-ed25519.pub). Unsigned requests work only in
an explicitly insecure demo mode.
For the full mechanism, see the enforcer README.
Reporting
The daemon reaches out to the fleet server over HTTPS. The server never connects to the daemon.
| Message | When | Carries |
|---|---|---|
| Enroll | First start | One-time token, the host's public key, hostname |
| Heartbeat | Every 30 s | Posture: enforcing yes/no, attached hooks, policy and scanner hashes, baseline hashes, uptime, kernel, live grants |
| Events | After each decision batch | ALLOW/DENY, action, uid, directory inode, time |
| Lifecycle | Daemon start | daemon_start |
Every message after enrollment is signed and numbered, so the server can tell it's genuine, unaltered and not a replay. For the exact format, see the API reference.
How the dashboard knows a baseline is real
The scanner uploads the baseline before the daemon has an identity, so the upload itself isn't signed. The fleet server closes that gap by comparing two things it receives separately:
flowchart LR
up["Scanner upload<br/>canonical_hash A"] --> cmp{"Same hashes?"}
hb["Signed heartbeat<br/>canonical_hash B"] --> cmp
cmp -- "A = B" --> ok["Verified"]
cmp -- "A ≠ B" --> bad["Mismatch + alert"]
The daemon reads its hashes from the scan record on the host, and signs every heartbeat. If they match the upload, the baseline is Verified. If someone uploaded a different baseline for that host, the dashboard shows Mismatch and raises an alert.
What the fleet server can't do
By design, it has no way to send anything to a host: no commands, no config, no policy. Changes to a host go through your own channels and are checked on the host. That means compromising the fleet server doesn't hand an attacker your servers. See the Security model.