Read this first. VIRP is a research prototype. Only the evaluation profile below has been exercised by people other than its author, and only against a simulated target. The lab profile reflects how the author runs it. The production-candidate profile is a design target: it describes what would be required, and several of its controls do not exist yet. Nothing here should be read as a claim that VIRP is ready for production, and the security page controls wherever this page and it disagree.
Profiles at a glance
| Profile | Target | Keys | Requester location | Status |
|---|---|---|---|---|
| Evaluation | Simulated (mock driver) | Disposable, per run | Same machine | Reproduced by third parties, 9/9 |
| Lab | Real equipment, non-production | Software-protected files, 0600 | Separate from the collector | How the author runs it |
| Production candidate | Real equipment | Hardware-backed, external anchoring | Separate security zone | Design target; not achieved |
1. Evaluation reproduced independently
One machine, a simulated target, and keys that are thrown away when you are done. This is what the demo builds, and the profile every other claim on this site is easiest to check against.
one machine +--------------------------------------------------+ | demo/run.sh --unix socket--> virp-onode-prod | | | | | +--> mock driver (in process) | keys + chain live under demo/output/session-*/ | +--------------------------------------------------+ no network access; container runs with network_mode: none
Ports and flows. None. The client reaches the collector over a
Unix domain socket inside the session directory, and the simulated target is in
process. The container declares network_mode: none.
Privileges. An unprivileged user. No root, no capabilities, no system paths written.
Key locations. Everything under
demo/output/session-<timestamp>/run/: the collector HMAC key, the
chain key, and the approver keypair, all generated per run.
Failure modes. If the approver registry fails to load, the harness aborts rather than reporting a partial pass. A failed build stops before the daemon starts.
Backup, upgrade, rollback. None needed. Delete the session directory; re-clone to upgrade.
What you get. Evidence that the protocol behaves as described: classification, refusal, authentication, approval binding, single use, fail-closed defaults, and per-session chain linkage. What you do not get. Anything about real devices, real credentials, real load, or a collector under attack.
2. Lab author-operated
The requester and the collector are separate; the collector talks to real equipment that nobody depends on. This is the first profile where credentials and persistence are real, and the first where operational discipline matters.
requester host collector host lab equipment
+----------------+ +---------------------+ +-------------+
| AI / script / | unix sock | virp-onode-prod | SSH | switch |
| operator client|<------------>| (user: virp) |------->| firewall |
+----------------+ /run/virp | chain.db, seqstore | HTTPS | hypervisor |
+---------------------+ +-------------+
^
| approver signs on a
| separate machine (Ed25519)
Ports and flows. The collector exposes no TCP listener.
Clients connect over a Unix socket at /run/virp/onode.sock, and the
accept path checks the peer's uid via SO_PEERCRED against an allowlist.
Outbound only: SSH (22) to network devices, HTTPS to API-backed platforms.
Privileges. A dedicated virp system user, never
root. The shipped systemd unit sets NoNewPrivileges,
ProtectSystem=strict, ProtectHome,
PrivateTmp, PrivateDevices,
MemoryDenyWriteExecute, RestrictNamespaces, and limits
address families to Unix and IP. Only /etc/virp (read) and
/var/lib/virp (write) are reachable.
Key and state locations.
| Path | Contents | Mode |
|---|---|---|
| /etc/virp/keys/onode.key | collector HMAC key | 0600 virp |
| /etc/virp/keys/chain.key | chain HMAC key | 0600 virp |
| /etc/virp/approvers.json | enrolled approver PUBLIC keys | read-only to the daemon |
| /var/lib/virp/chain.db | evidence chain | 0750 dir |
| /var/lib/virp/ (seqstore) | per-session replay high-water marks | 0750 dir |
| device credentials | root-owned, outside the daemon's reach, rendered at start | 0600 root |
| approver SECRET key | on the approver's machine, never on the collector | n/a |
Failure modes. See the operations section below; the important one is that approval mode refuses to start without a working trust chain, so a broken chain is a loud failure rather than a silent loss of history.
Backup. chain.db, the seqstore, the approvals
directory, approvers.json, and both key files. Losing the keys makes
existing evidence unverifiable; losing the seqstore weakens replay protection.
Upgrade. Stop the unit, replace the binary, start it, then confirm the log shows the approval registry loaded and the chain opened. The registry is read at start, so enrolling or disabling an approver takes effect at the next deliberate restart.
Rollback and uninstall. Reinstall the previous binary and
restart. To uninstall, stop and disable the unit and remove
/opt/virp; keep /var/lib/virp and the keys if you want
past evidence to stay verifiable, and destroy them deliberately if you do not.
What you get. Authenticated, request-linked records of real operations against real equipment, with elevated changes gated behind an Ed25519 approval signed on another machine. What you do not get. Protection against a compromised collector, detection of chain truncation, or any assurance about availability under load. Note also that credential handling in the API layer is marked untested in the project's own security scope.
3. Production candidate design target, not achieved
This profile is written as a specification of what would be required, not a description of something running. Items marked not built are exactly that.
- Collector in its own security zone, reachable only by the requester host, with device management-plane ACLs restricting which sources may reach the equipment at all.
- Hardware-backed keys. Not built: the collector loads key files today. PKCS#11 plumbing exists for the approver side; the collector's own HMAC key has no HSM path.
- External anchoring of chain checkpoints to an independent witness. Not built. Without it, a holder of the chain key can rewrite history, and truncation of the tail is not detectable.
- Two enrolled approver keys, primary and backup, so losing one does not strand the approval path.
- Documented backup, restore, and disaster-recovery drills, tested rather than written.
- Independent security evaluation of the deployment, which has not happened.
What you would get. Everything the lab profile provides, plus detection of collector-side rewriting once anchoring exists. What you would still not get. Certification that a target device reported truthfully. That is outside the protocol's claim in every profile.
Operations
At 2 a.m.: the collector is unavailable
The systemd unit restarts it every five seconds. While it is down, clients cannot submit operations: there is no fallback path that executes commands without producing a record, which is the intended behavior. Automation that depends on the collector stops rather than proceeding unrecorded. Check the unit's log first for the approval registry and chain lines; both are logged at start and both can prevent a start.
At 2 a.m.: the chain database is damaged
Chain writes are transactional and the seqstore is written with a
write-temp, fsync, rename pattern so a crash leaves either the old or the new record,
not a torn one. Crash recovery is covered by tests. If the database is genuinely
damaged, approval mode will refuse to start, which is the loud failure you want:
restore chain.db from backup, restart, and verify the chain replays
before resuming approvals. Be aware of a stated limitation: the C verifier accepts a
truncated tail, so a restore that silently loses recent entries will still verify.
Compare entry counts against your backup, not just the verifier's verdict.
At 2 a.m.: an approval will not apply
Each failure has a distinct code rather than a generic denial: bad signature, command-hash mismatch, device mismatch, expired, reused, not found, key not enrolled, key disabled. Read the code before assuming a key problem. Approvals are valid for 300 seconds and are single use, so a slow change window and a retried apply both produce refusals that are correct.
Clock requirements
The 300-second approval TTL is time-based, so the collector and the approver need roughly agreeing clocks. Run NTP. Large skew shows up as approvals that expire immediately or that outlive their intended window.
Key rotation and compromise
- Approver key. Enroll the new public key in
approvers.jsonand restart; the registry is read at start. To revoke, set the entry'senabledto false or remove it: applies signed by a disabled key are then refused with their own code. Enroll a backup key before you need it. A malformed entry is skipped and logged rather than disabling the rest, and a registry with zero usable keys leaves approvals disabled, which fails safe. - Collector HMAC key. Rotating it means records authenticated under the old key no longer verify with the new one. Retain the old key alongside your archived evidence, or the evidence stops being checkable.
- Compromise. Assume every record and chain entry produced under a compromised collector or chain key is untrustworthy, including ones that verify. This is the honest consequence of symmetric authentication and the reason external anchoring matters. Rotate keys, preserve the old database for forensics rather than overwriting it, and treat the affected window as unevidenced.
Log retention and availability
The daemon logs to the journal through systemd; retention is whatever your journald configuration says. The evidence chain is the durable record, not the log, so back up the database on the schedule your audit needs. The unit restarts on failure and is a single process: there is no clustering, no failover, and no measured availability figure.
Performance and resource requirements
Not published, because they have not been measured under any load worth quoting. The reference collector is a single C daemon with a SQLite database, and the demo completes in about 15 seconds end to end on a modest machine, but that is a correctness run, not a benchmark. Anyone sizing a deployment should measure against their own equipment and share what they find.
Licensing, disclosure, and support
Apache 2.0. Vulnerabilities go to nhoward@thirdlevelit.com rather than a public issue, with acknowledgment inside 48 hours; the scope list and current findings live in SECURITY.md. Support expectations should be set honestly: this is a research project run by one engineer, with no commercial support commitment and no SLA.