{"record":{"id":"beb002c934feacf3","repo":"paperclipai/paperclip","slug":"cloud-runtime-identity-signature-is-invalid","errorCode":null,"errorMessage":"Cloud runtime identity signature is invalid","messagePattern":"Cloud runtime identity signature is invalid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/cloud-runtime-identity.ts","lineNumber":283,"sourceCode":"  const parts = input.compactJws.split(\".\");\n  if (parts.length !== 3 || parts.some((part) => part.length === 0)) {\n    throw new Error(\"Cloud runtime identity 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_RUNTIME_IDENTITY_JWS_TYPE\n    || typeof header.kid !== \"string\"\n    || !header.kid\n  ) {\n    throw new Error(\"Cloud runtime identity protected header is invalid\");\n  }\n  const key = publicKeyForKid(input.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 runtime identity signature is invalid\");\n  }\n\n  const payload = decodeJsonPart(encodedPayload, \"payload\");\n  const nowSeconds = Math.floor(input.now.getTime() / 1000);\n  if (\n    payload.v !== 1\n    || payload.iss !== CLOUD_RUNTIME_IDENTITY_ISSUER\n    || payload.aud !== CLOUD_RUNTIME_IDENTITY_AUDIENCE\n    || typeof payload.sub !== \"string\"\n    || typeof payload.claimId !== \"string\"\n    || typeof payload.previousOrigin !== \"string\"\n    || typeof payload.canonicalOrigin !== \"string\"\n    || typeof payload.stackSlug !== \"string\"\n    || typeof payload.iat !== \"number\"\n    || !Number.isInteger(payload.iat)\n    || typeof payload.exp !== \"number\"\n    || !Number.isInteger(payload.exp)\n  ) {","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/cloud-runtime-identity.ts#L265-L301","documentation":"Thrown by verifyClaims when the Ed25519 signature over header.payload does not verify against the public key selected by kid from the JWKS. node:crypto's verify() returned false, meaning the signed bytes were altered, signed with a different key, or the signature bytes were corrupted.","triggerScenarios":"The signingInput (encodedHeader + '.' + encodedPayload) differs from what was signed (payload edited, whitespace inserted, re-encoding changed), the token was signed with a private key not corresponding to the JWKS entry for header.kid, or the signature segment was truncated/mangled in transit.","commonSituations":"Manually editing claims (e.g. extending exp) without re-signing; signing with the wrong environment's private key; token passed through a proxy that rewrites case or padding; mixing base64 and base64url encodings when re-serializing.","solutions":["Re-issue the assertion from the Cloud control plane; never modify any segment of a signed token","Ensure the signing key corresponds to the public key published in PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS under the same kid","Transmit the token verbatim (no trimming/re-encoding beyond whitespace) — do not normalize case or padding","Verify the token with the same environment's JWKS (staging token against staging JWKS)"],"exampleFix":"// before: editing payload without re-signing\nconst tampered = parts[0] + \".\" + b64url({...claims, exp: 9999999999}) + \".\" + parts[2];\n// after: re-mint the assertion server-side with the private key\nconst fresh = await controlPlaneClient.mintIdentityAssertion(stackId);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  verifyCloudRuntimeIdentityAssertion({ compactJws: assertion, expectedPreviousOrigin: prev });\n} catch (e) {\n  if (String((e as Error).message).includes(\"signature is invalid\")) {\n    logger.warn(\"Identity assertion signature check failed; fetching a fresh assertion\", { error: e });\n    const fresh = await controlPlaneClient.mintIdentityAssertion(stackId); // retry once with a new token\n  } else throw e;\n}","preventionTips":["Never mutate any segment of a signed token (including 'harmless' claim edits) — always re-sign","Ensure signer private key and verifier JWKS are a matched pair per kid and environment","Transmit tokens through channels that don't rewrite content (avoid HTML encoding, line wrapping)","Keep a single canonical base64url implementation for mint and verify"],"tags":["jwt","signature-verification","ed25519","security"],"backgroundTag":"checksum-mismatch","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"}