{"record":{"id":"078f4c0f8423e1f1","repo":"JuliusBrussee/caveman","slug":"runtimepolicy-publickey-must-be-a-base64-encoded-3","errorCode":null,"errorMessage":"runtimePolicy publicKey must be a base64-encoded 32-byte Ed25519 key","messagePattern":"runtimePolicy publicKey must be a base64-encoded 32-byte Ed25519 key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":1082,"sourceCode":" * failure. `decide()` is synchronous, local-only, and never throws — with no\n * bundle it returns the caller's baseline. Mirrors the Python\n * `RuntimePolicyClient`.\n */\nexport class RuntimePolicyClient {\n  private bundle: Record<string, unknown> | null = null;\n  private signedBundle = false;\n  private pinnedKey: Uint8Array | null = null;\n  private killedLocally = false;\n  private timer: ReturnType<typeof setInterval> | null = null;\n  /** True while a `refresh()` is in flight; the background timer skips rather than pile on. */\n  private refreshing = false;\n  private readonly killEnv: string;\n\n  constructor(private cave: Cave, options: RuntimePolicyOptions = {}) {\n    this.killEnv = options.killEnv ?? \"CAVEMAN_POLICY_KILL\";\n    if (options.publicKey !== undefined) {\n      const pin = base64Bytes(options.publicKey);\n      if (pin === null || pin.length !== 32) throw new Error(\"runtimePolicy publicKey must be a base64-encoded 32-byte Ed25519 key\");\n      this.pinnedKey = pin;\n    }\n    const seconds = options.autoRefreshSeconds;\n    if (typeof seconds === \"number\" && Number.isFinite(seconds) && seconds > 0) {\n      const timer = setInterval(() => {\n        // A slow or hung endpoint must not queue refreshes behind each other:\n        // one tick while a refresh is already in flight is skipped, not stacked.\n        if (this.refreshing) return;\n        void this.refresh();\n      }, seconds * 1000);\n      (timer as unknown as { unref?: () => void }).unref?.();\n      this.timer = timer;\n    }\n  }\n\n  /**\n   * Fetch and verify the current bundle (`GET /sdk/v1/runtime-policy`).\n   *","sourceCodeStart":1064,"sourceCodeEnd":1100,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L1064-L1100","documentation":"The RuntimePolicy constructor validates options.publicKey by decoding it as base64 and requiring exactly 32 bytes (an Ed25519 public key size). Anything else — invalid base64 or wrong length — throws this configuration error before the client is usable.","triggerScenarios":"Passing publicKey that is not valid base64, is base64 of a non-32-byte key (e.g. hex string, PEM, 64-byte private key, 65-byte uncompressed point), or a URL-safe base64 variant with -/_ characters.","commonSituations":"Copying a hex-encoded key instead of base64; pasting the Ed25519 private/seed key (64 bytes) instead of the public key; using a PEM-wrapped key; URL-safe base64 from another tool.","solutions":["Base64-encode the raw 32-byte Ed25519 public key (standard alphabet, padding allowed)","Ensure you export the public key, not the private key or seed","Convert hex to raw bytes then base64: Buffer.from(hexKey,'hex').toString('base64')","Convert URL-safe base64 to standard by replacing -/_ with +/"],"exampleFix":"// before\nnew RuntimePolicyEnforcer(cave, { publicKey: \"9d61b19deffd5a60ba844af492ec2cc4\" }); // hex, 16 bytes\n// after\nnew RuntimePolicyEnforcer(cave, { publicKey: \"nWGJne/9WmC6hEr0kuwsxMA5G0zmGToLWM0vB1X0N60=\" }); // base64, 32 bytes","handlingStrategy":"validation","validationCode":"function isValidPinnedKey(key: string): boolean {\n  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(key)) return false;\n  const raw = Buffer.from(key, \"base64\");\n  return raw.length === 32;\n}\nif (!isValidPinnedKey(opts.publicKey)) throw new Error(\"publicKey must be base64 of a 32-byte Ed25519 public key\");","typeGuard":"function isBase64Of32Bytes(v: unknown): v is string {\n  return typeof v === \"string\" && /^([A-Za-z0-9+/]{43}=?|)$/.test(v) && Buffer.from(v, \"base64\").length === 32;\n}","tryCatchPattern":"try {\n  const enforcer = new RuntimePolicyEnforcer(cave, { publicKey });\n} catch (e) {\n  if (/publicKey/.test(e.message)) dieWithConfigHint(\"Invalid CAVEMAN_POLICY_PUBLIC_KEY: expected base64 32-byte Ed25519 key\");\n  else throw e;\n}","preventionTips":["Store the key as standard base64 of the raw 32-byte public key","Never paste hex, PEM, or the private/seed key","Validate env-provided keys at startup with Buffer.from(key,'base64').length === 32"],"tags":["configuration","crypto","validation","ed25519"],"backgroundTag":"invalid-config-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}