toeverything/AFFiNE · error · SpaceAccessDenied
space_access_denied
space_access_denied
Error message
You do not have permission to access Space ${spaceId}. What it means
Thrown when fetching a workspace blob: the requester fails BOTH the Workspace.Read access check AND canReadSharedWorkspaceBlobs. The user is neither a workspace member nor covered by a public/share grant that allows reading blobs.
Source
Thrown at packages/backend/server/src/core/workspaces/controller.ts:117
// NOTE: because graphql can't represent a File, so we have to use REST API to get blob
@Public()
@Get('/:id/blobs/:name')
@CallMetric('controllers', 'workspace_get_blob')
async blob(
@CurrentUser() user: CurrentUser | undefined,
@Param('id') workspaceId: string,
@Param('name') name: string,
@Query('redirect') redirect: string | undefined,
@Res() res: Response
) {
const canReadWorkspace = await this.ac
.user(user?.id ?? 'anonymous')
.workspace(workspaceId)
.can('Workspace.Read');
const canReadSharedWorkspaceBlobs =
await this.canReadSharedWorkspaceBlobs(workspaceId);
if (!canReadWorkspace && !canReadSharedWorkspaceBlobs) {
throw new SpaceAccessDenied({ spaceId: workspaceId });
}
const { body, metadata, redirectUrl } = await this.storage.get(
workspaceId,
name,
true
);
if (redirectUrl) {
// redirect to signed url
if (redirect === 'manual') {
return res.send({
url: redirectUrl,
});
} else {
return res.redirect(redirectUrl);
}
}
View on GitHub (pinned to 26c515e050)
Solutions
- Authenticate the request and ensure the user is a member with Workspace.Read.
- Re-enable public/share access for the blob if it should be shared.
- Provide a valid share token / signed URL if using link sharing.
- Remove dead hotlinks that assume public access.
Example fix
// before
fetch(`/api/workspaces/${ws}/blobs/${name}`) // no creds
// after
fetch(`/api/workspaces/${ws}/blobs/${name}`, { credentials: 'include' }) Defensive patterns
Strategy: validation
Validate before calling
const canWs = await ac.user(userId ?? 'anonymous').workspace(ws).can('Workspace.Read')
const canShared = await canReadSharedWorkspaceBlobs(ws)
if (!canWs && !canShared) return redirectToLogin() Try / catch
try { await getBlob(ws, name) } catch (e) {
if (e.code === 'space_access_denied') redirectToLogin()
else throw e
} Prevention
- Send credentials on blob requests.
- Use short-lived signed URLs for sharing.
- Audit public-blob toggles.
When it happens
Trigger: An anonymous or unauthenticated request (or a user from another workspace) hits /workspaces/:id/blobs/:name without any share grant permitting blob reads.
Common situations: Direct/hotlinked blob URL with no credentials; share token expired; workspace switched from public to private; wrong cookie/session.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/455ab47be60d4e40.
Report an issue: GitHub.