{"record":{"id":"6fff53d894d659b8","repo":"paperclipai/paperclip","slug":"cloud-control-wrong-endpoint","errorCode":"cloud_control_wrong_endpoint","errorMessage":"cloud_control_wrong_endpoint","messagePattern":"cloud_control_wrong_endpoint","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/src/middleware/cloud-control.ts","lineNumber":40,"sourceCode":" * is bound to exactly one method's action, and the header is rejected loudly\n * anywhere else so it can never become an ambient credential. Instances\n * without a Cloud stack identity reject every assertion. The browser-facing\n * Cloud proxy strips this header, and possession of the shared tenant-session\n * token cannot mint it.\n */\nexport function cloudControlMiddleware(): RequestHandler {\n  return (req, res, next) => {\n    const assertion = req.get(CLOUD_CONTROL_HEADER)?.trim();\n    if (!assertion) {\n      next();\n      return;\n    }\n    const expectedAction = ACTION_BY_METHOD[req.method];\n    // Express's non-strict routing treats a trailing slash as the same\n    // route; the endpoint check must agree with it.\n    const normalizedPath = req.path.length > 1 && req.path.endsWith(\"/\") ? req.path.slice(0, -1) : req.path;\n    if (normalizedPath !== \"/api/instance/task-drain\" || !expectedAction) {\n      res.status(400).json({ error: \"cloud_control_wrong_endpoint\" });\n      return;\n    }\n    try {\n      verifyCloudControlAssertion({ compactJws: assertion, expectedAction });\n    } catch (error) {\n      logger.warn({ err: error }, \"Rejected Cloud control assertion\");\n      res.status(401).json({ error: \"invalid_cloud_control_assertion\" });\n      return;\n    }\n    req.actor = {\n      type: \"board\",\n      userId: \"paperclip-cloud\",\n      userName: \"Paperclip Cloud\",\n      userEmail: null,\n      isInstanceAdmin: true,\n      source: \"cloud_control\",\n    };\n    next();","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/middleware/cloud-control.ts#L22-L58","documentation":"cloudControlMiddleware authenticates Paperclip Cloud control-plane assertions (signed JWS). Before verifying the signature it confirms the request actually targets /api/instance/task-drain with the method-appropriate action; a trailing slash is normalized so it matches Express non-strict routing. Any other path or missing action is rejected with 400 and code cloud_control_wrong_endpoint without attempting JWS verification.","triggerScenarios":"Paperclip Cloud (or a test client) sends an assertion-bearing request to any path other than /api/instance/task-drain (after trailing-slash normalization), or uses an HTTP method that has no entry in ACTION_BY_METHOD (e.g. DELETE/PUT).","commonSituations":"Cloud-side routing misconfiguration pointing assertions at the wrong instance endpoint; adding a new cloud-control endpoint but not registering its method in ACTION_BY_METHOD; an old cloud build calling a retired endpoint path.","solutions":["Point the cloud control request at POST/GET /api/instance/task-drain exactly (a trailing slash is tolerated)","Use an HTTP method that maps to an expected action (currently the drain endpoint's configured methods)","If adding a new cloud-control endpoint, extend ACTION_BY_METHOD with the method→action mapping","Check the cloud orchestrator's endpoint configuration for typos or stale base paths"],"exampleFix":"// before\nawait fetch(base + \"/api/instance/tasks/drain\", { method: \"POST\", headers: { assertion } });\n// after\nawait fetch(base + \"/api/instance/task-drain\", { method: \"POST\", headers: { assertion } });","handlingStrategy":"validation","validationCode":"const path = new URL(url).pathname.replace(/\\/$/, \"\");\nconst allowed = { \"/api/instance/task-drain\": new Set([\"POST\", \"GET\"]) };\nif (!(path in allowed) || !allowed[path].has(method)) {\n  throw new Error(`cloud control endpoint must be /api/instance/task-drain, got ${method} ${path}`);\n}","typeGuard":null,"tryCatchPattern":"const res = await sendCloudControl(request);\nif (res.status === 400 && res.body?.error === \"cloud_control_wrong_endpoint\") {\n  fixEndpointConfiguration(); // align path/method with ACTION_BY_METHOD\n}","preventionTips":["Hardcode /api/instance/task-drain in the cloud orchestrator config","Keep method→action mappings in sync when adding new cloud-control endpoints","Normalize trailing slashes on both client and server","Smoke-test cloud assertions against a dev instance after endpoint changes"],"tags":["http","middleware","routing","cloud-control"],"backgroundTag":"invalid-url-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}