paperclipai/paperclip · error

Cloud runtime identity has an invalid ${label}

Error message

Cloud runtime identity has an invalid ${label}

What it means

decodeJsonPart decodes one base64url-delimited part of the Cloud runtime identity compact JWS (protected header or payload). It throws this error when the part contains characters outside [A-Za-z0-9_-] (i.e. is not valid base64url), when base64url decoding plus JSON.parse fails, or — as here at line 221 — before decoding, when the pre-parse charset check fails. The label in the message is 'protected header' or 'payload' depending on the caller.

Source

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

  if (env === process.env && currentIdentity) return currentIdentity.canonicalOrigin;
  const candidate = nonEmpty(env.PAPERCLIP_PUBLIC_URL)
    ?? nonEmpty(env.PAPERCLIP_AUTH_PUBLIC_BASE_URL)
    ?? nonEmpty(env.PAPERCLIP_API_URL);
  if (!candidate) return null;
  try {
    return new URL(candidate).origin;
  } catch {
    return null;
  }
}

/** The asserted Cloud origin only. Callers retain their non-Cloud precedence. */
export function runtimeCanonicalOrigin(): string | null {
  return currentIdentity?.canonicalOrigin ?? null;
}

function decodeJsonPart(part: string, label: string): Record<string, unknown> {
  if (!/^[A-Za-z0-9_-]+$/.test(part)) throw new Error(`Cloud runtime identity has an invalid ${label}`);
  let parsed: unknown;
  try {
    parsed = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
  } catch {
    throw new Error(`Cloud runtime identity has an invalid ${label}`);
  }
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error(`Cloud runtime identity has an invalid ${label}`);
  }
  return parsed as Record<string, unknown>;
}

function publicKeyForKid(env: NodeJS.ProcessEnv, kid: string) {
  const raw = nonEmpty(env.PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS);
  if (!raw) throw new Error("PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is not configured");
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Regenerate or re-fetch the runtime identity assertion from the Paperclip Cloud control plane instead of reusing a stored copy; use the token exactly as issued.
  2. Check that the string was not damaged in transit: shell-quote it, avoid line wrapping in YAML/env files, and confirm no truncation when copying.
  3. If you must construct the JWS yourself, base64url-encode (RFC 4648 §5, no padding) the JSON header and payload.
  4. Log/inspect the failing part and confirm it matches ^[A-Za-z0-9_-]+$ and JSON-decodes after base64url decoding before calling the API.

Example fix

// before (standard base64 with padding)
const part = Buffer.from(json).toString("base64"); // e.g. "eyJ...+g=="
// after (base64url, no padding)
const part = Buffer.from(json).toString("base64url");
Defensive patterns

Strategy: validation

Validate before calling

function isBase64UrlPart(part: string): boolean {
  return /^[A-Za-z0-9_-]+$/.test(part) &&
    (() => { try { JSON.parse(Buffer.from(part, "base64url").toString("utf8")); return true; } catch { return false; } })();
}
// before sending the assertion to the API:
const [h, p, s] = compactJws.split(".");
if (!h || !p || !s || !isBase64UrlPart(h) || !isBase64UrlPart(p)) {
  throw new Error("assertion is not a well-formed base64url compact JWS");
}

Type guard

function isWellFormedJws(token: string): boolean {
  const parts = token.split(".");
  if (parts.length !== 3 || parts.some((p) => !p)) return false;
  return parts.every((p) => /^[A-Za-z0-9_-]+$/.test(p));
}

Try / catch

try {
  verifyCloudRuntimeIdentityAssertion({ compactJws, expectedPreviousOrigin });
} catch (err) {
  if (err instanceof Error && err.message.includes("invalid protected header")) {
    // token damaged or wrongly encoded — re-fetch a fresh assertion from the control plane
  }
  throw err;
}

Prevention

When it happens

Trigger: verifyCloudRuntimeIdentityAssertion (via verifyClaims) is given a compactJws whose header or payload segment contains invalid base64url characters (e.g. '+' or '/' or '='), is not valid base64url encoding, or decodes to bytes that are not valid JSON.

Common situations: A caller passes a standard base64 JWT (with '+', '/', '=') instead of the base64url form; the assertion string was truncated or corrupted in transit, storage, or a shell command (quoting/line-wrap damage); someone pasted a URL-encoded or padded token; a test fixture was hand-crafted with a malformed segment.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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