Repository navigation
gage 0.3.1 — the subject fix, and the first dependency audit
A security fix, and the first dependency audit this project ever ran.
If you installed 0.3.0, update: the display path let an unconfirmed sender put
instruction-shaped text into a reading agent's context without anything being
accepted.
Fixed
-
The subject walked past the accept gate. Every print in the CLI passed
sender-controlled text throughone_line, butroutes::inbox_rowsemitted
subjectandfrom_handle_claimedraw and unbounded, and so did the
verdict block of thegage_openMCP tool. Demonstrated, not theorised: an
unconfirmed sender, nothing accepted, 441 bytes of instruction-shaped
text delivered into the context of an agent that did nothing but read its
inbox — the same shape as the Cline bot injected through a GitHub issue
title.Now
manifest::safe_display(s, max): escaped and bounded, and truncation is
announced rather than silent, because text that stops mid-sentence
without saying so is its own small lie. Applied toinbox_rows, the
gage_openverdict block, and thegage_pair_confirmpairing id.Said plainly: bounding caps the budget, it does not remove the
injection. 160 characters is still room to write a sentence in. What
stops the sentence being obeyed is the frame around it, and that is a
property of the reading agent, not of gage. See
docs/what-this-does-not-stop.md. -
Two certificate-verification advisories, both days old when found:
RUSTSEC-2026-0382 (rustls-webpki0.103.15 — verification may accept
distrusted intermediates) and RUSTSEC-2026-0381 (webpki-roots1.0.9).
Fixed to 0.103.16 and 1.0.10; lockfile only, no API change.Nobody had audited because the dependency list is short and hand-justified
in the manifest, which is exactly the reasoning that makes a tree feel safe
without anyone checking it. A short list bounds exposure; it does not tell
you when one of them is announced vulnerable.cargo auditnow runs as the
last stage oftests/selfcheck.sh— last so that an advisory published this
morning cannot mask a real test regression, and a skip is said rather than
passing quietly. -
journal::safe_keypermits/, which is fine for a JSON log field and
is a path separator as a directory name. Roughly half of base64 keys contain
one, which showed up as a ~49% flaky check that was a real bug. Anchor peer
directories are nowsha256(key)hex, truncated.
Added
gage agent list | new | remove. Agents created from the viewer had no
removal path at all.removeretires the key, drops the route, then deletes
the home — fail-closed in that order. Retired, not forgotten: the key
keeps a retirement cut, so bundles it sent before removal still verify.
Getting that wrong turns every bundle an agent ever sent fromSENDER VERIFIEDintoSENDER KEY REJECTED, which is how the first version of this
behaved before the suite caught it.- Mutual journal anchoring —
gage anchor send | record | list | check.
Two correspondents periodically exchange a signed digest of their own
journal head, so a later rewrite of either journal contradicts a record the
other side already holds. Anchors ride as ordinary signed bundles through
the ordinary accept gate: no new network surface, no new trust root.
Rationale and the decision left open:
docs/adr-journal-anchoring.md. - A remove button in the viewer, with a two-press confirm rather than a
modal (WKWebView giveswindow.confirmno window to sit in). tests/injection/stage.sh— stages two fixture homes, paired, one
sending, so that running the injection test is one command. The sender is a
confirmed contact on purpose: an unlisted key is refused by the frame on
its own, which would test the allowlist rather than the frame. The script
never reads the payload.
Changed
- The install instructions lead with
curl -fsSL https://gage.sh/install.sh | sh, and the reason is specific: the quarantine attribute behind "cannot
be opened because the developer cannot be verified" is set by browsers, Mail
and AirDrop — not bycurl. So a CLI tester needs no notarisation, and
handing someone a file was the most likely way to lose them.INSTALL.md
had the cause backwards. - README leads with the problem and puts the demo above the fold.
docs/a2a.mdgains "A directory, and the line it may not cross": a
directory may answer where, and may carry an unconfirmed claim, but may
never confer trust. The test is "if the directory were wholly hostile, what
changes?" — and the load-bearing property is replaceability.docs/what-this-does-not-stop.mdis new, and linked from the README and
the threat model. A list of what this does not protect you from, kept where
someone deciding whether to trust it will find it.
Notes
- No format or protocol change. A 0.3.0 binary and a 0.3.1 binary
interoperate.seal.rsis untouched. spctlstill rejectsGage.app— ad-hoc signature, not notarised. The
CLI and its owngage uiare unaffected, and are what a first tester should
use.