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

99 lines
4.7 KiB
Markdown

# 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](https://github.com/endojs/endo-but-for-bots/tree/llm/packages/chat).
Written 2026-10-08.*
## webkeys in markdown: works, but discloses
The cheapest option is a [webkey](webkeys.md) 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.