The collection half of a detection pass. dejima-guard PR 22 is the reading half.
No new attack surface, which is the constraint this was done under. No new command, no new sudoers entry, no new key permission, no new data path. The audit already ran as root under the same rule and already returned this shape of text. It was looking in fewer places.
Persistence that never needed root
Everything watched needed root to write: /etc, /usr/local/{bin,sbin}, and the two .ssh directories. A dejima session runs as the ordinary account, so the cheapest persistence available to it sat outside every watched path: a user systemd unit in ~/.config/systemd/user, a crontab (which crontab -e writes to the spool, not /etc), a shell rc file, or a binary in ~/.local/bin, which is on that account's PATH and is where dejima installs its own CLIs.
Named files under the home directory rather than the directory itself, which holds the work this box exists for and would be both enormous and drift on every save.
Two gaps in how, not where
Symlinks were invisible.find -type f returns nothing for one, so replacing a watched binary with a link to another removed a FILE line and added nothing, and a removal reads as tidying rather than substitution. LINK lines record the target. This also catches user-unit enablement, since that writes a symlink into a .wants directory.
UDP listeners were invisible.ss -ltn is TCP only, so a beacon binding a datagram socket produced no line at all.
Worth flagging, because it nearly went in as a one-flag change: adding -uadds a column. The local address moves from field 4 to field 5, and field 4 becomes the send queue, so -ltun with the old $4 would have recorded a queue depth as a port on every line, quietly. There is a test for that specific misreading.
v6, and why it needs one
The PORT format now carries the protocol, so a baseline taken before this differs from every state taken after it. Guest only, live-safe, no snapshot. The note says to re-accept on the guard, and to read what accept is about to record before running it: those paths have never been looked at, so the first baseline over them is what decides "normal" there.
Tested: 186 passing, up from 181. The four new collection tests were run against the previous audit and fail on it. The repository's own address check caught an RFC 1918 literal in one of them, which is now an RFC 5737 documentation address.
**Summary generated by LLM agent**
The collection half of a detection pass. dejima-guard PR 22 is the reading half.
**No new attack surface, which is the constraint this was done under.** No new command, no new sudoers entry, no new key permission, no new data path. The audit already ran as root under the same rule and already returned this shape of text. It was looking in fewer places.
## Persistence that never needed root
Everything watched needed root to write: `/etc`, `/usr/local/{bin,sbin}`, and the two `.ssh` directories. A dejima session runs as the ordinary account, so the cheapest persistence available to it sat outside every watched path: a user systemd unit in `~/.config/systemd/user`, a crontab (which `crontab -e` writes to the spool, not `/etc`), a shell rc file, or a binary in `~/.local/bin`, which is on that account's PATH and is where dejima installs its own CLIs.
Named files under the home directory rather than the directory itself, which holds the work this box exists for and would be both enormous and drift on every save.
## Two gaps in how, not where
**Symlinks were invisible.** `find -type f` returns nothing for one, so replacing a watched binary with a link to another removed a `FILE` line and added nothing, and a removal reads as tidying rather than substitution. `LINK` lines record the target. This also catches user-unit enablement, since that writes a symlink into a `.wants` directory.
**UDP listeners were invisible.** `ss -ltn` is TCP only, so a beacon binding a datagram socket produced no line at all.
Worth flagging, because it nearly went in as a one-flag change: adding `-u` **adds a column**. The local address moves from field 4 to field 5, and field 4 becomes the send queue, so `-ltun` with the old `$4` would have recorded a queue depth as a port on every line, quietly. There is a test for that specific misreading.
## v6, and why it needs one
The `PORT` format now carries the protocol, so a baseline taken before this differs from every state taken after it. Guest only, live-safe, no snapshot. The note says to re-accept on the guard, and to read what `accept` is about to record before running it: those paths have never been looked at, so the first baseline over them is what decides "normal" there.
Tested: 186 passing, up from 181. The four new collection tests were run against the previous audit and fail on it. The repository's own address check caught an RFC 1918 literal in one of them, which is now an RFC 5737 documentation address.
Everything drift watched needed root to write: /etc, /usr/local/{bin,sbin} and
the two .ssh directories. A dejima session runs as the ordinary account, so the
cheapest persistence available to it was outside every watched path - a user
systemd unit, a crontab in the spool rather than /etc, a shell rc file, or a
binary in ~/.local/bin, which is on that account's PATH and is where dejima
installs its own CLIs.
Named files under the home directory rather than the directory itself, which
holds the work this box exists for and would be enormous and drift on every
save.
Two more gaps were in how the state was collected rather than where.
SYMLINKS WERE INVISIBLE. `find -type f` returns nothing for one, so replacing a
watched binary with a link to another removed a FILE line and added nothing, and
a removal reads as tidying rather than as substitution. Recorded as LINK lines
with their targets, which also catches user-unit enablement, since that writes a
symlink into a .wants directory.
UDP LISTENERS WERE INVISIBLE. -ltn is TCP only, so a beacon binding a datagram
socket produced no line at all. Adding -u also ADDS A COLUMN: the local address
moves from field 4 to field 5 and field 4 becomes the send queue, so a flag
added without moving the field would have recorded a queue depth as a port on
every line. The netid is printed too, since a UDP and a TCP listener on the same
number are different facts.
v6, guest only and live-safe, because the PORT format change means a baseline
taken before this differs from every state after it. The note says to re-accept
on the guard and to read what it is about to record first: those paths have
never been looked at, so the first baseline over them is the one that decides
what "normal" means there.
None of this widens what the guard's key may run. The audit already ran as root
under the same sudoers rule and already returned this shape of text; it was
looking in fewer places.
The note said `dejima-devbox-upgrade mark-guest 6`. There is no mark-guest and
there never has been: bootstrap records the guest version itself, because that
is the half a box can know about itself, and mark-node exists for the half it
cannot see. So the note ended on an error, on the step that was supposed to
confirm the upgrade had landed.
A test now checks every dejima-devbox-upgrade subcommand named in a note
against the ones the tool actually dispatches. The existing test in this area
asked whether a note invoking the tool also runs bootstrap, which this note did,
so it passed while telling the operator to run something that does not exist.
branderson
approved these changes 2026-08-29 15:52:05 -07:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary generated by LLM agent
The collection half of a detection pass. dejima-guard PR 22 is the reading half.
No new attack surface, which is the constraint this was done under. No new command, no new sudoers entry, no new key permission, no new data path. The audit already ran as root under the same rule and already returned this shape of text. It was looking in fewer places.
Persistence that never needed root
Everything watched needed root to write:
/etc,/usr/local/{bin,sbin}, and the two.sshdirectories. A dejima session runs as the ordinary account, so the cheapest persistence available to it sat outside every watched path: a user systemd unit in~/.config/systemd/user, a crontab (whichcrontab -ewrites to the spool, not/etc), a shell rc file, or a binary in~/.local/bin, which is on that account's PATH and is where dejima installs its own CLIs.Named files under the home directory rather than the directory itself, which holds the work this box exists for and would be both enormous and drift on every save.
Two gaps in how, not where
Symlinks were invisible.
find -type freturns nothing for one, so replacing a watched binary with a link to another removed aFILEline and added nothing, and a removal reads as tidying rather than substitution.LINKlines record the target. This also catches user-unit enablement, since that writes a symlink into a.wantsdirectory.UDP listeners were invisible.
ss -ltnis TCP only, so a beacon binding a datagram socket produced no line at all.Worth flagging, because it nearly went in as a one-flag change: adding
-uadds a column. The local address moves from field 4 to field 5, and field 4 becomes the send queue, so-ltunwith the old$4would have recorded a queue depth as a port on every line, quietly. There is a test for that specific misreading.v6, and why it needs one
The
PORTformat now carries the protocol, so a baseline taken before this differs from every state taken after it. Guest only, live-safe, no snapshot. The note says to re-accept on the guard, and to read whatacceptis about to record before running it: those paths have never been looked at, so the first baseline over them is what decides "normal" there.Tested: 186 passing, up from 181. The four new collection tests were run against the previous audit and fail on it. The repository's own address check caught an RFC 1918 literal in one of them, which is now an RFC 5737 documentation address.
Everything drift watched needed root to write: /etc, /usr/local/{bin,sbin} and the two .ssh directories. A dejima session runs as the ordinary account, so the cheapest persistence available to it was outside every watched path - a user systemd unit, a crontab in the spool rather than /etc, a shell rc file, or a binary in ~/.local/bin, which is on that account's PATH and is where dejima installs its own CLIs. Named files under the home directory rather than the directory itself, which holds the work this box exists for and would be enormous and drift on every save. Two more gaps were in how the state was collected rather than where. SYMLINKS WERE INVISIBLE. `find -type f` returns nothing for one, so replacing a watched binary with a link to another removed a FILE line and added nothing, and a removal reads as tidying rather than as substitution. Recorded as LINK lines with their targets, which also catches user-unit enablement, since that writes a symlink into a .wants directory. UDP LISTENERS WERE INVISIBLE. -ltn is TCP only, so a beacon binding a datagram socket produced no line at all. Adding -u also ADDS A COLUMN: the local address moves from field 4 to field 5 and field 4 becomes the send queue, so a flag added without moving the field would have recorded a queue depth as a port on every line. The netid is printed too, since a UDP and a TCP listener on the same number are different facts. v6, guest only and live-safe, because the PORT format change means a baseline taken before this differs from every state after it. The note says to re-accept on the guard and to read what it is about to record first: those paths have never been looked at, so the first baseline over them is the one that decides what "normal" means there. None of this widens what the guard's key may run. The audit already ran as root under the same sudoers rule and already returned this shape of text; it was looking in fewer places.