Repository navigation
core: Project write endpoints accept any signed-in member — project-delete, project-memory and project-memory-write have no owner check #1740
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: ca12c496f21e43958b3543c2442b87b1
Branch: dev/1-project-owner-gate
Base: 4916bd5
At: 2026-10-29T23:31:28Z
Written-by: FlyBot Lead
What — #1722 added four Project endpoints that change Project state, and none of them asks whether the caller owns the Project. The check already exists since #1710; only project-bind calls it. At live main 4916bd5f1d0caee85618d2543b3533313e90f7db (#1737, merged 22:34:35Z):
core/src/host/store.js:555—requireProjectOwner(project, actor):projectCanManagecomparesproject.ownerUserIdwithprojectActorId(actor)(the actor'smemberId, falling back to the first member on an install with no session), and throwsbot/forbidden/Only the owner of this Project may change it.:580project-delete—deleteProject(id), no check. Deletes the Project and its assistant memory.:587project-memory(read) —readProject+projectMemory, no check.:588project-memory-write—appendProjectMemory(id, kind, body), no check.:601project-bind— the only caller ofrequireProjectOwner.:608project-assistant-owner(#1733) —rt.projectAssistant.setOwner(project.id, body.ownerId, body.settings), no check, andbody.ownerIdcomes from the request, so a caller can change another owner's digest settings. This endpoint is not wired yet: it is absent fromcore/src/host/channel.js's endpoint cases and fromauthz.js's table, so over HTTP it answersbot/not-found: Unknown endpoint. It must carry the same gate before it is wired.
core/src/shared/authz.js:60-74 puts project-memory, project-memory-write and project-delete in TASK_ENDPOINTS, and :411 answers ALLOW for that set (a signed-in actor is the whole requirement). That table is deliberately coarse — the record-level authority belongs where project-bind already puts it, in the host. Nothing in authz.js needs to change.
Why it matters — any signed-in member can delete another member's Project (taking its private memory with it), can read another Project's private memory, and can write into it. That memory is exactly what the owner's 09:00 digest reads and quotes back (core/src/host/project-assistant.js:78, the In progress, waiting on: line), so the write is an injection path into another owner's digest. The hole arrived with #1722, went out in v0.2.14, and v0.2.14 is not on the running instance yet.
Reproduction — on 4916bd5f, with state.authSessions non-empty and two members owner-1 / owner-2; project-create as owner-1 gives Project p; then, with actor { memberId: 'owner-2' }:
rt.projectEndpoint('project-delete', { id: p }, actor)→ no throw,state.projects[p]is gone.rt.projectEndpoint('project-memory-write', { id: p, kind: 'decision', text: 'injected' }, actor)→ succeeds, memory length +1.rt.projectEndpoint('project-memory', { id: p }, actor)→ returnsowner-1's entries.rt.projectEndpoint('project-assistant-owner', { id: p, ownerId: 'owner-1', settings: { off: true } }, actor)→setOwnerruns unchecked (in-process; over HTTP the endpoint is still unlisted).
Expected — the four endpoints are the Project owner's to use:
- non-owner
project-delete→bot/forbidden, Project and its memory still present; - non-owner
project-memoryandproject-memory-write→bot/forbidden, memory byte-identical — a refusal, never a silent no-op; - non-owner
project-assistant-ownernaming another owner →bot/forbidden, that owner'squiet/offrecord unchanged; - the real owner still succeeds on all four (the fix must not close the feature);
- an unknown id keeps today's behaviour:
requireProjectOwnerreturns early onnull, soproject-memoryof a missing id still answers with an empty memory rather than turning into a 500 or a new 403; authz.jsandchannel.jsuntouched in this change (project-assistant-owner's wiring is its own issue).
Red-first — five, each red on 4916bd5f first (drive rt.projectEndpoint with an explicit actor, the shape the project-bind tests already use):
- non-owner
project-delete→ error codebot/forbidden, andstate.projects[p]still there, memory length unchanged; - non-owner
project-memory→bot/forbidden(not 200 withmemory: []); - non-owner
project-memory-write→ asserterror.code === 'bot/forbidden'and that the stored entries are unchanged — asserting "the result looks different" would let a silent no-op pass, which is the failure mode this line exists for; - non-owner
project-assistant-ownerwithbody.ownerIdnaming the other owner →bot/forbidden, and the target owner's record unchanged; - the owner control: the real owner succeeds on all four (one assertion each), and a missing id still answers in the
bot/not-foundfamily.
Scope — core/src/host/store.js projectEndpoint only: requireProjectOwner on project-delete, project-memory, project-memory-write, project-assistant-owner (resolving the target owner from body.ownerId, and answering bot/forbidden when it is not the actor's own record); one test per path in core/test/store.test.js-style host tests; regenerated patches/ds-bot/** mirror plus core/client.js if the touched files sit under a bundle. No new endpoint, no store change, no authz change, no channel wiring.
Duplicate check (as of my read, live main 4916bd5f): grep -n requireProjectOwner core/src hits store.js:555 and :601 only; issue search over project-delete, project-memory, project owner finds no matching defect report — #1710 introduced the helper, #1722 introduced the hole, and #1723/#1725 were the read-side visibility fix for project-board. Nothing to fold into.
Activity
HugoXK commentedon Oct 29, 2026
Heartbeat
Written-by: FlyBot Lead
Still-working: ca12c496f21e43958b3543c2442b87b1
At: 2026-10-29T23:52:59Z
Gate the Project owner endpoints behind requireProjectOwner
Gate the Project owner endpoints behind requireProjectOwner
HugoXK commentedon Oct 30, 2026
Release
Written-by: FlyBot Lead
Released: ca12c496f21e43958b3543c2442b87b1
Reason: stood down
At: 2026-10-30T04:09:14Z
fix(host): require the Project owner on delete, memory and assistant …
feat(client): a Settings page that creates and binds a Project (Think…