Files
dejima-guard/SECURITY.md
branderson 26d29e92f2 Harden the host path, and describe the risk this actually carries
From a security review by the agent working on dejima, prompted by the operator
asking whether the three gaps this page listed were really the risks, given the
guard usually runs on an ordinary VM with none of the devbox's hardening. They
were not. Five findings, all confirmed by reading the code.

THE UNIT RAN AS ROOT. The container path has always run unprivileged, on the
argument that root would be granting root to a program holding an SSH key to
another machine, for no reason at all. The unit disagreed with its own project:
no User=, and NoNewPrivileges does not drop capabilities for root. So a bug here
yielded root on a machine nothing in this repository hardens. SECURITY.md listed
"runs as a non-root uid" among the stronger properties, which was true of the
container and read as a property of the whole. Now an account of its own,
StateDirectory, an empty capability bounding set, and the rest of what a unit
that opens two connections and writes one directory can give up.

COLLECTION WAS UNBOUNDED AND THE DEVBOX CHOSE THE SIZE. The answer went into a
variable, then a file, then both sides of a diff. That is memory pressure and a
full /var/lib on a host running other things, which is the clearest thing the
watched machine can do to the watcher's. Capped now, with an over-long answer
treated as a finding rather than truncated: truncation would hand the devbox a
way to manufacture a diff, or to cut the state off just before what it added.
pipefail is off across that pipeline on purpose, since head closing the pipe
SIGPIPEs ssh and would report a capped read as a failed connection.

THE SSH CLIENT INHERITED A CONFIG THIS PROJECT DOES NOT OWN. ForwardAgent,
PermitLocalCommand, ClearAllForwardings and ControlMaster are named explicitly
now, on a connection to a host the design assumes may be hostile. Not -F
/dev/null, which would discard a ProxyJump an operator may need to reach the
devbox at all.

THE FIRST BASELINE WAS TAKEN UNATTENDED. accept exists because that is a
decision, and the timer's first fire bypassed it five minutes after boot, with
the warning going to a journal nobody reads yet. check refuses without one now
and sends no heartbeat, so a guard that has not been told what normal looks like
reports as down, which is what it is.

THE CONFIG FILE IS EXECUTABLE-EQUIVALENT, since alert_command runs from it.
Refused if writable by group or others, the way ssh treats a private key.

And the framing, which is what prompted the review: the page led with three
comparisons against dejima. Those are accurate and none of them is what a reader
most needs. It now leads with the two things that bound this program - the host
it happens to run on, and a collector that runs on the machine under suspicion -
and keeps the dejima comparison below them, where it belongs.
2026-08-29 13:07:47 -07:00

6.2 KiB

Security policy

What dejima-guard claims

It notices when a devbox changes, from a machine that is not the devbox, and it tells you. It is a second opinion, not a boundary.

It prevents nothing. Everything it reports has already happened. The controls that bound a compromise are dejima's and dejima-devbox-hardener's; this only reports on the machine those two are protecting.

What it does claim is that its answer is hard to suppress from the devbox: the baseline lives here rather than there, the alert leaves from here, and it pulls rather than being pushed to, so a collection that stops arriving is itself the alarm. Root on the devbox can lie about the current state. It cannot alter what this machine recorded last week, and it cannot make this machine stay quiet without that silence being the signal.

What it holds, which is the new risk it introduces

Watching a machine means holding credentials to it, so this is a concentration of privilege that did not exist before you ran it.

It holds Taking it gets It does not get
An SSH key to the devbox Two read-only audit commands, through a forced command A shell, a file, a tunnel, or drift --accept
An ntfy token Publishing to the alert topic: a forged all-clear, or noise to bury a real one Reading past alerts, if it is write-only as documented
The baseline Knowing exactly what an intruder would have to preserve to stay unnoticed Anything on the devbox

The key is restricted at the devbox end rather than trusted here, which is the half that matters: see "Let the guard in". dejima-guard-install-key proves the restriction rather than assuming it.

What actually bounds this

Two things characterise the exposure, and both matter more than any comparison with the projects around it.

It runs on whatever you run it on. The guard holds a key to the watched box and parses that box's output, and its exposure is bounded by the hardening of a machine this project does not describe and asks nothing of. A NAS, a home server, an existing VM: whatever it is, it is not hardened by anything here. The container path answers this for itself, running unprivileged with every capability dropped and the root filesystem read-only. The host path is a systemd unit, and what that unit asks for is the whole of the answer: its own account, an empty capability bounding set, and memory and task limits.

What the guard does to protect that host: it never runs as root, it caps how much the watched machine may send it, it names the ssh settings a local ssh_config could otherwise widen, and it refuses a config file that is writable by group or others, because alert_command makes that file executable-equivalent.

The collector runs on the watched host. dejima-devbox-audit executes on the devbox, so root there can return whatever it likes, including last week's state verbatim. This is the detection ceiling and no amount of care on this side raises it. The heartbeat proves the guard ran, not that it saw the truth. What the design does buy is that lying has to be deliberate and continuous: the baseline is here, out of reach, so a payload that did not think to look is caught, and one that stops the collection entirely is caught by its silence.

Where it sits next to the other projects

Not a fourth enforcement layer. It enforces nothing, and it is the only one of the four that adds a machine holding credentials rather than taking some away.

Three of dejima's controls have no equivalent here, and are worth knowing rather than discovering:

  • No egress bound. dejima's deny-by-default allowlist covers DNS as well as IP. This has none: the container reaches whatever the host can. Its needs are three destinations, so an allowlist is cheap, but it belongs to your host or your container network. Doing it inside the container would mean granting NET_ADMIN to something that currently drops every capability, which is a worse trade than the gap.
  • The credential is in the container. dejima keeps the model credential out entirely, behind a proxy. Here the private key is mounted. An SSH_AUTH_SOCK from the host would give the same use-without-possession and is not documented yet.
  • No request logging. dejima logs egress verdicts. This logs findings and failures, not connections.

What it deliberately does not do

  • It does not catch an intruder who anticipated it. The devbox-side audit can be lied to by root on the devbox. This catches a payload that did not think to look, which is most of them.
  • It sends no detail off this machine. Alert bodies carry counts and categories, never a filename or a unit name, because the devbox chooses those strings. The detail stays in dejima-guard report.
  • It treats what the devbox says as untrusted input, including in that report: control characters are replaced as the text is collected, since a filename can carry an escape sequence and the report is read on the machine holding the key to the box that named the file.
  • It cannot tell you its alerts are arriving. Sent is not received. test-alert exists so that is a thing you settle deliberately.

This is not proven software

Early development. Nothing here has been through outside review, an external audit, or adversarial testing by anyone other than the person who wrote it. Every security property described above is a claim awaiting scrutiny rather than one that has survived it. Do not put it anywhere that being wrong about it would matter.

Reporting a vulnerability

Report it privately rather than in a public issue, to the address on the maintainer's forge profile. There is one maintainer and no promised response time; you will get an acknowledgement when it is seen, and credit if you want it.

The organisation-wide policy, including what the sister projects claim, is dejima's SECURITY.md.

No warranty

GPL-3.0-or-later, sections 15 and 16. This is provided as-is.