Repository navigation
The approval-timeout fix and a hardening pass. After upgrading, run
holdcall daemon restart: a 0.1.2 relay and a 0.1.3 daemon refuse each other.
Security
- The console answers only to whoever was handed its address.
holdcall consolemakes a 256-bit token when it starts and prints it in the
address it asks you to open (http://127.0.0.1:7717/#token=...; the token is
in the fragment, which a browser keeps to itself). Every request but the page
itself must carry it. Until now any process that could open a connection to
the port could read the journal and, from/api/pending, the real arguments
of the calls held for a human. Upgrading: a bookmarked plain address opens a
page that says it has no token and shows nothing; the token lasts until that
console stops, so a console started again prints a new one. - A peer is checked for the user it runs as, not only for the file it runs.
The daemon and the relay compared the other side's executable and nothing
else, so another user running the same binary passed the check. Both now
compare the effective uid as well, on Linux and macOS. Not on Windows, which
has no peer credentials to ask (see below). - Windows no longer releases credentials or connectors. The daemon served
a connection the platform could not verify, which is every connection on
Windows, like a verified one, so a process of the same user able to open the
socket could ask for a connector's secret and receive it. A secret now goes
only to a peer that was verified, and the relay takes no connector or
credential from a daemon it could not confirm. On Windows, which stays
experimental, that means connectors do not work andholdcall connector set
refuses to send a secret; decisions, rules and the journal are as before. - A socket or data directory another user owns is refused. The daemon
bound its socket wherever the path said, so a directory another user owns --
/tmpitself, say -- let that user put or replace the socket. The home, the
data directory and the socket's directory must now belong to the user running
Holdcall, or the daemon does not start and says which directory and which
setting to change. Directories Holdcall creates are0700. One that was
already there, is yours and is open to group or other is not changed
(asocket = ~/holdcall.sockwould otherwise take away your home's access
from everyone else): it is warned about in the daemon's log, and
chmod 700closes it. Linux and macOS; nothing is checked on Windows.
Changed
- Linux: the default socket moves when
XDG_RUNTIME_DIRis not set. It
used to go directly in/tmp, where any local user can pre-create a path.
It goes in/tmp/holdcall-<uid>/now (the temp directory itself is still
used where it is already private to you, as macOS's$TMPDIRis, and
$XDG_RUNTIME_DIRwhen set is as before). A daemon an earlier version
started on the old path is not found by the new one and keeps running
orphaned: runholdcall daemon restartafter upgrading, or stop it by hand.
Fixed
-
Opening the journal waits for a lock instead of failing at once. The
journal switched the database to WAL before armingbusy_timeout, so a lock
another connection held at that moment made the open fail with "database is
locked" instead of waiting the five seconds it is owed. A daemon starting
while another handle was mid-write failed intermittently. -
A held call nobody decides no longer ends the session, and is no longer
reported as a human's rejection. Withapproval_timeoutrunning out, the
relay and the daemon's own timer raced, and either result was wrong. When
the relay's clock won, it hung up on the daemon for good and every later
call in that session was denied with "could not reach a decision". When the
daemon's answer won, the model was told "A human reviewing this call's real
arguments rejected it" about a call nobody had looked at. The relay now
waitsapproval_timeoutplus a five-second grace, the daemon answers a
timeout with a value of its own (timed_out), and the model is told
"nobody decided within the approval timeout". The next call in the session
is decided as usual. The journal is unchanged: the call is still recorded
asrejected, so existing journals verify as before, and the daemon's log
now says, for each one, that nobody decided.
Upgrading: where the daemon and the relay check each other (macOS, Linux)
a daemon and a relay from different builds already refuse each other, and
holdcall daemon restartis the remedy. Where they do not (Windows), a
relay from an earlier version does not knowtimed_out, denies the call
and drops its connection to the daemon, so the rest of that session is
denied. -
A daemon that goes away while it holds a call is no longer reported as an
approval timeout. The relay counted any failed read as the wait running
out, so a daemon killed a second into a one-minute hold was reported as
"nobody decided" at that moment. The client is told "could not reach a
decision", and the relay's stderr says the daemon stopped answering. -
The relay's two-line notices say
holdcall:on both lines. The second
line of five of them still begannim:, the product's name before the
rename. -
A configuration the daemon refuses is reported for what it does. A
socket path past the limit made the relay print "relaying anyway; calls
will not be recorded". It relays nothing unrecorded: the daemon it then
tries to start runs the same check and exits, so no daemon is ever
reachable and everytools/callis denied, while every other message is
still relayed. The notice now says that. -
The console says what a client is told when nobody decides a held call.
The note under "Held for a human" still said the client is told a human
said no.
Changed
- Head-of-line blocking is documented as a known limitation. While a call
is held for a human, every other message in the same session waits behind
it, up toapproval_timeout; other connectors' sessions are not affected.
Not fixed here: it needs decisions to be asynchronous, which is a redesign
of the relay. Seedocs/decisions/0005-human-approval.md.