paperclipai/paperclip · error
Cloud control protected header is invalid
Error message
Cloud control protected header is invalid
What it means
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.
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
Example fix
// before
const header = { alg: "RS256", kid };
// after
const header = { alg: "EdDSA", typ: CLOUD_CONTROL_JWS_TYPE, kid };
const jws = `${b64url(JSON.stringify(header))}.${b64url(payload)}.${sig}`; Defensive patterns
Strategy: validation
Validate before calling
function headerLooksValid(token) {
try {
const h = JSON.parse(Buffer.from(token.split(".")[0], "base64url").toString("utf8"));
return h.alg === "EdDSA" && typeof h.kid === "string" && h.kid.length > 0 && typeof h.typ === "string" && h.typ.length > 0;
} catch { return false; }
}
if (!headerLooksValid(assertion)) throw new Error("assertion protected header fails local pre-check"); Type guard
const isCloudControlHeader = (h: unknown): h is { alg: "EdDSA"; typ: string; kid: string } =>
!!h && typeof h === "object" && (h as any).alg === "EdDSA" && typeof (h as any).kid === "string" && !!(h as any).kid; Try / catch
try {
return verifyCloudControlAssertion({ compactJws: token, expectedAction });
} catch (e) {
if (e.message === "Cloud control protected header is invalid") {
// decode header locally (without verifying) to diagnose alg/typ/kid, then re-mint
logger.warn("assertion rejected: bad protected header");
return respond(401, "invalid assertion header");
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Cloud control assertion is not a compact JWS
- Cloud control signature is invalid
- Cloud control assertion does not authorize this action
- Cloud control assertion is expired or has an invalid…
- Cloud control assertion request id is invalid
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/03dae5f8c8ab0097.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/cloud-runtime-identity.ts:522
expectedAction: CloudControlAction;
env?: NodeJS.ProcessEnv;
now?: Date;
}): CloudControlClaims {
const env = input.env ?? process.env;
const now = input.now ?? new Date();
const parts = input.compactJws.split(".");
if (parts.length !== 3 || parts.some((part) => part.length === 0)) {
throw new Error("Cloud control assertion is not a compact JWS");
}
const [encodedHeader, encodedPayload, encodedSignature] = parts;
const header = decodeJsonPart(encodedHeader, "protected header");
if (
header.alg !== "EdDSA"
|| header.typ !== CLOUD_CONTROL_JWS_TYPE
|| typeof header.kid !== "string"
|| !header.kid
) {
throw new Error("Cloud control protected header is invalid");
}
const key = publicKeyForKid(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 control signature is invalid");
}
const payload = decodeJsonPart(encodedPayload, "payload");
const configuredStackId = nonEmpty(env.PAPERCLIP_CLOUD_STACK_ID);
const nowSeconds = Math.floor(now.getTime() / 1000);
if (
payload.v !== 1
|| payload.iss !== CLOUD_RUNTIME_IDENTITY_ISSUER
|| payload.aud !== CLOUD_CONTROL_AUDIENCE
|| typeof payload.sub !== "string"
|| typeof payload.action !== "string"
|| typeof payload.requestId !== "string"View on GitHub (pinned to 3f1d897a7c)