{"record":{"id":"5f8bd21fe44d5f87","repo":"paperclipai/paperclip","slug":"cloud-control-assertion-request-id-is-invalid","errorCode":null,"errorMessage":"Cloud control assertion request id is invalid","messagePattern":"Cloud control assertion request id is invalid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/cloud-runtime-identity.ts","lineNumber":562,"sourceCode":"    || !Number.isInteger(payload.exp)\n  ) {\n    throw new Error(\"Cloud control claims are incomplete\");\n  }\n  if (!configuredStackId || payload.sub !== configuredStackId) {\n    throw new Error(\"Cloud control assertion stack does not match this instance\");\n  }\n  if (\n    !(CLOUD_CONTROL_ACTIONS as readonly string[]).includes(payload.action)\n    || payload.action !== input.expectedAction\n  ) {\n    throw new Error(\"Cloud control assertion does not authorize this action\");\n  }\n  if (\n    !payload.requestId\n    || payload.requestId.trim() !== payload.requestId\n    || payload.requestId.length > 256\n  ) {\n    throw new Error(\"Cloud control assertion request id is invalid\");\n  }\n  if (\n    payload.exp <= nowSeconds\n    || payload.iat > nowSeconds + MAX_CLOCK_SKEW_SECONDS\n    || payload.exp <= payload.iat\n    || payload.exp - payload.iat > CLOUD_CONTROL_MAX_LIFETIME_SECONDS\n  ) {\n    throw new Error(\"Cloud control assertion is expired or has an invalid lifetime\");\n  }\n  // Consumed LAST, only after every other check passed: a rejected\n  // assertion must not burn its request id, or an attacker could deny a\n  // legitimate call by replaying a mangled copy of it first.\n  if (!consumeControlRequestId(payload.requestId, payload.exp + MAX_CLOCK_SKEW_SECONDS, now.getTime())) {\n    throw new Error(\"Cloud control assertion has already been used\");\n  }\n  return payload as CloudControlClaims;\n}\n","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/cloud-runtime-identity.ts#L544-L580","documentation":"verifyCloudControlAssertion validates the requestId claim for replay protection: it must be a non-empty string, contain no leading/trailing whitespace, and be at most 256 characters. The requestId is later consumed idempotently (see the consumeControlRequestId call after the expiry check), so it must be a stable, unique identifier.","triggerScenarios":"A minted assertion with an empty requestId (\"\") or whitespace-only value; requestId built by naive string concatenation that introduced leading/trailing spaces; an oversized identifier (e.g. a full URL or concatenated trace id exceeding 256 chars) placed in the claim.","commonSituations":"Template interpolation like `req ${id}` leaving a trailing space; using a long JWT or URL as the requestId; a client generating undefined/stringified-object request ids (\"undefined\", \"[object Object]\") under error conditions.","solutions":["Generate requestId as a short unique identifier — crypto.randomUUID() or an id ≤ 256 chars with no surrounding whitespace","Trim or validate the id at the issuer before signing; never interpolate it into templates with extra spaces","Fix fallback paths that stringify undefined/null values into the requestId claim","Add an issuer-side assertion mirroring the verifier's checks so bad ids fail at mint time"],"exampleFix":"// before\nconst requestId = `control ${operation}-${Date.now()} `;\n// after\nconst requestId = crypto.randomUUID();\nif (!requestId || requestId.trim() !== requestId || requestId.length > 256) throw new Error(\"bad requestId\");","handlingStrategy":"validation","validationCode":"function isValidRequestId(id) {\n  return typeof id === \"string\" && id.length > 0 && id.trim() === id && id.length <= 256;\n}\nif (!isValidRequestId(requestId)) throw new Error(\"requestId must be non-empty, untrimmed-free, and <=256 chars\");","typeGuard":"const isRequestId = (v: unknown): v is string =>\n  typeof v === \"string\" && v.length > 0 && v.trim() === v && v.length <= 256;","tryCatchPattern":null,"preventionTips":["Always generate requestId via crypto.randomUUID() or equivalent bounded unique id","Validate at mint time with the same rules the verifier enforces","Never build request ids via string interpolation that can add whitespace or stringify undefined"],"tags":["jws","validation","replay-protection","security"],"backgroundTag":"invalid-identifier-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}