Repository navigation
v0.4.0
Two threads. Devour no longer requires Obsidian — a clip can land as a plain markdown file
in the download folder, chosen on the options page or during onboarding. And the browser's
bridge connection now outlives the agent session that opened it, so starting a session is a
local attach rather than a fresh hunt for a socket. A third arrived late, from the audit
before Gullet's first npm publish: the bridge handshake had a relayable proof, and closing
it moves the wire protocol.
Breaking
-
The bridge wire protocol is now 3, and the token proof is bound to the channel it is
presented on. Update the extension and Gullet together; there is deliberately no
downgrade path. Protocol 2 hashed only the token and a nonce, in one function used in
both directions, so the proof was a credential anyone could borrow: every honest client
proves first to whatever answers the marker probe, so a local process knowing no token
could bind a free candidate port, wait to be dialled, forward the real hub's challenge as
its own, and replay the answer to that hub as a peer. That yielded the full tool surface —
list, read, clip, close every tab — plus enough to retire a detached hub and hold its
port permanently. Proven against the live hub and re-run against the fix;
SECURITY-REVIEW.mdcarries the exploit and the before/after. The proof now includes the
role it is claimed under and the loopback port it is presented at, so a proof handed to a
squatter is worthless at the real hub. No fallback and no negotiated downgrade: an
attacker who can imitate the marker can also claim to be old.Upgrading needs one manual step, once. A background hub started by the previous
version outlives the session that spawned it and keeps its port, and a protocol bump
cannot ask it to stand aside — retiring requires proving the token, and the proof is
itself protocol-shaped. If the bridge does not come back after updating, stop the old
hub:pkill -f "gullet.*--detached-hub". Tabglutton's settings now say "Sidecar
version mismatch — update both halves" instead of sitting on "Waiting for a sidecar",
and Gullet names the port and this command in its own error.
Features
- Devour can write plain markdown files instead of filing into Obsidian
(#36). Same frontmatter, the same
Clippings/<site subfolder>/layout and the same note names, written to the browser's
download folder.downloadsis an optional permission, requested when you pick the
destination rather than at install. It is a chosen mode and never an automatic fallback:
the extension provably cannot tell a refusedobsidian://handoff from a successful one,
so anything shaped like "we noticed that failed, saving a file instead" would be a guess. - Onboarding asks where clips land, at step 2
(#42). Choosing markdown files hides
the vault field and drops theobsidian://approval step out of the walkthrough entirely
— a walkthrough that files to disk never needed it. Step 2 previously refused to advance
without a vault name, putting the install gate #36 exists to remove straight back on first
run. - The bridge's
tab_clipobeys the same destination setting as the popup's Devour
(#41), with one stated exception: a
vaultoverride still names Obsidian for that call, because it is a destination the
caller asked for outright. Before this the bridge read the vault directly and an agent
clip failed with vault-missing for anyone in file mode. - Duplicates are shown, not just counted
(#39). The popup and the cockpit both
open with a Duplicates section: one group per canonical URL, the copy Dedup keeps marked
keep, and the cost of closing the rest. Select extras puts those copies into the
normal selection, so duplicates can be devoured rather than only closed. - The browser connection outlives the agent session
(#40,
#29). The first Gullet to find no hub
spawns a detached one and attaches to it as a peer, so session start is a local attach and
the socket is won once per browser session rather than once per agent session. The hub
exits by itself after six idle hours, stands aside for a newer Gullet, and takes a
SIGTERM;--no-detachkeeps the old session-scoped shape. The extension arms its
keepalive only while the hub reports at least one attached session, so a hub with nobody
waiting no longer pins the event page.
Changed
tab_clip's result is a discriminated union, and two of its fields are no longer
unconditional.destinationis"obsidian"or"file";vaultandcontentHash
exist only on the Obsidian variant, andfileis absent exactly when nothing could
confirm the write.confirmedBy—"browser","gullet"or"nobody"— names who
proved the clip landed, replacing a boolean whosefalsemeant "could not check" and
read as "did not land". This shape needed no protocol bump of its own — Gullet is its
only consumer and ships alongside the extension — though the handshake fix above moves
the protocol anyway.- A bridge clip's destination follows the user's setting rather than always being the
vault. No existing install can notice the difference: file mode ships in this same
release, so there is no state in which the old and new behaviour disagree. - Revoking the
downloadspermission is now handled instead of merely failing
(#43). The grant is optional and can
be taken back from the browser's own add-on settings, and it is checked once per Devour
run before any tab is woken — with it gone, a run over 50 discarded tabs used to reload
all 50 and then fail every one of them. The options page now re-checks it on load and, if
it is gone, moves the destination back to Obsidian and says so. That revert is the fix,
not the wording: re-selecting an already-checked radio fires no event, so the file
destination previously had no way left to ask for the permission again. Onboarding
already did this; the popup, the background page and the bridge now name a remedy the
user can actually perform. - A clippings folder is sanitized the same way a note name is in file mode. A base
folder containing: " < > ?or a control character reached the downloads API unfiltered,
where Chrome silently rewrote it and Firefox on Windows could refuse the call outright. - Gullet reads
./.envlast, below the global token file, instead of above it. It is
the only token source read out of a directory the user did not choose — an MCP host sets
the sidecar's working directory to whatever project the agent session started in — so a
cloned repository shipping a.envcould silently replace the token the options page
wrote, putting the session in a realm its author also knows. It can still supply a token
when no global one exists, and it never answers for a global file that merely failed to
read, so a locked secret manager still surfaces its own error and still heals on retry. - A hub's log can no longer be flooded by anything that can open a socket. The refused-
upgrade line carried the caller's rawOrigin, which is unauthenticated input: one
loopback client sustained ~480 MB/s intohub.log, a file the README calls small by
construction. It is now one line a minute with a count of what it suppressed, and the
origin is truncated.