upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-20 12:21:24 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-20 21:00:50 +0000
commit365dfb9a1e986b68bc2389e2a3cd3da30b0d4636 (patch)
tree903a296510d2be62d48634148f237273cf4cc5b5
parent1583a5f5b137ec408d9358c12f6716f7033a2c69 (diff)
integrate co-maintainer announcement rationale into architecture docs
adds scam-prevention rationale, announcement model trade-offs, and push/fetch asymmetry to docs/architecture/maintainer-model.md
-rw-r--r--docs/architecture/maintainer-model.md90
1 files changed, 90 insertions, 0 deletions
diff --git a/docs/architecture/maintainer-model.md b/docs/architecture/maintainer-model.md
index 9aebea7..54decb5 100644
--- a/docs/architecture/maintainer-model.md
+++ b/docs/architecture/maintainer-model.md
@@ -82,3 +82,93 @@ When `ngit init` runs, there are 5 possible states based on what exists locally
82| **Not Listed** | Announcement exists, I'm not in maintainer set | Requires `--force` | 82| **Not Listed** | Announcement exists, I'm not in maintainer set | Requires `--force` |
83 83
84See `src/bin/ngit/sub_commands/init.rs` (`InitState` enum) and `tests/ngit_init.rs` for the implementation and test coverage. 84See `src/bin/ngit/sub_commands/init.rs` (`InitState` enum) and `tests/ngit_init.rs` for the implementation and test coverage.
85
86## Why Each Co-Maintainer Must Publish Their Own Announcement
87
88### The Scam Scenario
89
90Nostr git repository state is tracked by Kind:30618 events (the "git state" event).
91These events say: "for the repository with identifier `X`, the current branch tips are...".
92
93Crucially, a state event knows only its identifier (`d` tag) — not which specific
94coordinate chain (which trusted maintainer's pubkey) it belongs to.
95
96This creates an attack vector:
97
981. Alice has a reputation in the Rust ecosystem. She contributes to `my-lib` and
99 is listed in the trusted maintainer's Kind:30617 announcement.
1002. Alice pushes and, in doing so, publishes a Kind:30618 state event for
101 identifier `my-lib`.
1023. A scammer creates a completely different, malicious repository — also with
103 identifier `my-lib` — and publishes their own Kind:30617 listing Alice as a
104 maintainer.
1054. The scammer points to Alice's state event as "proof" that Alice maintains their
106 project. Clients that filter state events by maintainer pubkey would include
107 Alice's state event when fetching the scam repo.
1085. Alice's reputation is attached to a project she has never heard of.
109
110### Why the Announcement Resolves This
111
112A Kind:30617 announcement is a signed statement from Alice that says:
113
114> "I, Alice (pubkey X), am a maintainer of the repository at identifier `my-lib`
115> whose trusted maintainer is pubkey Y."
116
117The coordinate used to discover Alice's announcement is
118`30617:Y:{identifier}` — it is rooted at the trusted maintainer's pubkey, not the
119identifier alone. Alice's own announcement event, signed by Alice's key, is published
120under that same coordinate chain (because `get_repo_ref_from_cache` walks the
121maintainer graph starting from Y's event, and finds Alice's event because Alice
122listed herself as a maintainer of the same identifier).
123
124A scammer's fake `my-lib` has a different trusted maintainer pubkey (Z, not Y). Even
125if Z lists Alice in their maintainers tag, Alice's existing Kind:30617 under the
126`30617:Y:my-lib` coordinate chain does NOT appear under `30617:Z:my-lib`. The scammer
127cannot bootstrap from Alice's existing announcement.
128
129And crucially: Alice's Kind:30618 state events are only fetched when a client is
130looking at a coordinate chain that Alice has explicitly announced. If Alice has never
131published a Kind:30617 for `30617:Z:my-lib`, her state events are not fetched in
132that context.
133
134Without Alice's announcement, her state events carry no coordinate chain membership.
135With her announcement, her state events are trusted only within the chains she has
136explicitly joined.
137
138### The Remaining Vulnerability Without Announcements
139
140If we allowed state events without announcements — i.e., we filtered state events
141by `repo_ref.maintainers` even for maintainers without their own Kind:30617 — the
142attack above works:
143
144- The scammer publishes `30617:Z:my-lib` listing Alice.
145- `get_repo_ref_from_cache` for `Z`'s coordinate now includes Alice in `maintainers`.
146- `get_filter_state_events` includes Alice's pubkey in the author filter.
147- Alice's legitimate state events for the real `my-lib` are fetched and used by the
148 scam repo.
149
150The scammer cannot forge Alice's state events (they're signed), but they can attribute
151real ones to their fake project. A user fetching the scam repo sees a real commit
152history, ostensibly co-maintained by Alice, with no indication anything is wrong.
153
154### Asymmetric Enforcement: Push vs Fetch
155
156ngit enforces the announcement requirement on push only. When fetching, state events
157are accepted from any pubkey in the maintainer set, regardless of whether that pubkey
158has published its own Kind:30617. This encourages good practice while remaining
159resilient when other tools don't follow the same pattern.
160
161- **Push (strict)**: ngit will not publish a state event for a co-maintainer who
162 lacks an announcement. If the user has no announcement, ngit auto-publishes one
163 with defaults before proceeding. This ensures that every state event ngit produces
164 is backed by an explicit, signed opt-in.
165
166- **Fetch (permissive)**: state events from announcement-less maintainers are still
167 accepted. This keeps ngit interoperable with other tools that may not enforce the
168 announcement requirement, and avoids silently dropping legitimate state from
169 maintainers who used a different client.
170
171The scam scenario is therefore partially mitigated rather than fully prevented: a
172scammer can still attribute Alice's state events to a fake coordinate chain if Alice
173has never pushed via ngit. The push-side requirement limits the window of exposure
174to maintainers who have only ever used non-compliant tooling.