Agent Payload Signing
Since the hardening round of May 2026, each agent signs its ingest payloads with an own Ed25519 keypair. The hub-side bearer-token alone is not sufficient to push data on behalf of an agent — the attacker also needs the private signing key of the host.
How it works
Agent (first start) ├─ generates Ed25519 keypair ├─ writes secret to /var/lib/monsys/agent-signing.key (mode 0600) └─ sends public key to hub via /api/v1/agents/register │ ▼ Hub pins the key in agents.signing_pubkey (trust-on-first-use)
Agent (each ingest) body = "[{...metrics...}]" sig = base64(Ed25519(SHA256(body))) HTTP POST /api/v1/ingest Authorization: Bearer ms_... X-Monsys-Signature: <sig>
Hub pubkey = SELECT signing_pubkey FROM agents WHERE token_hash = … if pubkey NULL → accept + log "unsigned_payload" (medium) if sig missing → 403 + log "unsigned_payload" (high) if sig invalid → 403 + log "signature_invalid" (critical) otherwise → ✓ processFile Location
| Platform | Path |
|---|---|
| Linux | /var/lib/monsys/agent-signing.key |
| Windows | C:\ProgramData\monsys\agent-signing.key |
Permissions on Linux: mode 0600, owner = the user running the agent (user/monsys depending on your install). Windows: ACL restricted to SYSTEM + Administrators.
Content is 32 bytes (raw Ed25519 secret seed). No header, no PEM — keep the file exactly that size.
Trust-on-first-use semantics
The first time an agent sends a pubkey in register, the hub pins it and sets signing_set_at = NOW(). From then on, the pin is immutable from the agent — a new pubkey will be rejected and generates a signature_invalid integrity-anomaly.
To rotate legitimately, an admin must explicitly unset the pin via:
POST /api/v1/agents/<id>/rotate-signing-key(see Token rotation)
What an attacker with only the bearer token cannot do
| Attack | Blocked by |
|---|---|
| Fake metrics push under same agent | signature mismatch → 403 |
| Honeypot trip falsification | signature mismatch → 403 |
| Process DNA hash replacement | signature mismatch → 403 |
| Version downgrade in reporting | signature mismatch → 403, plus version_downgrade integrity check |
What an attacker with token + key file can do
If both are stolen, the attacker can fully report on behalf of the agent. The key file is however outside /etc/ (where system-config backups typically go), and is mode 0600. Concrete measures to prevent both from leaking together:
- Keep the key file out of backups (
backup_excludein restic / borg) - Rotate the signing-key after a confirmed security event
- Monitor changes on
/var/lib/monsys/agent-signing.keywith the host’s own FIM (auditd,auditctl -w /var/lib/monsys/agent-signing.key -p wa)
Migration of existing installations
An older agent without signing module will continue to work: the hub accepts the unsigned payload but makes a unsigned_payload-anomaly every hour so it doesn’t go unnoticed.
Upgrade path:
# On the hostsudo apt update && sudo apt upgrade monsys-agent# Or via install script:curl -fsSL https://get.monsys.ai/install.sh | sudo bashsudo systemctl restart monsys-agentThe agent then generates a keypair, registers the pubkey, and the unsigned_payload-anomalies are automatically closed in the next integrity cycle (10 min).