Open
Conversation
Ninth in the Phase 2 rollout; first non-bundle PR in the series. Stacked on F.8 (PeerTube). Ships the core plumbing for tying per-app fediverse handles (Mastodon @user@host, Funkwhale channels, Matrix MXIDs, etc.) to Crow's root Ed25519 identity with publicly-verifiable signatures. **This is attestation, not key replacement.** Each federated app still uses its own keys for federation; the Crow root key signs only the binding (crow_id, app, external_handle, version). New files: - servers/shared/identity-attestation.js — Core Ed25519 sign/verify helpers. canonicalPayload() produces sorted-key JSON for deterministic signing. verifyCrowIdBinding() catches swap attacks (crow_id is derived from the root pubkey; a valid sig with the wrong pubkey doesn't bind to the claimed crow_id). signRevocation/verifyRevocation for the revocation list. SUPPORTED_APPS constant caps the attestation surface to the 8 federated bundles shipped in F.1-F.8 (prevents typos like "matodon"). - skills/crow-identity.md — Full workflow skill: when to attest, when NOT to attest (ephemeral identities, pseudonymous accounts), endpoint surface, payload format, key-rotation flow, safety notes around permanent publication. Modified files: - scripts/init-db.js — Three new tables: - moderation_actions (bundle_id, action_type, payload_json, requested_at, expires_at, status, idempotency_key UNIQUE). Finally creates the table the federated bundles have been writing to since F.1; before F.11 the queueModerationAction() helper returned "queued_unavailable" gracefully when the table was absent. - identity_attestations (crow_id, app, external_handle, app_pubkey?, sig, version, created_at, revoked_at). UNIQUE(crow_id, app, external_handle, version) — each rotation adds a new version row rather than overwriting. Partial index for active (non-revoked) lookups. - identity_attestation_revocations (attestation_id FK CASCADE, revoked_at, reason, sig). Signed revocations stored separately so the revocation list itself is cryptographically verifiable. - contacts extended with external_handle + external_source columns (idempotent via addColumnIfMissing) so federated contacts can be linked into the existing contacts table. - servers/sharing/server.js — Four new MCP tools: - crow_identity_attest(app, external_handle, app_pubkey?, confirm) — creates row, signs with root key, returns attestation_id + sig + publish_url. Confirms permanence explicitly. - crow_identity_verify(crow_id, app, external_handle, max_age?) — fetches latest non-revoked row, verifies sig AND verifies the crow_id→pubkey binding. Currently only verifies attestations belonging to THIS instance; cross-instance verification uses the gateway's /.well-known endpoint instead (rate-limited there). - crow_identity_revoke(attestation_id, reason?, confirm) — signs a revocation, updates the attestation row's revoked_at, publishes via the revocation list. Refuses to revoke attestations that belong to a different crow_id. - crow_identity_list(include_revoked?, app?, limit?) — surfaces what this instance has attested. - servers/gateway/index.js — Two new public endpoints: - GET /.well-known/crow-identity.json — paginated active attestations (256 per page, cursor-based). Returns root_pubkey + active_attestations array. Cache-Control: 60s. - GET /.well-known/crow-identity-revocations.json — paginated revocations with signed proofs. Same pagination. - Shared identityLimiter (60 req/min/IP) on both endpoints to prevent verification-storm DoS against the attestation surface. - skills/superpowers.md — Trigger row added (EN+ES): attest identity, prove I am, link my mastodon, verify handle, keyoxide, revoke attestation. - CLAUDE.md — DB schema docs for moderation_actions + identity_attestations + identity_attestation_revocations. Skills Reference entry for crow-identity.md. Security design points: - No gossip over crow-sharing. Attestations publish only via .well-known for now (per plan Q3). Revisit after F.12 lands. - Pinned-post attestation (Keyoxide pattern) remains manual — automation would open forgery vectors (plan Critical #3 answer). - Publication is effectively permanent. Revocations themselves are public. Consent text in the tool descriptions warns about this explicitly. - Rate-limited at the HTTP surface to 60 req/min/IP — the verify endpoint can be turned into a DoS vector against small hosts otherwise. - Pagination capped at 256 entries/page per plan §Publication. Verified: - node --check on all modified + new files - node scripts/init-db.js runs cleanly; the three new tables + two new contacts columns land without conflicts on existing DBs - createSharingServer() boots with the four new tools registered - Core sign/verify/tamper-rejection/binding-check round-trip exercised via ad-hoc script - (Live gateway .well-known HTTP test deferred — the bundled gateway is hostile to the sandboxed test environment on this machine) Next: - F.12 cross-app bridging (now the final piece) — matrix-bridges meta-bundle + crow-native crosspost with 60s-delay consent UX. F.12 can now assume the moderation_actions + identity_attestations tables exist and the .well-known publication surface is live.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First non-bundle PR in the Phase 2 rollout. Stacked on F.8 (PeerTube). Ships the core plumbing for tying per-app federated handles to Crow's root Ed25519 identity with publicly-verifiable signatures. Attestation, not key replacement — each federated app still uses its own keys; the Crow root key signs only the binding
(crow_id, app, external_handle, version).Also finally creates the
moderation_actionstable that federated bundles have been writing to since F.1 (they gracefully returnedqueued_unavailablewhen absent).What lands
Core sign/verify —
servers/shared/identity-attestation.js:canonicalPayload()— deterministic JSON for signing (sorted keys, nullish omitted)signAttestation()/verifyAttestation()— Ed25519 over the canonical JSONverifyCrowIdBinding()— catches swap attacks (crow_id = first-8-bytes-of-sha256(ed25519_pub) → base36; valid signature with wrong pubkey doesn't bind to claimed crow_id)SUPPORTED_APPSconstant — the 8 federated bundles from F.1-F.8, prevents typosDB schema —
scripts/init-db.js:moderation_actions(bundle_id, action_type, payload_json, idempotency_key UNIQUE) — finally existsidentity_attestations(crow_id, app, external_handle, app_pubkey?, sig, version, revoked_at) — UNIQUE(crow_id, app, external_handle, version) so rotation = new row, not overwriteidentity_attestation_revocations(attestation_id FK CASCADE, revoked_at, reason, sig) — signedcontactsextended withexternal_handle+external_sourceMCP tools — added to
servers/sharing/server.js:crow_identity_attest(app, external_handle, app_pubkey?, confirm)crow_identity_verify(crow_id, app, external_handle, max_age?)crow_identity_revoke(attestation_id, reason?, confirm)crow_identity_list(include_revoked?, app?, limit?)Gateway endpoints — paginated, rate-limited:
GET /.well-known/crow-identity.json(256 per page,?cursor=N)GET /.well-known/crow-identity-revocations.jsonidentityLimiter(60 req/min/IP) — the verify endpoint becomes a DoS vector otherwise.Docs — new
skills/crow-identity.mdskill, trigger row inskills/superpowers.md, DB schema + Skills Reference entries inCLAUDE.md.Design notes
.well-knownpublication only; revisit after F.12.verifyCrowIdBinding()catches the case where a signature validates against a pubkey that doesn't derive to the claimed crow_id.crow_identity_revoke+crow_identity_attestwith incremented version. Version counter auto-increments incrow_identity_attest.Test plan
node --checkon all filesnode scripts/init-db.jsruns cleanly, creates all 3 tables + adds both columns on existing DBscreateSharingServer()boots with the 4 new toolsnpm run checkpasses/.well-known/crow-identity.json(deferred — bundled gateway is hostile to sandboxed test environment; manual verification at deploy time).well-knownand verifies) — integration test for a follow-upcrow_identity_listtool surfaces this via the AI until a dedicated panel landsRollout position
moderation_actions+identity_attestationstables exist and the.well-knownpublication surface is live.🤖 Generated with Claude Code