{"record":{"id":"03dae5f8c8ab0097","repo":"paperclipai/paperclip","slug":"cloud-control-protected-header-is-invalid","errorCode":null,"errorMessage":"Cloud control protected header is invalid","messagePattern":"Cloud control protected header is invalid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/cloud-runtime-identity.ts","lineNumber":522,"sourceCode":"  expectedAction: CloudControlAction;\n  env?: NodeJS.ProcessEnv;\n  now?: Date;\n}): CloudControlClaims {\n  const env = input.env ?? process.env;\n  const now = input.now ?? new Date();\n  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\"","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/cloud-runtime-identity.ts#L504-L540","documentation":"After the JWS splits into three parts, verifyCloudControlAssertion decodes the protected header and requires alg === \"EdDSA\", typ equal to the expected cloud-control JWS type, and a non-empty string kid. This error means the header parsed as JSON but failed one of those constraints, so the assertion cannot be bound to a known Ed25519 signing key.","triggerScenarios":"A token signed with a different algorithm (e.g. RS256, HS256) or missing/wrong typ header; a header without a kid or with an empty kid; a hand-rolled signer omitting required header fields.","commonSituations":"A client library defaults to a different JWT algorithm; the signing side was updated to a new token type without updating the assertion issuer; a generic JWT library was used instead of the cloud control assertion signer; token was issued by an older/other environment with different header conventions.","solutions":["Inspect the decoded protected header of the token (base64url-decode part 1) and confirm alg is exactly \"EdDSA\", typ matches CLOUD_CONTROL_JWS_TYPE, and kid is a non-empty string","Fix the token issuer to set { alg: \"EdDSA\", typ: <CLOUD_CONTROL_JWS_TYPE>, kid: <keyId> } in the protected header","Verify the signer uses an Ed25519 (OKP) key, per the JWKS requirements in publicKeyForKid","Ensure the kid used to sign corresponds to an entry in PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS, otherwise the next check will also fail"],"exampleFix":"// before\nconst header = { alg: \"RS256\", kid };\n// after\nconst header = { alg: \"EdDSA\", typ: CLOUD_CONTROL_JWS_TYPE, kid };\nconst jws = `${b64url(JSON.stringify(header))}.${b64url(payload)}.${sig}`;","handlingStrategy":"validation","validationCode":"function headerLooksValid(token) {\n  try {\n    const h = JSON.parse(Buffer.from(token.split(\".\")[0], \"base64url\").toString(\"utf8\"));\n    return h.alg === \"EdDSA\" && typeof h.kid === \"string\" && h.kid.length > 0 && typeof h.typ === \"string\" && h.typ.length > 0;\n  } catch { return false; }\n}\nif (!headerLooksValid(assertion)) throw new Error(\"assertion protected header fails local pre-check\");","typeGuard":"const isCloudControlHeader = (h: unknown): h is { alg: \"EdDSA\"; typ: string; kid: string } =>\n  !!h && typeof h === \"object\" && (h as any).alg === \"EdDSA\" && typeof (h as any).kid === \"string\" && !!(h as any).kid;","tryCatchPattern":"try {\n  return verifyCloudControlAssertion({ compactJws: token, expectedAction });\n} catch (e) {\n  if (e.message === \"Cloud control protected header is invalid\") {\n    // decode header locally (without verifying) to diagnose alg/typ/kid, then re-mint\n    logger.warn(\"assertion rejected: bad protected header\");\n    return respond(401, \"invalid assertion header\");\n  }\n  throw e;\n}","preventionTips":["Always set { alg: \"EdDSA\", typ: CLOUD_CONTROL_JWS_TYPE, kid } in the protected header when signing","Use Ed25519 keys only; reject signer configs defaulting to RS256/HS256","Pin the assertion signer library/function so header fields cannot drift"],"tags":["jws","security","authentication","header-validation"],"backgroundTag":"invalid-argument-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"}