OverviewFilesCommitsIssuesDocsReleasesPeers

favela-identity commits / aeb0f13

spec/identity 6.1.1-6.1.2: signing custody, and signing as a service
atproto signs CONTENT so a membrane can relay it keylessly; AP signs the TRANSPORT so something must sign per request. That asymmetry decides both: a thin signer for atproto-bridge is pointless, ap-bridge must hold a key, and RemoteSigner only pays when the bridge is NOT co-located. The DID key is copied to every co-owner device today - N holders, none revocable, the v1-DeviceLinker defect one layer up. Fix: one bridge:bsky per identity holds it; devices write to the room and the bridge signs. Signing becomes a service rather than a device capability, which deletes ~252 lines of signer election. Cost: a stalled projection when the bridge is offline, which is a peripheral degrading. Residual, unsolved: the PLC rotation key must survive the bridge box (#49). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
aeb0f13f917eefa35895d73128398f3c489e0479
by plankatron <fleeky@gmail.com> · 2026-09-06T00:03:43.000Z

Changed files (1) +66 -0

spec/identity.md +66 -0
……@@ -148,6 +148,72 @@ A bridge holds its own credentials — ap-bridge carries an RSA keypair for HTTP
148148atproto path uses the shared `#atproto` signing key. Those are the **bridge's** secrets, not a
149149person's. So a bridge is a resource with a credential set, never a row in `identities[]`.
150150
151### 6.1.1 Signing custody — the asymmetry, and what it decides
152
153**atproto's signature is over CONTENT; ActivityPub's is over the TRANSPORT.** That one difference
154decides every custody question below, and `ap-bridge/lib/signer.js`'s header states it:
155
156> *"atproto-bridge achieves [keylessness] because a peer's signature travels in its firehose, so the
157> membrane re-serves a signature it never produced. ActivityPub has no equivalent — HTTP Signatures
158> are computed per-request over live request headers, so something must sign at delivery time."*
159
160⇒ **A thin signer for `atproto-bridge` makes no sense** — it is already keyless *for free* (verified:
161`RepoManager`, `signCommit`, `privateKey` appear only under `pds/test/`). ⇒ **ap-bridge must hold a
162key to deliver at all**, today as `LocalSigner`.
163
164**Is ap-bridge a security issue? Yes, bounded — and the code says so:** *"a compromised bridge yields
165the actor's identity; a RemoteSigner means it yields nothing but the ability to ask for signatures
166while the connection lasts."*
167
168| a stolen AP RSA key **can** | **cannot** |
169|---|---|
170| deliver forged activities to any fediverse inbox — post, delete, follow **as** the actor | touch canonical records (they are in rooms) |
171| | forge atproto records (different key) |
172| | write to any room (not an admitted writer) |
173
174★ **This is precisely what §4's demotion buys.** Before it, compromising the peer yielded *the
175identity*; now it yields *one network's delivery capability* — a **grant to revoke and a key to
176rotate**, not an identity to mourn.
177
178⚠ **`RemoteSigner` only pays when the trust boundary is real.** The seam exists and is unwired
179(hyper-social #29 M2; the header says swapping is "one line of wiring"), and it costs **one RPC per
180delivery** — unbatchable, because the signature covers `host` and `(request-target)`, so a fan-out to
181N inboxes is N signatures. A RemoteSigner from a bridge to a peer **on the same box buys nothing**:
182compromising that box gets you the peer anyway. So it is a *deployment* decision — co-located →
183`LocalSigner`; shared or remote hardware → `RemoteSigner`.
184
185### 6.1.2 ★ Signing is a SERVICE, not a device capability
186
187The DID `#atproto` key is currently **copied to every co-owner device**. That is the v1-DeviceLinker
188defect one layer up: **N holders, and you cannot revoke one.**
189
190The fix falls out of §6.3 rather than being new. The DID key belongs to `bridge:bsky`, so run **one
191instance per identity**, co-located with the always-on peer. Devices write records to the room as
192attested writers; the bridge materialises and signs.
193
194| requirement | met by |
195|---|---|
196| the key stays private | **one** holder, not N |
197| multi-device | writing and signing are **different capabilities** — a new device gets a writer attestation, never the DID key |
198| keys out of the membrane | already true; unchanged |
199
200★ **It deletes the signer election.** `atproto-room`'s `signer-presence.js` (105),
201`signer-presence-channel.js` (57) and `lease.js` (90) — **~252 lines** — exist solely to arbitrate
202among N devices that *could* sign. With one signer there is nothing to elect.
203
204**The cost, and it is correct rather than merely tolerable:** if the bridge is offline the bsky
205projection stalls, while the canonical record still replicates to every device. **A peripheral being
206down degrades the peripheral** — which is the whole claim of `SOCIAL-MODEL.md`.
207
208⛔ **The residual, and it is not solved: the PLC rotation key.** If the bridge box dies the DID
209signing key dies with it, PLC is append-only so the DID survives *frozen*, and recovery is **only**
210via the rotation key — a different secret with different backup requirements. That is hyper-social
211#48's scar (rotation authority recovered from a backup) and #49. **Nothing here addresses it.**
212
213⚠ **Second, smaller:** a co-owned community room has N co-owners but one `bridge:bsky`, so *who runs
214the community's bridge* becomes a governance question. Coherent — ap-bridge is already single-instance
215and `SOCIAL-MODEL.md` §6.7 makes governance a room role — but it should be decided, not defaulted.
216
151217### 6.2 `reach` resolves through the bridge registry
152218
153219`reach` in a record names a **network** (`design.md` §5.1). The bridge registry resolves