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
- Re-sign the assertion with alg EdDSA and typ set to CLOUD_RUNTIME_IDENTITY_JWS_TYPE (check the constant in this file)
- Always include a non-empty kid in the JOSE header matching a JWKS entry
- Inspect the decoded header (base64url-decode segment 1) to see what the issuer actually put there
- 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
- Pin alg to EdDSA when signing; never allow algorithm negotiation or 'none'
- Set typ to the exact CLOUD_RUNTIME_IDENTITY_JWS_TYPE constant when minting
- Always include a non-empty kid that exists in the verifier JWKS
- Inspect headers of tokens from new issuers before wiring them into this flow
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
- PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is not configured
- Cloud runtime identity signature is invalid
- Cloud runtime identity claims are incomplete
- Access denied
- [plugin-kubernetes] egressMode=standard cannot enforce FQDN-
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/c67c7b664a2dc0a0.
Report an issue: GitHub.