Skip to content

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 23:46
· 46 commits to main since this release
d3c610e

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.md carries 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. downloads is 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 refused obsidian:// 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 the obsidian:// 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_clip obeys the same destination setting as the popup's Devour
    (#41), with one stated exception: a
    vault override 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-detach keeps 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.
    destination is "obsidian" or "file"; vault and contentHash
    exist only on the Obsidian variant, and file is absent exactly when nothing could
    confirm the write. confirmedBy — "browser", "gullet" or "nobody" — names who
    proved the clip landed, replacing a boolean whose false meant "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 downloads permission 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 ./.env last, 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 .env could 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 raw Origin, which is unauthenticated input: one
    loopback client sustained ~480 MB/s into hub.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.