paperclipai/paperclip · error

Cloud runtime identity protected header is invalid

Error message

Cloud runtime identity protected header is invalid

What it means

Thrown by verifyClaims when the decoded protected header is present but does not meet the required shape: alg must be exactly 'EdDSA', typ must equal CLOUD_RUNTIME_IDENTITY_JWS_TYPE, and kid must be a non-empty string. This prevents algorithm-confusion attacks and ensures the right key can be selected.

Source

Thrown at server/src/services/cloud-runtime-identity.ts:277

function verifyClaims(input: {
  compactJws: string;
  env: NodeJS.ProcessEnv;
  now: Date;
}): RuntimeIdentityClaims {
  const parts = input.compactJws.split(".");
  if (parts.length !== 3 || parts.some((part) => part.length === 0)) {
    throw new Error("Cloud runtime identity assertion is not a compact JWS");
  }
  const [encodedHeader, encodedPayload, encodedSignature] = parts;
  const header = decodeJsonPart(encodedHeader, "protected header");
  if (
    header.alg !== "EdDSA"
    || header.typ !== CLOUD_RUNTIME_IDENTITY_JWS_TYPE
    || typeof header.kid !== "string"
    || !header.kid
  ) {
    throw new Error("Cloud runtime identity protected header is invalid");
  }
  const key = publicKeyForKid(input.env, header.kid);
  const signature = Buffer.from(encodedSignature, "base64url");
  const signingInput = Buffer.from(`${encodedHeader}.${encodedPayload}`, "ascii");
  if (!verify(null, signingInput, key, signature)) {
    throw new Error("Cloud runtime identity signature is invalid");
  }

  const payload = decodeJsonPart(encodedPayload, "payload");
  const nowSeconds = Math.floor(input.now.getTime() / 1000);
  if (
    payload.v !== 1
    || payload.iss !== CLOUD_RUNTIME_IDENTITY_ISSUER
    || payload.aud !== CLOUD_RUNTIME_IDENTITY_AUDIENCE
    || typeof payload.sub !== "string"
    || typeof payload.claimId !== "string"
    || typeof payload.previousOrigin !== "string"
    || typeof payload.canonicalOrigin !== "string"

View on GitHub (pinned to 01ad858492)

Solutions

  1. Re-sign the assertion with alg EdDSA and typ set to CLOUD_RUNTIME_IDENTITY_JWS_TYPE (check the constant in this file)
  2. Always include a non-empty kid in the JOSE header matching a JWKS entry
  3. Inspect the decoded header (base64url-decode segment 1) to see what the issuer actually put there
  4. Use the official control-plane signing tooling instead of a generic JWT library

Example fix

// before
const header = { alg: "none", kid };
// after
const header = { alg: "EdDSA", typ: CLOUD_RUNTIME_IDENTITY_JWS_TYPE, kid: "k1" };
Defensive patterns

Strategy: validation

Validate before calling

function hasValidJwsHeader(token: string, expectedTyp: string): boolean {
  try {
    const h = JSON.parse(Buffer.from(token.split(".")[0], "base64url").toString("utf8")) as Record<string, unknown>;
    return h.alg === "EdDSA" && h.typ === expectedTyp && typeof h.kid === "string" && h.kid.length > 0;
  } catch { return false; }
}

Try / catch

try {
  verifyCloudRuntimeIdentityAssertion({ compactJws: assertion, expectedPreviousOrigin: prev });
} catch (e) {
  if (String((e as Error).message).includes("protected header is invalid")) {
    logger.error("Assertion header alg/typ/kid mismatch — re-mint with EdDSA and the protocol typ", { error: e });
  } else throw e;
}

Prevention

When it happens

Trigger: decodeJsonPart succeeded on the header, but header.alg is missing or not 'EdDSA' (e.g. 'RS256', 'none'), header.typ differs from the expected type constant, or header.kid is absent/empty/not a string.

Common situations: Token minted by a different JWT library with default alg/typ values; alg accidentally downgraded to 'none' or HS256; kid omitted when signing; assertion generated for a different token type/audience by a sibling service.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/c67c7b664a2dc0a0. Report an issue: GitHub.