{"record":{"id":"84ab0de872ace629","repo":"paperclipai/paperclip","slug":"cloud-control-signature-is-invalid","errorCode":null,"errorMessage":"Cloud control signature is invalid","messagePattern":"Cloud control signature is invalid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/cloud-runtime-identity.ts","lineNumber":528,"sourceCode":"  const parts = input.compactJws.split(\".\");\n  if (parts.length !== 3 || parts.some((part) => part.length === 0)) {\n    throw new Error(\"Cloud control assertion is not a compact JWS\");\n  }\n  const [encodedHeader, encodedPayload, encodedSignature] = parts;\n  const header = decodeJsonPart(encodedHeader, \"protected header\");\n  if (\n    header.alg !== \"EdDSA\"\n    || header.typ !== CLOUD_CONTROL_JWS_TYPE\n    || typeof header.kid !== \"string\"\n    || !header.kid\n  ) {\n    throw new Error(\"Cloud control protected header is invalid\");\n  }\n  const key = publicKeyForKid(env, header.kid);\n  const signature = Buffer.from(encodedSignature, \"base64url\");\n  const signingInput = Buffer.from(`${encodedHeader}.${encodedPayload}`, \"ascii\");\n  if (!verify(null, signingInput, key, signature)) {\n    throw new Error(\"Cloud control signature is invalid\");\n  }\n\n  const payload = decodeJsonPart(encodedPayload, \"payload\");\n  const configuredStackId = nonEmpty(env.PAPERCLIP_CLOUD_STACK_ID);\n  const nowSeconds = Math.floor(now.getTime() / 1000);\n  if (\n    payload.v !== 1\n    || payload.iss !== CLOUD_RUNTIME_IDENTITY_ISSUER\n    || payload.aud !== CLOUD_CONTROL_AUDIENCE\n    || typeof payload.sub !== \"string\"\n    || typeof payload.action !== \"string\"\n    || typeof payload.requestId !== \"string\"\n    || typeof payload.iat !== \"number\"\n    || !Number.isInteger(payload.iat)\n    || typeof payload.exp !== \"number\"\n    || !Number.isInteger(payload.exp)\n  ) {\n    throw new Error(\"Cloud control claims are incomplete\");","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/cloud-runtime-identity.ts#L510-L546","documentation":"verifyCloudControlAssertion verifies the Ed25519 signature over signingInput (\"header.payload\" ASCII) using the public key selected by the header's kid from the PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS env configuration. This error means node:crypto's verify() returned false: the signature bytes do not match that key over that exact signing input.","triggerScenarios":"The assertion was signed with a private key whose public half is not the JWKS entry for the given kid; the payload or header was modified after signing; the signature was re-encoded (e.g. base64 vs base64url) or truncated; signing happened over a different serialization (pretty-printed JSON, different key order).","commonSituations":"JWKS env var points at a rotated key set while clients still sign with the old private key; the kid matches but the key material was copy-pasted incorrectly; a debug build re-serialized the payload after signing; clock-shifted test harness reuses a captured token with edited claims.","solutions":["Confirm the private key used to sign matches the public JWK published under the same kid in PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS","Sign the exact bytes `${encodedHeader}.${encodedPayload}` of the final compact token, and never mutate header/payload after signing","Regenerate/rotate the JWKS entry so it contains the public half of the currently deployed signing key, and restart the instance","Verify signature encoding is base64url (no '+', '/', or '=') and that signing used Ed25519/EdDSA"],"exampleFix":"// before\nconst sig = Buffer.from(crypto.sign(null, data, privateKey)).toString(\"base64\");\n// after\nconst sig = Buffer.from(crypto.sign(null, Buffer.from(`${encodedHeader}.${encodedPayload}`, \"ascii\"), privateKey))\n  .toString(\"base64url\");\nconst jws = `${encodedHeader}.${encodedPayload}.${sig.toString()}`;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return verifyCloudControlAssertion({ compactJws: token, expectedAction });\n} catch (e) {\n  if (e.message === \"Cloud control signature is invalid\") {\n    // signature mismatch: token was modified or signed with an unknown key — fail closed, never retry same token\n    logger.warn(\"cloud control signature verification failed\", { kid: peekKid(token) });\n    return respond(401, \"invalid signature\");\n  }\n  throw e;\n}","preventionTips":["Keep PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS in sync with the deployed signing private key; rotate both together","Sign the exact final \"header.payload\" bytes; never re-serialize claims after signing","Use base64url encoding end-to-end; avoid base64 in signature output","Add a self-test: sign then verify a canary assertion at deploy time"],"tags":["jws","signature","security","authentication","crypto"],"backgroundTag":"checksum-mismatch","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"}