{"record":{"id":"f13b0bd3146a86d8","repo":"toeverything/AFFiNE","slug":"space-access-denied-f13b0b","errorCode":"space_access_denied","errorMessage":"You do not have permission to access Space ${spaceId}.","messagePattern":"You do not have permission to access Space (.+?)\\.","errorType":"exception","errorClass":"SpaceAccessDenied","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/sync/gateway.ts","lineNumber":326,"sourceCode":"      };\n    }\n  }\n\n  private rejectJoin(client: Socket) {\n    // Give socket.io a chance to flush the ack packet before disconnecting.\n    setImmediate(() => client.disconnect());\n  }\n\n  private async assertDocActionAllowed(\n    spaceType: SpaceType,\n    userId: string,\n    spaceId: string,\n    docId: string,\n    action: DocAction\n  ) {\n    if (spaceType === SpaceType.Userspace) {\n      if (spaceId !== userId) {\n        throw new SpaceAccessDenied({ spaceId });\n      }\n      return;\n    }\n\n    await this.ac.user(userId).doc(spaceId, docId).assert(action);\n  }\n\n  private assertUserdataSubject(\n    spaceType: SpaceType,\n    userId: string,\n    workspaceId: string,\n    docId: string\n  ) {\n    if (\n      spaceType === SpaceType.Workspace &&\n      !authorizeUserdataDocSubject(userId, workspaceId, docId)\n    ) {\n      throw new SpaceAccessDenied({ spaceId: workspaceId });","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/sync/gateway.ts#L308-L344","documentation":"SpaceAccessDenied (code `space_access_denied`, category `no_permission`, carries `{ spaceId }`) at gateway.ts:326, in `assertDocActionAllowed`. For `SpaceType.Userspace`, the spaceId must equal the authenticated userId — a userspace is per-user, so no other user may touch it. Any divergence throws before the doc-level permission check.","triggerScenarios":"A sync gateway message (load-doc, delete-doc, push-doc-update, etc.) with `spaceType === 'userspace'` and `spaceId !== user.id`. The check runs after `assertUserdataSubject` but is the userspace-specific gate; the equivalent also exists in `UserspaceSyncAdapter.assertAccessible` (gateway.ts:999).","commonSituations":"Client sends another user's userId as spaceId; session user changed but the client kept an old spaceId; cross-user sharing attempt on a userspace doc; client bug confusing workspaceId with userspaceId.","solutions":["For userspace operations, always set `spaceId` to the current user's id.","If cross-user collaboration is needed, use a workspace, not a userspace.","On the client, distinguish userspace from workspace and never substitute another userId."],"exampleFix":"// before\nsocket.emit('space:load-doc', { spaceType: 'userspace', spaceId: otherUserId, docId }); // -> 278\n\n// after\nsocket.emit('space:load-doc', { spaceType: 'userspace', spaceId: currentUser.id, docId });","handlingStrategy":"try-catch","validationCode":"// Best-effort client guard: only open userspace for the current user.\nfunction ownUserspace(spaceType: string, spaceId: string, userId: string): boolean {\n  return spaceType !== 'userspace' || spaceId === userId;\n}\nif (!ownUserspace(msg.spaceType, msg.spaceId, currentUser.id)) {\n  // do not emit; surface access error to UI\n}","typeGuard":null,"tryCatchPattern":"// Permission is server-authoritative; catch SpaceAccessDenied and surface it.\nsocket.on('error', (err: { code?: string; message?: string }) => {\n  if (err?.code === 'space_access_denied') {\n    ui.showAccessDenied(err.message ?? 'You do not have permission to access this space.');\n    return;\n  }\n  throw err;\n});","preventionTips":["For userspace, always set spaceId to the authenticated user's id.","Treat spaceId as the user's identity, not a shareable handle.","Use workspaces for multi-user collaboration; never share userspaces."],"tags":["authz","sync","websocket","userspace","permission"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}