aboutsummaryrefslogtreecommitdiffstats
path: root/packages
Commit message (Collapse)AuthorAgeFilesLines
* fix(hub): a username is unique whatever its caseChristophe Besson9 hours2-4/+34
| | | | | | | | Registration refuses a name that differs from an existing one only by case; accounts that already do keep their names, and a pending retry needs the exact name (F-26). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): what an offer or a node message costs the hub is boundedChristophe Besson9 hours3-25/+113
| | | | | | | | | An offer's IP-log row (kept a year) was written before any check, for any string named as a node; it is written once the offer goes to a node. The ICE list is capped (64 candidates, 32 KiB). A node's update_groups, a database read each, is budgeted like chat_notify and claims at most 1000 groups (F-22). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): the audit export never hands a spreadsheet a formulaChristophe Besson9 hours3-5/+48
| | | | | | | A cell starting with = + - @ (or a tab or carriage return) gets a leading apostrophe; the export carries text members chose (F-29). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: remove a spike page served in production and an unused derivationChristophe Besson9 hours4-453/+1
| | | | | | | | static/webrtc-test.html posted a raw password to /login from the hub's own origin; meshbay_common/keyderive.py derived keys from a password and nothing called it (F-32). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): the clear fields beside a chat message are boundedChristophe Besson9 hours2-2/+48
| | | | | | | | sender_name and thread_id travel in clear beside the sealed envelope and were stored and relayed whatever their type and size. A name longer than a username or a thread id that is not a short id is now dropped (F-27). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a member is told a media tool failed, not what ffmpeg saidChristophe Besson9 hours4-3/+30
| | | | | | | | Stream, transcode and subtitle failures sent the exception's text — operator paths, versions — to the member. Fixed messages now, the cause in the log (F-24). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): the reaper deletes only the .part files the node wroteChristophe Besson9 hours2-11/+34
| | | | | | | | Any *.part older than a day in a writable root was deleted — a browser's download in progress in a shared folder included. Only names carrying the node's tag (name.<8 hex>.part) are reaped now (F-28). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a revoked account is disconnected, not only refused next timeChristophe Besson9 hours2-13/+84
| | | | | | | | A user revocation closed nothing: the denylist stopped the next connection and left the live ones streaming and chatting. Revocations now go through one method that closes the account's or the group's sessions (F-21). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: an MBK2 bundle is opened once and stored again as MBK3Christophe Besson10 hours6-8/+216
| | | | | | | | | | | | Transitional. The Argon2 run that makes M makes A, the key MBK2 bundles were sealed under; a session keeps it as a decrypt-only key (IndexedDB in a browser, the key storage in the desktop app). A client meeting an MBK2 bundle opens it — or its recovery copy — and stores the same identity as MBK3 once connected; the desktop app reseals or withdraws it as browser access says. A session without A asks for the passphrase once. Older formats stay refused by name. Replaces the unpin-and-reinvite step the 0.17 flag day required on every node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: downloads are marked and keep their extension; Explorer files are refusedChristophe Besson11 hours9-5/+108
| | | | | | | | | | | The desktop app writes the Mark-of-the-Web on each file it saves on Windows, as a browser does. Bidirectional controls are reserved characters in a saved name (portable-name.js and paths.sanitize_for_download, and again in the main process), so a name cannot display one extension and carry another. The node refuses uploads of files Windows Explorer acts on by itself: desktop.ini, .lnk, .url, .scf, .library-ms, .searchConnector-ms (F-19). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a stranger who knows your name locks only browsers you never usedChristophe Besson11 hours6-21/+272
| | | | | | | | | | | A sign-in from a browser that presented no token is answered with one (known_browser, kept hashed, twenty per account); a later sign-in presenting it counts failures on its own row, which nobody else can spend. Passphrase checks inside an open session (change, e-mail, deletion, device, pepper) count on the account's own row, so a locked name no longer stops its owner there either; /me reports that row. Reset and erasure forget the browsers (F-15). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): one member holds a share of the node, sized past real useChristophe Besson12 hours9-28/+278
| | | | | | | | | | | | | | 128 peer sessions on the node, at most 64 per account (the hub names the account with each offer; the node's own account is not counted). One account plays at most half the stream slots, rounded up, and runs two subtitle extractions at once. Frames after the handshake are 8 MiB (was 64), decoded with per-container bounds, and a frame refused for either ends the session instead of jamming its buffer (F-16). Sized for the heaviest real member: twenty groups on one node, three devices and a tab, up to 52 sessions. Measured: ~0.15 MiB and one fd per idle session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a group name fits its column and carries no control charactersChristophe Besson13 hours3-4/+58
| | | | | | | | Over 128 characters was a 500 on PostgreSQL; line breaks, C0/C1 controls and bidi overrides are refused (joiners stay, for emoji). The creation form caps the field at 128 (F-13, what remains of it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): an upload never replaces a file, nor shares a part with anotherChristophe Besson13 hours4-12/+151
| | | | | | | | | | | A name an upload in flight will take is reserved; each upload writes its own `name.<tag>.part`; the finished file is published by a hard link, which refuses an existing target, and takes the next free name if one appeared meanwhile — the last ack names it. Two members sending one name at once wrote one part and published it twice; a file copied in during an upload was replaced (F-09). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): ffprobe over a member's file is bounded, and stopped when it isChristophe Besson13 hours3-1/+84
| | | | | | | | | | | probe_video waits 30 s at most and kills ffprobe on a timeout or when its caller gives up — a cancelled wait left the process running. The seek probe kills what it timed out on. Stream, subtitle and enrichment requests no longer hang on a file that keeps ffprobe busy (F-18, timeouts; the protocol whitelist was dropped: ffmpeg already confines nested protocols of a local input, measured on 8.0 against HLS and concat inputs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): link previews connect to the address they checked, without blockingChristophe Besson13 hours3-55/+198
| | | | | | | | | | The name is resolved off the event loop and every answer checked; the socket is then opened to that IP literal through a pinned httpcore backend, TLS still verifying the certificate for the name, and no proxy from the environment. A name that answers clean and then with a LAN address no longer gets a request sent there, and a slow name no longer stalls the node (F-12). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): how a hosted group admits people is the operator's, not the hub'sChristophe Besson13 hours11-33/+202
| | | | | | | | | | | attach_group no longer copies join_policy and visibility from the hub's answer: they come with the operator's request (the desktop creation form, `group add --open`) and default to invite/private; the CLI says when the hub lists the group otherwise. Every string written into node.toml is escaped (toml_string) and read back through tomllib, so a group or folder name cannot write lines of its own (F-17). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: an identity signs a named kind, and a device approval answers a requestChristophe Besson26 hours17-66/+636
| | | | | | | | | | | The desktop main process builds every transcript itself from fields (transcripts.js) and signs no raw bytes; the page's identity has the same contract (crypto.js transcriptFor). The keyring seals no bundle while browser access is off. On the node, device_add must redeem a pending request filed by the same keys, and device_revoke is signed under its own prefix (meshbay:device_revoke:v1), so a retirement signature admits nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): the pepper and a device key take the passphrase, not a tokenChristophe Besson26 hours9-39/+134
| | | | | | | | | POST /me/bundle-pepper (was GET) and POST /users/devices require auth_key. A refreshed or lifted token could otherwise fetch the pepper, or register a device whose every sign-in carries it. Both callers have just been given the passphrase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: browser access, decided in the desktop applicationChristophe Besson29 hours19-5/+231
| | | | | | | | | Off for an account made there: its identities stay on the device and nothing is left on nodes. Turned on from the Profile page behind a native confirmation; each node is settled when its group next opens. The hub keeps a mirror a browser reads to say why a group will not open; it grants nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(client): the desktop application keeps M and every node identity in its ↵Christophe Besson30 hours25-59/+873
| | | | | | | | | | | | main process keyring.js derives, opens, mints, seals, signs and agrees there; the page gets public keys and a handle. Argon2 comes from the page's own WebAssembly build (Electron's crypto has none). Without OS key storage the page keeps its keys as a browser does. A node's bundle is settled after connecting, re-sealed when the key changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(hub): the transport holds an identity, never a private keyChristophe Besson31 hours13-99/+140
| | | | | | | | Two public keys, sign() and shared(); the apps take transport.signFn. What holds the keys (this page, or the desktop main process) is the identity's business alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: bump version to 0.17.0, and the client minimum with itChristophe Besson31 hours8-9/+13
| | | | Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: bundles sealed per node under the passphrase and the hub's pepperChristophe Besson32 hours30-479/+682
| | | | | | | | | The session key is M = HKDF(Argon2(passphrase) || pepper, account id); each node's bundle key and the playlist key derive from it. Bundles are MBK3, bound to account and node; MBK1/MBK2 are refused by name, never replaced silently. Playlists move to key v2 and are re-sealed over unreadable node copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): a bundle pepper per account, handed only to a proven sessionChristophe Besson33 hours5-0/+283
| | | | | | | | Sealed at rest and bound to the account; returned by sign-in, device sign-in, a passphrase change and GET /me/bundle-pepper, never by a refresh, to a node token, in a token or in a log. Erasure clears it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a downloaded file opens in a tab only under a type that runs nothingChristophe Besson33 hours5-24/+111
| | | | | | | Open is offered for PDFs, raster images, audio, video and plain text, typed from the name; HTML, SVG and the rest are not opened in the hub's origin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: the page connects to its own origin and reCAPTCHA, nowhere elseChristophe Besson33 hours3-9/+20
| | | | | | | | connect-src drops https: and wss: in both policies. Checked against Google's reCAPTCHA test keys: in Chrome widget, token and registration unchanged; in Firefox the widget loads; no violation reported in either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): the page names node operations, and the app confirms what ↵Christophe Besson34 hours23-191/+548
| | | | | | | | | | | widens the node node:call is replaced by named operations with checked arguments; hosting a group, sharing an unpicked folder, key rotation, denylist clearing and a change of node account are confirmed by a native dialog. Every channel checks its sender, secrets:get/set/clear are gone, node:start writes the app's own hub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): the admin allow-list grants an account, not a usernameChristophe Besson34 hours5-18/+269
| | | | | | | Each name in admin_usernames is pinned to the first active account seen holding it (admin_pins), so a name freed by a deletion grants nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): the roster, not the key alone, decides who gets a sessionChristophe Besson35 hours4-18/+186
| | | | | | | | | | | | | | | | | | | | | The handshake opened a session for anyone holding the group key with a hub token naming the group; the roster was consulted only when wrapping the key in a join. A member revoked or unpinned on the node but still a member on the hub kept a full session with the key they already held — and was handed the chat epoch their removal had just opened, since chat keys go to any session. An honest client never met this (it asks for the key through join_request every time); one that kept the key did not have to. - After the proof, the node asks the roster and refuses with `not_authorized_for_group` unless the account is an active member of the group or the node's operator. - A removal from any door — MNP, the node page, the CLI — now opens a new chat epoch in each group the person could read, broadcasts it, and closes every connection they hold (`ops.members._after_removal`). The CLI and the node page did neither. - Design §5.2, protocol §6.1, §6.3, §14.2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: only the owner decides who hosts a group, and nobody is made a member ↵Christophe Besson35 hours40-84/+1521
| | | | | | | | | | | | | | | | | | | | | | | | | | unasked - hub: a node may host a group only if its account owns it or the owner approved that node (new `group_hosts`). Membership was the ceiling, and every member holds the group key, so any member's node could register as a host and be the one clients kept. A node claiming a group it may not host is recorded as a request; the owner is notified once and approves or refuses it (GET/POST/DELETE /v1/groups/{id}/hosts[/{node_id}]), which takes effect on a connected node at once. - hub: an owner adding a username creates an invitation (new `group_invitations`), accepted or declined by the invitee (/v1/groups/invitations, /{id}/invitation/accept|decline). Until then the group is not listed, not dialled, not searched and not in any token. Invitation links, open joins and group creation still make members directly: they are the account's own act. - hub: the MNP token names only the group it is minted for (group_id is now required), so a node operator no longer learns a member's other groups. - SPA: invitations on the home page; invited people and host requests in the group's settings; the transport sends group_id. Ten catalogues. - Browser probes for both screens, run in Chrome and Firefox. - Design §5.2, §7.2, §7.3, AV32, AV33; protocol §6.3; user guide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: a member can no longer lock a node, crash it with a link, or stop hub ↵Christophe Besson35 hours6-84/+484
| | | | | | | | | | | | | | | | | | | cleanup - node: only a wrong code counts towards the join lock, now per account (5) as well as node-wide (20), and it is consulted only when a code is tried. Every member reconnecting gets the group key through join_request, so a lock checked before recognition let one member refuse it to everyone. - node: link previews read the body as a stream and stop at the cap, counted on decoded bytes; a declared oversized image is not read; 15 s total deadline; image decoding off the loop. `client.get` had buffered the whole (decompressed) response before the caps looked at it. - hub: the daily purge of never-verified accounts detaches their IP-log rows (keeping the name) and clears every other reference first, and each cleanup step runs on its own. On PostgreSQL the bare DELETE violated the ip_logs foreign key and stopped every purge behind it for good. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): search title, description, canonical and noscript in the shellChristophe Besson36 hours3-4/+67
| | | | | | | | | A descriptive <title>, a two-line meta description apart from the short one messengers get, "/" canonical for every shell path, and a <noscript> pitch with the download and doc links for crawlers that do not run app.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): sitemap.xml, named by robots.txtChristophe Besson36 hours3-1/+37
| | | | | | | Home, downloads, and the repository's about page and docs on git.meshbay.org, spelled as the welcome page links them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(hub): remove the closed relay registryChristophe Besson3 days4-259/+0
| | | | | | | Every /v1/relays route answered 503 and nothing called them; no TURN relay is needed. The proof-of-possession rule it carried stays as AV6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(node): remove five loopback routes nothing calledChristophe Besson3 days1-45/+0
| | | | | | | | The group-setting routes for app directories, chat directory, link previews, Search listing and scan settings had no caller and no test; those settings are signed MNP operations only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: keypair_bundle_delete is reserved for device_policy (O3)Christophe Besson3 days1-0/+4
| | | | | | | The node honours it and no interface sends it; offered alone it would strand the next browser that signs in. Stated in both documents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor: drop the unread transfer_limits field from the handshake ackChristophe Besson3 days3-35/+17
| | | | | | | The interface reads a member's cap from transfer_state and never read the copy on the ack. The transfer probe reads it from transfer_state too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(client): save files under a name every platform can writeChristophe Besson3 days18-7/+236
| | | | | | | | | A node serves the name its disk gave a file; the client now makes it portable at save time (single file, zip entries, zip name) and says so on the transfer row. Same rule as paths.sanitize_for_download, held by a parity test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): reports from public-group members, decided by an administratorChristophe Besson3 days21-113/+908
| | | | | | | | | | A report needs a person's account at least a day old, membership of the public group, and fits a daily allowance per account. Past the threshold a hash is queued and administrators are notified; blocking without review is an instance setting, off by default. Report menu item in public groups, Reports tab and settings in the admin panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: nodes apply the content blocklist in their public groupsChristophe Besson3 days15-55/+627
| | | | | | | | | A node hosting a public group syncs the hub's blocklist on every connection (paged, node token only) and applies pushed changes. A blocked file leaves the index and is refused (content_blocked); private groups are untouched. The unused per-hash check route is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor: remove the public-content swarmChristophe Besson3 days11-329/+57
| | | | | | | | Nodes registered the hashes of their public groups on the hub and nothing ever read them back. Routes, model and node registration removed; a migration drops swarm_sources. No node sends the hub a content hash now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: bound pending admin challenges and sign every value an op acts onChristophe Besson3 days16-110/+540
| | | | | | | | | | Any member could make a node hold unbounded challenge requests; a connection now keeps at most 8, 64 KiB each. root_add, group_attach, invite_create and tmdb_config signed less than they did; their subjects are now canonical JSON of every value (the TMDB token by SHA-256). MNP 5.0, floor kept at 4.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor: remove dead code across packagesChristophe Besson3 days23-784/+10
| | | | | | | | | Unused modules, functions, constants and client helpers with no caller, the unreachable hub:probe IPC handler, and the CSAM hash matching. Behaviour unchanged; the dispatch golden loses only the two removed message types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor: remove the unused GroupIndex.serialize chainChristophe Besson3 days11-388/+41
| | | | | | | | | serialize/deserialize had no production caller, and took with them the per-chunk signature, the ChaCha20 cipher variant and the zstandard dependency. Key derivations are unchanged. Docs corrected, including design §4.3's claim that chunks are compressed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: refuse an unsigned handshake challengeChristophe Besson3 days4-42/+35
| | | | | | | | Every node the 4.0 floor admits signs its challenge, and one without a channel binding could not complete the proof anyway, so a missing signature is refused like a wrong one (browser and QUIC client). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): new login tagline, dimmer gradient topChristophe Besson3 days11-11/+11
| | | | Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): say so when a lazily loaded view cannot be fetched0.16Christophe Besson3 days15-5/+277
| | | | | | | | A tab opened before a hub deploy got 404 for every module it had not loaded yet, and lazy.js kept its spinner for good. It now shows a notice with a Reload button and logs the failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): keep a show's detail modal open under the playerChristophe Besson3 days6-4/+373
| | | | | | | Closing the player lands back on the season being watched, with the episode just started marked. A film's modal still closes on Play. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): tell whether a pid is alive on Linux tooChristophe Besson4 days1-0/+8
| | | | | | | | _pid_alive only ran tasklist, so request_graceful_stop raised FileNotFoundError off Windows and two real-daemon tests failed on Linux. Read /proc there, counting a zombie as gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>