{"record":{"id":"243ea6ead733b691","repo":"paperclipai/paperclip","slug":"runtime-service-control-is-outside-this-actor-s-au-243ea6","errorCode":null,"errorMessage":"Runtime service control is outside this actor's authorization boundary","messagePattern":"Runtime service control is outside this actor's authorization boundary","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"server/src/routes/projects.ts","lineNumber":151,"sourceCode":"  async function assertProjectReadAllowed(req: Request, res: Response, project: { id: string; companyId: string }) {\n    const decision = await access.decide({\n      actor: req.actor,\n      action: \"project:read\",\n      resource: { type: \"project\", companyId: project.companyId, projectId: project.id },\n    });\n    if (decision.allowed) return true;\n    res.status(403).json({ error: \"Project is outside this actor's authorization boundary\" });\n    return false;\n  }\n\n  async function assertRuntimeManageAllowed(req: Request, res: Response, companyId: string) {\n    const decision = await access.decide({\n      actor: req.actor,\n      action: \"runtime:manage\",\n      resource: { type: \"company\", companyId },\n    });\n    if (decision.allowed) return true;\n    res.status(403).json({ error: \"Runtime service control is outside this actor's authorization boundary\" });\n    return false;\n  }\n\n  async function filterProjectsForActor<T extends { id: string; companyId: string }>(req: Request, rows: T[]) {\n    const decisions = await Promise.all(rows.map((project) =>\n      access.decide({\n        actor: req.actor,\n        action: \"project:read\",\n        resource: { type: \"project\", companyId: project.companyId, projectId: project.id },\n      })\n    ));\n    return rows.filter((_, index) => decisions[index]?.allowed);\n  }\n\n  router.param(\"id\", async (req, _res, next, rawId) => {\n    try {\n      req.params.id = await normalizeProjectReference(req, rawId);\n      next();","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/routes/projects.ts#L133-L169","documentation":"HTTP 403 sent by assertRuntimeManageAllowed in the projects router when the access-control layer denies the actor's 'runtime:manage' action on the project's company. The route guards runtime service control (start/stop/restart style commands) behind a per-company authorization decision; the actor is authenticated but not authorized for this operation.","triggerScenarios":"POST/PUT to a project workspace runtime command endpoint (e.g. /projects/:id/workspaces/:workspaceId/runtime/...) where access.decide({action:'runtime:manage'}) returns allowed:false for req.actor — e.g. an agent API key or a non-admin board user issuing runtime commands on a company they can only read.","commonSituations":"Agent bearer keys trying to restart a workspace runtime; board users with view-only role; actor from a different company than the project; missing runtime:manage grant after role changes.","solutions":["Grant the actor (role/policy) the runtime:manage permission for the project's company in the access-control configuration.","Retry the call as a user/agent key that has runtime management rights on that company.","If an agent key must manage runtimes, re-scope the request to the actor's own company or route it through a board-authorized service.","Verify req.actor is correctly resolved (auth middleware loaded, correct company context) — a misattributed actor denies otherwise-valid calls."],"exampleFix":"// before\ncurl -H \"Authorization: Bearer $AGENT_KEY\" /api/projects/p1/workspaces/w1/runtime/restart\n// 403 Runtime service control is outside this actor's authorization boundary\n// after\ncurl -H \"Authorization: Bearer $BOARD_ADMIN_KEY\" /api/projects/p1/workspaces/w1/runtime/restart","handlingStrategy":"try-catch","validationCode":"// client-side pre-check\nif (!actor.permissions?.includes('runtime:manage')) throw new Error('actor cannot manage runtime for this company');","typeGuard":"function canManageRuntime(actor) {\n  return typeof actor === 'object' && actor !== null && Array.isArray(actor.permissions) && actor.permissions.includes('runtime:manage');\n}","tryCatchPattern":"try {\n  const res = await fetch(url, { method: 'POST', headers });\n  if (res.status === 403) throw new AuthzError('runtime manage denied for this actor/company');\n  return await res.json();\n} catch (e) {\n  if (e instanceof AuthzError) { notifyAdminToGrantPermission(); return null; }\n  throw e;\n}","preventionTips":["Check the actor's runtime:manage grant before issuing runtime commands.","Keep agent API keys scoped to their own company only.","Re-check permissions after role changes on the board.","Fall back to a board-authorized service account for runtime operations."],"tags":["authorization","http-403","access-control","runtime"],"backgroundTag":"permission-denied","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}