Security-Model: invite secrets stay out of the registry, and why that was measured
They are the obvious fourth needle and the answer is counter-intuitive: only inviteHash(secret)
is persisted, so the plaintext lives in one frame and is dropped. Measured on a real jam — none
of the eight state-dir files, not the daemon log, not the pane. Registering one would require the
daemon to start retaining every live invite's plaintext, creating the exposure in order to close
it. A discarded secret cannot be printed.
Security: the 0.23.1 findings — the view, the hook secret, and discovery
Security-Model:
- the browser view bullet becomes two: the URL is a credential and IS the join token when one
is set (so a leaked view link is a leaked join link, /token new revokes it and /kick does
not), and read-only is real as of 0.23.1 — earlier versions rested it on ttyd's own default,
which is version-dependent, and ttyd honours a resize even when it refuses input, so a
viewer could shrink the host's live pane from anywhere.
- a new section on the third secret and the registry: what the hook secret authenticates, why
session.json's lower-case "secret" was the one shape the pattern masker ignored, that holding
it alone was sufficient, and why the fix is a registry with two tests as the forcing function
rather than a fourth hand-threaded needle. Invite secrets are excluded, with the reason.
- the mDNS section gains the direction it never covered: find does not authenticate anybody,
anybody on the network can publish a look-alike advertisement, and what changed (no printed
--token, address leads, the warning line, invite links as the safe path).
Joining-a-Jam: the example listing updated to the new column order and the new footer, why the
address comes first, and the token row now points at an invite link as the better answer.
Remote-Access: the same two facts about the view URL, at the place people actually turn it on.
Security Model: a Windows file reduces to one ACL entry, a directory does not
Measured on the first Windows CI run this project has ever had (2026-08-30),
which contradicted the page: it said the `(OI)(CI)` form on a directory
leaves the same single entry a file gets. It does not. `icacls` exits 0 and
the directory keeps NT AUTHORITY\SYSTEM and BUILTIN\Administrators beside the
owner; re-applying changes nothing.
Says so now, with what it does and does not mean: a weaker guarantee rather
than a hole (both principals read anything on the machine whatever a DACL
says), the true assertion CI makes (owner granted, no principal beyond those
three — no Users, no Everyone, no Authenticated Users), and the fact that
with no Windows host there is no state directory on Windows to begin with.
Security Model: Windows has no mode bits — say ACL, and say who verified it
v0.32 W1 replaces 0600/0700 with an NTFS ACL of one entry on Windows
(icacls /inheritance:r /grant:r <user>:F, (OI)(CI) on a directory). The page
said 0600 as though it were universal. It now also says the two things a
reader needs and would otherwise assume: a failure degrades to profile
inheritance rather than failing the write, and the path is verified only by
a Windows CI runner — no human has watched it work.
NOT PUSHED.
Security Model + Joining: authenticate before answering anything about the roster (0.22.1)
Until 0.22.0 a hello naming somebody already in the jam was refused by name and closed
4409 BEFORE any admission, so the roster was enumerable name by name with no token —
unlimited, since the close happened above the pending set. Measured: probing Roy, Dana
and Nobody returned 4409, 4409 and a pending knock.
The page now states the fix as what it is — ordering, not silence. A token or invite
holder is still told at once. A knocker is not told at all, and their clash is settled
at admission: they join as Dana-2 and are told so. The host IS told, on the frame they
approve from, because otherwise a stranger could make the approval bar read a name that
is in the room.
Plus the one behaviour change: `name already connected` was the only one of the five
invite refusal reasons that closed the socket rather than falling through to a knock.
It now falls through like the other four, so reconnecting on your own link while your
stale socket is still in the roster is no longer a lockout.
Security model, files and peer tasks: what the 2026-08-30 review found and fixed
Four repairs to the pages, all from measurements rather than restatement:
- the wrapped-row scrub was documented as an accepted ceiling; it was measured and is
the MAJORITY case ((L-1)/W, so 79% for the 64-hex key at 80 columns, certain below
64), and it is closed in 0.22.1. The remaining case is an escape sequence inside the
value.
- the free-text answer is raw keyboard access and now says what that meant: it was the
one participant text that skipped stripControl, so a CR submitted claude's field and
typed the rest as a second, unattributed prompt. Plus the adversarial input set the
relay was actually driven with.
- jam-uploads/ confinement is a NAME filter, and a name filter cannot see a
filesystem: a dangling symlink carried an upload out of the directory until the write
became exclusive. And the quota is counted at grant time now, not only at write time.
- the peer result's agent-facing copy is fenced, and a result could close that fence.
Also: a result's text never reaches /peers log at all.
Plus what the invite blob is worth (nothing — every field is re-checked server-side on
every use, driven), what its address list is and is not, and the one thing a stranger
can learn pre-approval: the roster, name by name, in knock or token mode.
Security Model: every way out is scrubbed, not just /export (0.22.0)
v0.34 said the host key is scrubbed from /export and reasoned it could not
reach a transcript at all, since it is never told to claude. The 0.22.0 release
gate found the hole in that: claude runs as the host user with file tools, so
any participant can ask it to read host.key, and the answer lands on the pane
and in the transcript — both of which the daemon broadcasts.
Documents all three paths as a table, the join token getting the same treatment
(and why knock mode makes that the bigger of the two), literal-over-pattern and
why, and the wrapped-row ceiling with the rotate-the-jam advice that follows
from it.
Also corrects the ceilings list, which still said claude-jam "strips its own
token block and the raw token, and nothing else" on export alone.
Security Model: host authority is a local file, not a network address (v0.34)
0.21.1 closed F1 by reading the proxy headers a relay cannot suppress. That
holds for cloudflared, which was measured, but it is a blocklist: it
enumerates what a relay looks like, and the next relay that proxies to
loopback without a header on that list re-opens the hole silently.
v0.34 makes the answer a 0600 file in the 0700 state dir that only a local
process can read, with the header test kept as a second, independent
condition. The page now says which two conditions host requires, why a file
replaced the inference, that the key grants nothing filesystem access did not
already grant, that it is scrubbed and never logged, and that there is no
fallback -- a client with no key file joins as a guest and says so.
the release gate's doc check: five shipped features the entry page called unbuilt
Home.md's "Not built yet" list was never revisited after the first commit, so the entry
page — the one an agent reads first — was telling people that LAN discovery, join/knock
sounds, /nudge, `--uploads auto` and guest-side scrollback do not exist. All five have
shipped and all five have a page here. What remains genuinely unbuilt is Windows, and
Linux is honest about being thinly proven rather than silently fine.
Hosting-a-Jam: the flag table was missing `--history` and `--peer-tasks`, and then
claimed `--help` prints the same list. It does not, in either direction — the table also
carries four flags `--help` deliberately leaves out. Say what is actually true.
Architecture: 283 tests -> 389, two lints -> four, thirteen smokes -> eighteen, and it
quoted a SPEC.md heading ("Running the thirteen end-to-end smokes") that has never
existed under that name.
Joining-a-Jam: /peer and /peers were missing from the only command list a guest gets,
on the release that ships them. Verified every form against parseClientLine.
Security-Model: the mDNS example record still advertised v0.18.0.
wiki: sounds, nudges, and the upload policy that only removes the prompt
- Files-and-Export: the three upload policies in a table, the list of
protections that do NOT move with them (and the note that smoke-nudge proves
each one still refuses while the policy is auto), the 40-file/200 MB session
quota and why the fallback line exists, and export's own separate toggle.
- Hosting-a-Jam: the four new flags in the flag table; a 'Hearing what needs
you' section with the two arrival sounds, why they differ, the once-only
knock repeat, and the three toggles.
- Joining-a-Jam: /ping and /sound in the command table, and a full section on
nudges — what the addressee sees, what the room sees, why it is never queued,
the rate limit, the once-only escalation, what idle actually reports, and the
opt-in phone config with the topic warning.
- Security-Model: a new section, 'The ntfy topic stays on your machine' — the
topic is a bearer credential, it lives only in the recipient's config, their
own client posts it, and it is never in a frame, an invite link, the
protocol or a log. Plus three new entries under 'What to assume': the upload
policy is a prompt setting and not a protection, export is separate and stays
ask, and a nudge is deliberately not on the approval ladder.
wiki: finding a jam on your network, and what announcing tells the room
Joining-a-Jam leads with finding one, because on the same wifi that is now the
easiest way in — the find/join commands, a real listing, and a table of what
each `access` value still requires of you. It also answers the three ordinary
reasons a jam is not in the list (the host used --no-announce, you are not on
that network, this machine has no mDNS tool), none of which is a fault.
Security-Model gets the privacy note as a section of its own: the six fields
that go on the wire and why each is safe, the list of what is never in it and
the fact that this is enforced by an allow-list rather than a redaction pass,
and the two consequences worth thinking about — that a jam name is a project
name and the default is the directory's, and that on an untrusted network you
should turn it off. Plus the point that discovery does not bypass any gate on
that page, and the one it would be easy to get wrong: --no-announce hides the
name, not the port. That is a bind and a firewall question.
Hosting-a-Jam gets the two flags, told apart from --tmux (which is the
identifier, not the label), and a short section pointing at the full note.
wiki: the ten pages — install, agent-install, hosting, joining, remote, files, security, architecture, troubleshooting
Written against what actually shipped (README/MANUAL/SPEC 'what shipped' notes and
the real flags in the source). Everything specced but not built — LAN discovery,
sounds, nudges, upload auto-allow, guest scrollback, peer tasks, Windows — is listed
on Home under 'Not built yet' and described nowhere else.
Agent-Install is the page another person's agent is pointed at: the never-do list
first, then numbered non-interactive commands with a verification after each, the
steps that need a human called out, and a self-test whose expected output was
recorded from a real run on 2026-08-29.