Commits
-
Private things stay private on every read path (roadmap WS-R task 5: private bypass), with four fixes the new test found. test/security-private.test.js seeds private text and screenshot pastes (and a comment on one), an unlisted paste, burn-after-read pastes, a members-only space and a members-only thread (VIP down: fail closed), a staff space, a members space, a hidden comment thread and a Live VOD's thread, each with a marker, then crawls every GET route from the router stack with their ids and a query string (search, include_unlisted, ?fork=) as anonymous, another user and a service acting for them (about 6000 requests): no marker may appear (a gated thread's title is listed by design), and every route must answer a private paste, a staff space or post, or a hidden comment thread exactly as an unknown one (status and error code or page title). On top: each read and read-like write of a private paste answers like a missing one and writes nothing, the owner still reads it; unlisted pastes open by link but are never listed or counted; hidden threads and a VOD thread's sequential id look missing to browsers; nothing private is a Pulse item, a search document or a public event. Extends members-only.test.js and security.test.js without repeating them. Fixed (severity): - medium, server/events.js + server/comments/store.js: community.comment.created was a public event for every comment on a thread that was not hidden, with the thread's access id, its ref and /c/<access id>. Public events reach every realtime listener and developer app, so comments on a private Live VOD or clip were readable by anyone who watched the stream, and a comment on an unlisted paste's thread published its secret slug. The event is now public only when Community knows the item is public (a public, live, non-burning paste; a post in a public space outside members-only); otherwise internal, url null, as the contract says. - low, server/forum/store.js: threads and posts in a members-only (VIP) space were public events with their URL (thread slugs carry the title). They are 'members' now, like gated threads and non-public spaces. - low, server/forum/service.js: the 403 vip.members_only for a thread inside a members-only space carried the thread's title, and post ids are sequential, so GET /api/v1/posts/<n>/versions named every thread in such a space; the title is now given only when the thread's own gate refuses. A post in a staff space answered 404 space.not_found (an unknown post: post.not_found), confirming it exists; it is post.not_found now. - low, server/pastes/service.js + server/app.js: /p/:slug/screenshot (and /raw, which redirects there) never counted as the read of a burn-after-read screenshot, so someone with the link could look at it any number of times without burning it (the ?no_view=1 class). It is a read now, raw's rule: the first one passes, the next finds it burned (410); the owner's own looks do not count.OpenVibers · 26 Sept 2026
249d0ed -
Security crawl for internal secrets (roadmap WS-R task 5: internal-secret leak). test/security-secrets.test.js boots the app with every secret Community reads set to an obviously fake sentinel (sentinel-not-a-secret-<name>): OV_OAUTH_CLIENT_SECRET, both COMMUNITY_EVENTS_SECRET values, VIEW_HASH_SECRET, DISCORD_BOT_TOKEN and a Discord webhook URL named by DISCORD_RELAY_WEBHOOK_VARS. Positive controls show each is really in use (Network grants service tokens only for the sentinel client secret, the relay posts to the sentinel webhook, deliveries signed with either events secret are accepted, anonymous views are keyed by an HMAC under VIEW_HASH_SECRET). Then every GET route Express knows after boot (test/helpers/routes.js walks the router stack, so a route added later is crawled too) is requested with seeded and nonsense ids and a query string as anonymous, a user, an admin and a staff service, plus error paths (unknown routes, malformed JSON on every write, garbage, expired, forged, wrong-audience, sandbox and unvouched tokens, failed sign-in and refresh, unsigned, forged and proxied event deliveries), /api/ready, /metrics, /release.json and the staff relay and paste admin reads: 1566 requests. No body or header carries a sentinel (as is, URL-encoded, hex, base64 at any alignment) or a service token the Network mock issued; neither do the events outbox, the relay's recorded errors, the content tables, or anything the process logged. None found. Helpers: boot({ env }) sets environment after the defaults (its client secret is the one the Network mock accepts), the mock records the tokens it issues.OpenVibers · 26 Sept 2026
cf50b27 -
The notification bell hears new notifications as they happen (notificationsRealtime; Shared 1.22.0, roadmap WS-E task 3): Network's network.notification.created over OpenVibe.Events' realtime stream, with polling as the fallback. Only a signed-in bell with a Network token opens a stream.OpenVibers · 26 Sept 2026
ccdab27 -
N-1 fixtures re-recorded from 90edad6, the release now in production (npm run n-1:record).OpenVibers · 26 Sept 2026
a0db8c4 -
Pulse shows game milestones (roadmap WS-M task 2, PF7): Community subscribes to network.module.updated, and a person's games.progress.summary write (level is a public field in Contracts) updates game_progress. A later write crossing a multiple of 5 becomes one Pulse item, 'Reached level 10 in Scraplandia' (Games · level), with the person as actor. The first write seen is only a baseline, so there is no stale announcement; a level going down (another character) announces nothing; exactly once through the inbox; only the record's public fields are read.OpenVibers · 26 Sept 2026
90edad6 -
N-1 tests (roadmap WS-P task 11): the previous release's clients against this server, and its SQL against this schema. test/n-1.test.js boots this checkout as production runs it (pastes authority community, the shared database) with its Live, Network and Media mocks, on a database created from the previous release's schema, so Community's migrations run over it, and replays test/fixtures/n-1/client.json inside openvibe-shared's mixed-version matrix (release-compat assertMixedVersion): 78 requests (community.js, the templates' forms, the openvibe-sdk community client, and every link, script and form of ten pages that release served: /, /pastes, a paste, a space, a thread, a comment thread, /search, /pulse, /new, /my; every call anonymously, reads signed in with the ov_token cookie), each answered compatibly (any 2xx for a 2xx, the same status otherwise, JSON where there was JSON, redirects to the same place, every response field the client reads with a type it had). Then every statement in test/fixtures/n-1/worker.json (175: what that release ran, plus its SQL literals) must still prepare on the migrated schema, and no old INSERT may miss a new NOT NULL column without a default. Fixtures recorded from 19441aa, the release in production; refresh after each deploy with `npm run n-1:record [ref]` (a temporary worktree of the release, booted, seeded, replayed). The harness is the file Live, Chat and Media share (test/n-1/harness.js); test/n-1/service.js and boot-child.js are Community's part. In npm test, so in CI, about 2 s, no git history or network. Checked by breaking it: a paste's views as a string fails it.OpenVibers · 26 Sept 2026
0a3b5ea -
Pins: openvibe-shared 1.20.0 -> 1.22.0 (realtime notification badge, off until a site turns it on; the unreachable-host browser check)OpenVibers · 26 Sept 2026
19441aa -
A space can attach a chat room on OpenVibe.Chat (roadmap WS-I task 4): its owner (the space's creator, or the creator whose members' space it is) or discussion staff attach one room by address or link, from the space page without JavaScript (POST /s/:space/chat-room, /chat-room/detach) or PUT/DELETE /api/v1/spaces/:space/chat-room; the space page (feed and forum style) links it with its kind and "members only" for a private room. Each side checks its own end: Community the space, Chat that the same person manages the room, because Community asks Chat with the person's own Network token (server/chat-rooms.js: POST OV_CHAT_INTERNAL_URL/api/chat/rooms/:room/attachments { service: community, resource, title }), so no service grant or capability is involved and services cannot attach (moderator services may detach). Chat's refusals come back as chat_room.not_owner (403), chat_room.not_found (404, also a private room the person is not in) and chat_room.unavailable (503/502), shown as a notice on the page. Attaching the same room again is a no-op, another room replaces it (Chat is told the space let go of the old one, best effort), detach is idempotent and asks Chat to drop its side when the person may. The link is kept in space_chat_rooms (CREATE TABLE IF NOT EXISTS: the room's id and slug, and its name, kind and visibility as Chat answered); spaces in the API carry chat_room. No event yet (no contract has one). Test: chat-room.test.js against a stub ChatOpenVibers · 26 Sept 2026
50668b7 -
Open redirect fixed (roadmap WS-R task 5): the sign-in next let "/<TAB>/evil.com" through, which browsers read as "//evil.com"; any control character or backslash in next now goes home. test/open-redirect.test.js feeds it the known bypassesOpenVibers · 26 Sept 2026
1eff653 -
Discord relay both ways, as an Events worker, with an external message map (roadmap WS-J tasks 5 and 6): replies follow new threads to Discord, edits PATCH and deletes (or gating a thread members-only) DELETE the Discord messages through relay_message_map (Discord message <-> thread/post per mapping, unique both ways; creates are sent with ?wait=true to learn the message id, and a mapping can post into a Discord thread via thread_id). Creates are queued by an Events worker that reads Community's own community.thread.* / community.post.* from OpenVibe.Events' pull API with a cursor kept in relay_cursors (moved in the same transaction as the deliveries; first start begins at the head; dedupe keys make replays a no-op); without EVENTS_URL / the client secret, or with DISCORD_RELAY_EVENTS=off, the forum queues them as before. Inbound: a Discord gateway client on Node 22's built-in WebSocket (IDENTIFY/RESUME, heartbeats with zombie detection, RECONNECT, INVALID_SESSION, fatal close codes; intents GUILD_MESSAGES | MESSAGE_CONTENT) behind DISCORD_RELAY_INBOUND=on and DISCORD_BOT_TOKEN turns replies to relayed messages in mapped channels into posts with origin discord (no author subject, the Discord name in posts.relay_author, a Discord badge), rate- and size-limited with mentions defused; edits and deletes on Discord follow through the map. Loop prevention both ways (origin discord never goes out; webhook, bot and system messages never come in). Dead letters: failed deliveries keep their error; staff retry or drop them, and inbound failures (ids and reason, never text) are listed and dismissed at /api/v1/relay (new: status, drop, inbound). Off by default and inert without the owner's webhooks, mappings and bot token; owner steps in docs/discord-relay.md. Schema: relay_deliveries rebuilt once (post_id, action, source, event_id, statuses dropped/skipped), relay_mappings gets discord_channel_id/discord_thread_id/inbound, posts gets relay_author, new relay_message_map, relay_cursors, relay_inbound_failures. Tests: relay.test.js, relay-events.test.js (fake Events pull server, the whole outbox -> Events -> worker -> Discord path), relay-inbound.test.js (fake gateway), shutdown.test.jsOpenVibers · 26 Sept 2026
f94d83a -
Pins: openvibe-shared 1.18.0 -> 1.20.0 (the update matrix and release manifest 1.2.0: client generations, the shell)OpenVibers · 26 Sept 2026
d511da5 -
Pins: openvibe-shared 1.17.1 -> 1.18.0 (update telemetry: prompted count, sessions by generation)OpenVibers · 26 Sept 2026
6a855de -
Pins: openvibe-shared 1.16.1 -> 1.17.1 (release notifications)OpenVibers · 26 Sept 2026
9a468fb -
Pins: openvibe-shared 1.16.0 -> 1.16.1 (the OV mark stops animating when idle, hidden or off-screen)OpenVibers · 26 Sept 2026
be15c21 -
A guest's session probe answers 200, not 401 (browser check, WS-Q task 3): GET /auth/me with no session credential at all answers { user: null }, which the shared navbar reads as signed out. It asks on every page view, and the 401 logged a console error on each. A credential that is present but invalid or expired still answers 401. Test: auth.test.jsOpenVibers · 26 Sept 2026
564c610 -
The CSP allows Cloudflare Web Analytics (browser check, WS-Q task 3): script-src https://static.cloudflareinsights.com loads the beacon Cloudflare injects at the edge, connect-src https://cloudflareinsights.com is where it reports. The network's privacy text already says Cloudflare may measure performance, and Live and Tools allow it; here the policy blocked it and every page view logged a CSP error. Test: seo.test.jsOpenVibers · 26 Sept 2026
e2f7c1d -
Pins: openvibe-shared 1.15.0 -> 1.16.0OpenVibers · 26 Sept 2026
1bac6a9 -
nginx: /auth/me, the session probe the shared navbar asks on every page view, gets its own location on the API zone (burst 30) instead of the sign-in zone (10 a minute, burst 10), and both answer 429 when limited instead of nginx's default 503. The browser check (Host scripts/browser-check.js, WS-Q task 3) saw /auth/me answer 503 after about ten quick page loads, which the navbar takes as signed out. test/nginx-auth-limit.test.js. Takes effect when the vhost is next installed (not deployed here)OpenVibers · 26 Sept 2026
eb68e9e -
Breadcrumb links are underlined: in the muted crumb line they were told apart by colour alone (axe link-in-text-block, serious, on /pulse, /s and every paste and thread page; found by the browser check, Host scripts/browser-check.js, WS-Q task 3)OpenVibers · 26 Sept 2026
4072708 -
Graceful stop (WS-P lifecycle follow-up): SIGTERM used to close only the HTTP server (which waited on 65 s keep-alive connections until the 5 s fallback) while the events outbox, the Discord relay, the Pulse subscription retries and the profile and search-document scans kept running into the exit. Now (server/graceful.js): the subscription retries, the community.profile scan and the search-document scans stop; the server stops taking connections, closes idle keep-alive ones and lets requests in flight finish with Connection: close (4 s at most); then the profile scan in progress (it stops between people), the relay's drain and the outbox's send are awaited (unsent rows stay for the next start), community.db closes (db.closeDb) and the process exits 0, within 5 s (exit 1 past it). events.stop(), relay.stop() and profile.stop() resolve when their work in flight ends and start nothing after. test/shutdown.test.js: the real process with every worker on, SIGTERM with a request in flight and an idle keep-alive connection: refused afterwards, the request answered, every worker reported stopped, exit 0 in < 5 sOpenVibers · 26 Sept 2026
fc7aaf6 -
Size budgets for the home page (WS-T task 1): openvibe-shared/perf-budget (shared 1.15.0) measures / as the server answers it on a fresh database, no browser, in npm test; budgets a little above the 2026-09-26 measurementOpenVibers · 26 Sept 2026
5bf0400 -
Community: primary buttons use the derived --accent-strong pair (4.5:1 in every theme) and links in running text are underlined (colour alone did not mark them) — WS-T task 3OpenVibers · 25 Sept 2026
4132923 -
Pins: openvibe-shared 1.12.0 → 1.13.0, openvibe-sdk 0.9.1 → 0.11.0OpenVibers · 25 Sept 2026
077d7be -
Platform blocks (WS-E task 5, Contracts 0.49.0): Community consumes network.block.changed into a newest-revision projection and refuses (403 community.blocked, plain message on the no-JS forms) a reply in a thread whose author blocked you, a reply to a comment whose author blocked you, and a comment on a paste or post whose owner blocked youOpenVibers · 25 Sept 2026
0cce476 -
Pulse on Events realtime (WS-F task 1): on the first page, public activity from here and the other sites (new pastes, threads, replies, go-lives, blog, wiki and news posts) arrives over events.openvibe.network/realtime/stream and a bar offers to show it; CSP connect-src allows events.openvibe.networkOpenVibers · 25 Sept 2026
d401ef4 -
Search page (WS-O task 10: product search boxes use the query API): /search finds public threads and pastes through OpenVibe.Search (anonymous, owner=community, at its internal origin), with type filter and cursor paging; snippets keep only Search's <mark>; Search not answering is a plain 503 page offering the paste filter; noindex; Search in the navbar; a link to search the whole networkOpenVibers · 25 Sept 2026
49282fc -
Search: a paste whose slug starts with '-' or '_' is indexed as paste_<row id> (a Search document id starts with a letter or digit; Search refused -wgXuYn0); the refused id is forgotten, with no tombstoneOpenVibers · 25 Sept 2026
0fe3c07 -
Threads and pastes in OpenVibe.Search (WS-O task 10, Contracts 0.44.0): each public thread (public, open space; open thread) and public paste is a community.index_document.upserted through the outbox, sent only when it changed with a growing revision, and a tombstone once deleted, made private, unlisted or members-only, or burning after reading; NSFW pastes and crossposts are noindex; a paste's syntax is a facet, not a language; a scan every minute and an hourly paste refreshOpenVibers · 25 Sept 2026
6b608f3 -
Pins: openvibe-contracts 0.41.0 → 0.44.0; CI runs the pin-drift check (shared test and security workflows at 423fe51)OpenVibers · 25 Sept 2026
adbfd8b -
C-24: screenshot pastes name media objects. New screenshots (and censored replacements) are OpenVibe.Media v2 objects (kind screenshot, unlisted, owned by the person when signed in; the v1 file store only without Community's service principal), with media_ref med_…; forum images are unlisted too (reachable by link, never listed on openvibe.media). scripts/migrate-screenshot-refs.js moves existing legacy:live:paste / legacy:community:file references to their med_ objects from Media's export (dry run by default, --apply takes a backup first, keeps screenshot_url, idempotent)OpenVibers · 25 Sept 2026
5355423