Files
captoys/docs/discord-references.md
dckc b4ab820486 capsite/docs: references to DOs in Discord messages
How a Discord message could carry references to our DOs, after
@endo/chat: names in the text, references beside it in a mailbox DO,
an Adopt button answered ephemerally, and per-user petstores. Webkeys
in markdown work but disclose the capability to every channel reader
and to Discord itself; ephemeral replies still pass through Discord,
so references should never cross it at all.

Generated-by: Claude Opus 5.5
2026-10-08 16:19:45 -05:00

4.7 KiB

references to DOs in Discord messages

In Smalltalk, JavaScript, and Unix sockets, a message can carry references the receiver can then send messages on. Discord messages can refer to messages, threads, channels, and servers, but not to our Durable Objects. This note works out how they could, after @endo/chat. Written 2026-10-08.

webkeys in markdown: works, but discloses

The cheapest option is a webkey as a markdown link, [cookie store](https://webapp.example/store/#<key>). It gets the basics right: following it in a browser works, and the key sits in the fragment, so Discord's link-preview fetcher never sends it anywhere.

But the message body itself is the leak. Every reader of the channel now holds the capability, and so does Discord: its servers, its logs, its search index, its moderation tooling, and whoever reads those. That is fine for capabilities meant to be public (a public cache page) and wrong for anything like webapp's store id, where holding it means choosing whose session the cache uses.

what @endo/chat does instead

In @endo/chat a message is not a string with secrets in it. Its parser (message-parse.js) splits Here is @my-file for you into

  • strings: the text between references,
  • edgeNames: the names the sender put in the text (my-file),
  • petNames: the sender's own names for those references,

and the daemon delivers the references themselves beside the text, over CapTP. The receiver sees the edge name and adopts the reference under a petname of their own. Three things follow:

  • References never appear in text. Only names do, and names are safe to show anyone.
  • Names are per-person. My cookie-store and your alice's store can be the same object, and neither name has authority by itself.
  • Adopting is an act. Receiving a message gives you the option to hold a reference, not the reference itself, until you choose a name for it.

the endo-shaped version for Discord

A capsite bot (a Worker serving Discord's HTTP interactions endpoint) plays the daemon's part:

  • Petstores. Each Discord user has a petstore DO: petname → DO reference. Minting files results there directly: webapp's mint could file the store id under diigo cookie store in Alice's petstore instead of handing it to her password manager.
  • Sending. A slash command, e.g. /send to:@bob text:"here is @cookie-store". The bot parses the text like parseMessage, looks up each @name in the sender's petstore, posts the text (names only) to the channel, and files { discord message id → recipients, edgeName → reference } in a mailbox DO.
  • Adopting. The posted message carries an Adopt button. On a click, Discord tells the bot who clicked (in a signed interaction); if they are a recipient, the bot answers with an ephemeral message, shown only to them, asking for a petname, then files the reference in their petstore.
  • Using. References are exercised through the bot by petname (/call cookie-store set cookie …, or a link to a page the bot serves). The reference stays inside our Workers.

what this does and doesn't keep from Discord

Ephemeral replies hide content from other channel readers, but not from Discord: they still pass through its servers. So the design above never sends a reference through Discord at all, ephemeral or not; only petnames, edge names, and message ids cross it. Handing over a webkey in an ephemeral reply would be a weaker variant: hidden from the channel, still disclosed to the infrastructure.

What Discord does hold is identity: the bot believes Discord about who clicked Adopt. A compromised Discord account, or Discord itself, could therefore adopt references meant for that user. That is the same trust users already put in Discord for their messages, but it should be stated where references matter more than chat does.

open questions

  • Durable references across RPC. A petstore entry is a DO id plus enough to say what kind of thing it is (webapp instance, header store, cache). Getting from that back to a live RpcTarget is the marshalling-layer question from the cookie-rotation work (docs/cookie-rotation-wiring.html on the webapp-cookie-rotation branch).
  • Petstore ids. idFromName(discordUserId) is convenient but ambient: any worker with the namespace binding can recompute it. Acceptable if only the bot has the binding; otherwise mint with newUniqueId and keep the mapping in one place.
  • Leaving Discord. The same mailbox and petstore could back a web page or @endo/chat itself; Discord would then be one front end of several.