paperclipai/paperclip · error

PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid

Error message

PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid

What it means

Thrown by publicKeyForKid when PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is set but its value is not parseable as JSON, or (same message reused at line 246) when the parsed JSON has no 'keys' array. The env var must contain a JWKS document: a JSON object like {"keys":[...]}.

Source

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

  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);
  } catch {
    throw new Error("PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid");
  }
  const keys = parsed && typeof parsed === "object" && !Array.isArray(parsed)
    ? (parsed as { keys?: unknown }).keys
    : undefined;
  if (!Array.isArray(keys)) throw new Error("PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS is invalid");
  const matches = keys.filter((candidate): candidate is JsonWebKey & { kid: string } => {
    if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
    const key = candidate as JsonWebKey & { kid?: unknown };
    return key.kid === kid;
  });
  if (matches.length !== 1) throw new Error("Cloud runtime identity uses an unknown signing key");
  const jwk = matches[0];
  if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || jwk.use !== "sig" || jwk.alg !== "EdDSA" || !jwk.x || jwk.d) {
    throw new Error("Cloud runtime identity signing key is invalid");
  }
  return createPublicKey({ key: jwk, format: "jwk" });
}

View on GitHub (pinned to 01ad858492)

Solutions

  1. Log or echo the env var value and run it through JSON.parse locally to see the parse error
  2. Ensure the value is a complete JWKS document with a top-level 'keys' array: {"keys":[{"kty":"OKP",...}]}
  3. Fix shell/manifest quoting so quotes survive (single-quote the value in shell, use a k8s secret not an inline literal)
  4. Confirm the control-plane JWKS endpoint output is copied verbatim, not re-encoded

Example fix

// before: bare key, no keys wrapper
PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS='{"kty":"OKP","crv":"Ed25519",...}'
// after: proper JWKS
PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS='{"keys":[{"kty":"OKP","crv":"Ed25519","use":"sig","alg":"EdDSA","kid":"...","x":"..."}]}'
Defensive patterns

Strategy: validation

Validate before calling

function jwksIsParseable(raw: string | undefined): boolean {
  if (!raw) return false;
  try {
    const parsed = JSON.parse(raw);
    return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && Array.isArray((parsed as { keys?: unknown }).keys);
  } catch { return false; }
}
// run at startup: if (!jwksIsParseable(process.env.PAPERCLIP_CLOUD_RUNTIME_IDENTITY_JWKS)) failFast();

Try / catch

try {
  verifyCloudRuntimeIdentityAssertion({ compactJws: assertion, expectedPreviousOrigin: prev });
} catch (e) {
  if (String((e as Error).message).includes("JWKS is invalid")) {
    logger.error("Configured JWKS is not valid JSON with a keys array; check quoting/encoding of the env var", { error: e });
  } else throw e;
}

Prevention

When it happens

Trigger: JSON.parse of the env var value throws (line 241), or the parsed value is not an object, is an array, or its 'keys' property is not an array (line 246). Both raise the identical message.

Common situations: The JWKS was pasted into the env var with shell quoting damage (escaped quotes consumed, JSON truncated); a bare key object {"kty":...} was configured instead of a wrapping JWKS {"keys":[...]}; the value was stored as base64 instead of raw JSON; YAML/k8s stringification mangled newlines or quotes.

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/6bb234b09a33ebaf. Report an issue: GitHub.