Data privacy & scope
Running a closed-source agent on your servers deserves scrutiny. This page states, precisely and completely, what the agent reads, what leaves the host, and the boundaries it will not cross — so you can decide based on facts, not trust.
The short version: the agent reports operational metadata, never secrets or file contents. It is not a general-purpose collector that can be pointed at arbitrary data. It runs a small, fixed set of collectors (see the full list) and nothing else — there is no scripting, no plugin system, and no remote instruction channel that could tell it to read something new.
The fears, answered directly
my.cnf, .env, connection strings), and it never connects to or queries a database. It knows only that a database package is installed and its version — exactly what dpkg -l already shows./etc/passwd or /etc/shadow, and never reports the name of an account on the machine, nor shells, home directories or password hashes. The one user name it can send is the opposite of an account: with the opt-in grant-ssh-logs, up to five names per address that an SSH guesser tried and that sshd itself logged as invalid user — names that do not exist on the server, like a bot's admin or oracle. When sshd names a real account, the agent drops the name on the host.| You might fear it reads… | What the agent actually does |
|---|---|
Database / app passwords, .env, connection strings | Never opened. No config files are read for their contents. |
/etc/shadow, password hashes | Never read. |
Local users, usernames, shells (/etc/passwd) | Not enumerated, and no existing account’s name is ever sent. Only with grant-ssh-logs: names sshd reported as invalid user in failed SSH logins (see SSH logins below). |
Environment variables (printenv, Environment=) | Not collected. |
Process command lines (--password=… in ps) | Not collected — there is no process-argument collector. |
| SSH keys, API tokens, cloud credentials | Never touched. |
| TLS private keys | Never read. Only the public certificate metadata is observed. |
| Your application source code or data files | Not read. The agent does not crawl your filesystem. |
System or application logs (journald, syslog, auth.log) | Not read by default. When a service fails, the agent reports systemd’s status for it — result, exit code, restart count — not what it logged. Its account isn’t in systemd-journal or adm, so the OS refuses it the journal. The exceptions are opt-in: with grant-ssh-logs (v0.22+) it reads sshd’s messages only, and sends per-address counts of failed logins — see SSH logins below; with grant-fail2ban-logs (v0.24+) it joins adm and reads fail2ban’s ban lines only — see fail2ban bans below. |
| Web server access logs | Not read unless you allow it. They belong to root:adm, which the agent’s account is not in. If you run grant-web-logs, it may read the web server log directories — and only those — to summarize traffic per client address. No log line ever leaves the host (see below). revoke-web-logs takes the access back. |
Exactly what leaves the host
This list is exhaustive. Everything the agent transmits falls into these categories, and nothing else:
-
Host identity — hostname, OS and version, kernel, CPU architecture, and a stable machine fingerprint (a hash used to recognize the same machine across reports; it is not a hardware serial or personal data).
-
System metrics — numeric usage figures over time (CPU, memory, disk). Numbers, not contents.
-
Package posture — installed package names and versions, plus counts of pending and security updates and whether a restart is required. The names/versions are needed to correlate published vulnerabilities against what you actually run; they are the same non-secret identifiers your package manager exposes.
-
Kernel and service hardening (v0.29+) — kernel parameters from
/proc/sysand thesysctl.dfiles, mount options from/proc/mounts, which of four protocol modules is loaded (/proc/modules), and systemd’s exposure score per service (systemd-analyze security, which any user may run). For SSH, more named directives ofsshd_config— and forAllowUsers/AllowGroups, only whether one is set, never the names — plus what sshd offers a client: every 6 hours the agent opens a connection to the host’s own sshd on127.0.0.1, reads its version and algorithm lists, and closes before any login is attempted. sshd logs it asConnection closed by 127.0.0.1 … [preauth]; nothing is authenticated and nothing leaves but the algorithm names. -
Hardening audits (v0.29+, opt-in) — only once you run
service.sh grant-audit <tool>: the report of your Lynis or OpenSCAP run, parsed on the host. What leaves is the tool’s version and score, the count of results by status, and for each finding its identifier (SSH-7408, an XCCDF rule id), status, severity and title. Lynis’s free-text details — which can name accounts, files or values — are dropped on the host, and a home directory in a title is sent as~/…. -
Software installed outside apt (v0.28+) — the name, version and location of Node.js runtimes, npm’s global packages, Go toolchains and Go-built binaries, Composer phars, and (v0.30+) Python interpreters and the packages pip installed outside apt (virtualenvs included, one row per package), read from the version files they ship (never by running them), plus whether a process runs each one. To know that, the agent reads the processes’ command lines on the host and compares them with those locations; only the yes/no leaves, never a command line or an argument, which is where a password passed on the command line would be. A location inside a home directory is sent as
~/…: which account’s home it is stays on the host. Where the system hides other users’ processes, the agent says it cannot tell rather than “not running”. -
Service posture — how many services are running, how many failed, the names of the failed units, and for each of those why it stopped: systemd’s result category (
exit-code,timeout,oom-kill…), the exit code or the signal that killed it, how many times it has been restarted, and since when it has been down. This is systemd’s own accounting for the unit — not its logs. When the failed unit is a certbot renewal (v0.26+), it adds the names of the certificates certbot renews — the file names in/etc/letsencrypt/renewal/, plus the oneauthenticatorline of each — and whether each name still resolves, asked of the host’s own resolver. Nothing else in those files is read, and nothing underlive/orarchive/. The agent reads no unit’s logs for this: its account is not insystemd-journaloradmunless you grant it SSH logins or fail2ban’s bans (below), and even then it reads sshd’s messages and fail2ban’s ban lines only. No process contents, no arguments. -
Served domains — the domain names (FQDNs) served, whether TLS is enabled, the document-root path, and which web server. These specific directives are extracted from your Apache/Nginx virtual hosts — nothing else in those files is read (no rewrite rules, no
proxy_passcredentials, no included secret files, no auth directives). -
Certificate metadata — the public parts of the TLS certificates in use: issuer, subject and validity dates. This is the same information any client sees during a TLS handshake. Private keys are never read.
-
Package history — past
apttransactions from/var/log/apt/history.log: the timestamp, the command line, and the package names installed, upgraded or removed. This is what gives a server a timeline that predates the agent. The log also records aRequested-By:line naming the local account that ran the command — the agent parses past it and never sends it, because what changed on a machine is a fact about the machine, and who typed it is a fact about a person. -
Web traffic summaries (only if you ran
grant-web-logs) — per client address, over each collection window: request and status counts (2xx/3xx/4xx/5xx), how many of your vhosts it touched, when it was first and last seen, up to five sample request paths with their query strings removed, and its user agent. Only the busiest addresses and the most error-prone ones are sent, not everyone. This is what shows you a scanner sweeping your sites. With them come the detection rule firings computed on the host: which built-in rule fired on which address, when, what it would have done (they run in shadow mode — nothing is blocked), and the few paths that made it fire. And, per minute, how many requests were served, how many ended in an error, and which of your vhosts returned server errors — counts, so an attack can be lined up with its impact. Raw log lines never leave the host, and neither does a query string — that is where reset tokens and email addresses live. On Demerzel’s side, sample paths and user agents are deleted after 30 days and the summaries after 90. Without the grant, the agent only reports that it cannot read these logs. A replay (demerzel-agent replay --send, run by you on the host, never by the server) sends the very same summaries for a past range, plus the paths of the log files it read and fingerprints (hashes) of the rules, file list and lines it ran on — not the lines themselves. -
Web stack posture — a handful of named settings that decide how an attack lands, read from world-readable config files: MySQL/MariaDB’s
max_connectionsand whether the slow query log is on, each PHP-FPM pool’s name, process manager andpm.max_children(and whether that PHP version is running), and whether Apache (mod_remoteip) or Nginx (real_ip_header) logs the visitor behind a proxy. Nothing else in those files is read, anddebian.cnf— which holds the database maintenance password — is never opened. From the access logs come two more counts: per vhost, how many requests arrived through Cloudflare; and any secret file (.env,.git/config, a SQL dump…) a site answered with real content — its path, status and size, never the content. Answers that are a site’s catch-all page are recognized and dropped on the host. From v0.21, three sshd settings —PasswordAuthentication,KbdInteractiveAuthentication,PermitRootLogin— and its ports, read from/etc/ssh/sshd_configand the files it includes (a root-only one is reported by name, never read), and whetherunattended-upgradesordnf-automaticis on. No key, no user name, nothing else from those files. -
SSH logins (only if you ran
grant-ssh-logs) — per client address, how many SSH connections failed to log in within a few minutes, and the user names tried when no such account exists on the server (a bot’sadmin,oracle). Never a log line, never the name of an existing account, never anything about successful logins except that one happened (which keeps that address from counting as a guesser). -
fail2ban bans (only if you ran
grant-fail2ban-logs) — for each ban or unban a fail2ban jail logged: the jail’s name, the address, the action and the time. Theadmgroup would let the account read other logs in/var/log; the agent opensfail2ban.logonly, and keeps nothing of it but those four fields. Demerzel keeps them a year, like the warden’s own blocks. -
What the warden did (only if you installed the warden) — its status (mode, the rules you cleared to enforce, its caps, how many addresses it blocks), and its journal: each address it blocked, throttled, refused to block or unblocked, with the rule and the reason. That includes the address of an operator it protected because they were logged in over SSH — never the account name. The agent reads this from the warden’s files; it never writes them. Demerzel keeps it for a year: a block is an act on someone’s traffic and has to stay explainable.
Notice what is absent: no file contents, no credentials, no environment, no user lists or account names, no process arguments.
Read-only, and least-privilege
The agent observes and summarizes only. It performs no remediation, opens no inbound port, and takes no command from the server: nothing Demerzel sends can make it read, run or change anything. If you choose to block attackers, that is done by a separate component you install on purpose, the warden — the agent only writes it requests, which the warden checks against a policy only root can change — a compromised Demerzel server still has no path to execute anything on an agent host (see Security & mTLS identity). With fleet decisions on, the agent also fetches the bans and unbans Demerzel signed for your servers and copies them, unread, to the warden; the warden takes only what your organization’s key signed, and only within its policy. The fetch sends nothing but where it left off.
Run it as a dedicated unprivileged user that owns only its config directory. This is defense-in-depth: the code doesn’t attempt to read secrets, and because it isn’t root, the OS wouldn’t permit it to read /etc/shadow or another user’s files even if it tried.
Summarized on the host
Facts are reduced on the machine before anything is sent. Demerzel receives compact, structured facts — counts, versions, names, timestamps — not raw logs, config dumps or file streams. Less data leaves the box than most monitoring agents send.
On Demerzel’s side, each message is kept as received for 30 days (the server’s page shows the latest ones, payload included), then only what it meant remains: the server’s state and history, metrics, and the evidence above on their own retention clocks.
You don’t have to take our word for it
Because the agent is closed-source, these guarantees are designed to be verifiable from the outside, regardless of the binary:
- Control the egress. The agent is outbound-only and talks to a single endpoint. Firewall it so it can reach nothing but your Demerzel instance, and observe its connections — there is no second destination.
- Inspect the payloads. Run
demerzel-agent inspect > payload.json. It runs the real collection cycle — same collectors, same envelopes, same bytes — and prints the exact requests it would have made, without opening a connection. It works before you enroll, so you can look first and decide after. A test in the agent’s build fails ifinspectever drifts from what a live run sends. - Audit what was sent.
demerzel-agent transmissionslists every transmission the agent has made: when, to which endpoint, how large, how long it took, and whether it succeeded. The journal keeps each payload’s SHA-256, not the payload. - Constrain it with the OS. Run it unprivileged; audit what that user can access. The agent can only ever read what the OS lets that user read — and it deliberately reads far less.
- No inbound surface. There is no listening socket, so there is nothing to exploit as a foothold.
In transit and at rest
Everything is sent over HTTPS, authenticated by the agent’s own certificate identity (mTLS). On the server, observations are stored as immutable, append-only evidence — records are never rewritten, so what was reported is always auditable.