Skip to content

core: Project write endpoints accept any signed-in member — project-delete, project-memory and project-memory-write have no owner check #1740

Closed

Description

@HugoXK

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): projectCanManage compares project.ownerUserId with projectActorId(actor) (the actor's memberId, falling back to the first member on an install with no session), and throws bot/forbidden / Only the owner of this Project may change it.
  • :580 project-delete — deleteProject(id), no check. Deletes the Project and its assistant memory.
  • :587 project-memory (read) — readProject + projectMemory, no check.
  • :588 project-memory-write — appendProjectMemory(id, kind, body), no check.
  • :601 project-bind — the only caller of requireProjectOwner.
  • :608 project-assistant-owner (#1733) — rt.projectAssistant.setOwner(project.id, body.ownerId, body.settings), no check, and body.ownerId comes from the request, so a caller can change another owner's digest settings. This endpoint is not wired yet: it is absent from core/src/host/channel.js's endpoint cases and from authz.js's table, so over HTTP it answers bot/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) → returns owner-1's entries.
  • rt.projectEndpoint('project-assistant-owner', { id: p, ownerId: 'owner-1', settings: { off: true } }, actor) → setOwner runs 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-memory and project-memory-write → bot/forbidden, memory byte-identical — a refusal, never a silent no-op;
  • non-owner project-assistant-owner naming another owner → bot/forbidden, that owner's quiet / off record unchanged;
  • the real owner still succeeds on all four (the fix must not close the feature);
  • an unknown id keeps today's behaviour: requireProjectOwner returns early on null, so project-memory of a missing id still answers with an empty memory rather than turning into a 500 or a new 403;
  • authz.js and channel.js untouched 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):

  1. non-owner project-delete → error code bot/forbidden, and state.projects[p] still there, memory length unchanged;
  2. non-owner project-memory → bot/forbidden (not 200 with memory: []);
  3. non-owner project-memory-write → assert error.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;
  4. non-owner project-assistant-owner with body.ownerId naming the other owner → bot/forbidden, and the target owner's record unchanged;
  5. the owner control: the real owner succeeds on all four (one assertion each), and a missing id still answers in the bot/not-found family.

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

  1. HugoXK commented on Oct 29, 2026

    @HugoXK
    ContributorAuthor

    Heartbeat

    Written-by: FlyBot Lead
    Still-working: ca12c496f21e43958b3543c2442b87b1
    At: 2026-10-29T23:52:59Z

  2. added a commit that references this issue on Oct 29, 2026
    e0745e2
  3. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    area:securityAuth, grants, approvals, secrets
    bugSomething isn't working
    on Oct 30, 2026
  4. added a commit that references this issue on Oct 30, 2026
    178bf75
  5. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 30, 2026
  6. HugoXK commented on Oct 30, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: ca12c496f21e43958b3543c2442b87b1
    Reason: stood down
    At: 2026-10-30T04:09:14Z

  7. added 2 commits that reference this issue on Oct 30, 2026
    5bd16da
    e20f8d1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:securityAuth, grants, approvals, secretsbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions