summaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_DESIGN.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-13 15:40:34 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-13 15:40:34 +0200
commitd917bb61e42336c38782b22da604d7ca923d484a (patch)
tree3ad99072f02d621ca4a54f4fcce65a2c530c4b79 /docs/MESHBAY_DESIGN.md
parentc5fff4ce8366b08669c0c8b6d30b99b94b9fefca (diff)
downloadmeshbay-d917bb61e42336c38782b22da604d7ca923d484a.tar.gz
fix(node): an uploaded file records who sent it
`_register_uploader` walked the index for the entry it had just written, at a moment when no such entry can exist: the file was a `.part` until the rename on the line above, which is not indexable, and the watchdog that will index it debounces for two seconds and then hashes. The walk matched nothing, silently, so every uploaded file in every group was owned by nobody — and `file_delete` refuses a caller with no admin authority when the entry records no uploader, so a member could not delete what they had just sent. MESHBAY_DESIGN.md §5.4 grants that to any non-revoked device of the uploading account. The record is now written when the last chunk lands (`indexer.record_upload`) and the entry is stamped from it in `_hash_or_cached`, the one funnel every entry passes through — initial scan, watchdog, reconcile and replug alike. It lives in the index cache rather than on the entry alone, because the index is rebuilt from disk at every start and an owner the node forgets on restart is a right quietly taken away. It is validated against a live `stat()`, so whatever later occupies that path inherits nothing; and `_rescan_root`'s carry-over no longer copies over it, or memory would beat the durable record. §5.4 also claimed ownership was *provable* — a transcript the uploader signs, stored with the entry. No such signature has ever existed; `meshbay:upload:v1` in the code is the groupbox purpose that seals the envelope. The section now states what the code does, and the transcript is an open item in §15.3. `test_upload_attribution.py` drives the real handler and a real indexer across that seam. Against the previous source its two positive cases fail on the property, not on a missing method — an upload, then a rebuild from disk, then a different file at the same path inheriting nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UMxEQadpzPkYLFf5CYKhpW
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
-rw-r--r--docs/MESHBAY_DESIGN.md26
1 files changed, 20 insertions, 6 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index 14ec5d3..07e208b 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -934,12 +934,25 @@ authorisation key and becomes the **audit record** of which device acted. This
remains **roster-rooted, not token-rooted**: a hub minting a token that claims to
be someone holds no key the node pinned for them, so the signature fails.
-**Ownership is provable, not merely recorded.** The uploader signs
-`meshbay:upload:v1` over node, group, root, path, content hash, account and
-timestamp, and the node stores it with the index entry. Ownership is then
-verifiable by any member rather than asserted by the node, and the path where
-overwriting a file made the attacker its recorded uploader (**C5a**) is closed a
-second time.
+**Ownership is recorded by the node, from the authenticated session, and it is
+durable.** The record is written when the last chunk lands and the file reaches
+its final name; the entry is stamped from it when the indexer creates the entry,
+which is a **later** moment — the entry does not exist while the upload is still
+arriving, so an attribution written against the index at the end of the upload
+matches nothing, silently, and leaves every uploaded file owned by nobody. It is
+stored beside the hash cache rather than on the entry alone, because the index is
+rebuilt from disk at every start and an owner the node forgets on restart is a
+right quietly taken away. It is validated against a live `stat()`, so whatever
+later occupies that path inherits nothing.
+
+**It is asserted by the node, not proved by the uploader.** A member verifies
+nothing here: they are told who uploaded a file, by the node that served them the
+index — the same trust they already extend to every other field in it. The
+overwrite path where an attacker could become a file's recorded uploader
+(**C5a**) is closed by the no-overwrite rule (§6.4), not by a signature. An
+upload transcript the uploader signs — over node, group, root, path, content
+hash, account and timestamp, stored with the entry — would make it verifiable by
+any member instead. That is an open item (§15.3), not something the product has.
**One implementation, several front doors.** `meshbay_node/ops.py` holds every
operation. The loopback API, the CLI and the signed MNP handlers all call these
@@ -2684,6 +2697,7 @@ account recovery, and the Windows port through packaging.
| **T3** for browser users | **Accepted permanently.** Removed for native clients, and that removal's value depends on reproducible builds |
| **Hub identity pinning** (O13) | Nothing pins the hub's key. Bounded, because a substituted hub can neither read content nor ship code to a native client |
| **Aggregate upload quota** | Per-file caps exist; a per-user or per-group total does not |
+| **A signed upload transcript** | Ownership is recorded by the node and verifiable by nobody else (§5.4). Making it provable is a transcript the uploader signs, stored with the entry — designed in outline, not built |
| Forward secrecy in group chat | **Given up deliberately and on the record** (§4.5). If it becomes a requirement it belongs in 1:1 DM |
| Metadata at the hub | Membership, and who posted in which group and when. A known leak, not a solved problem (§7.1) |
| The exact-hash content check | Structural, not functional (§7.5) |