paperclipai/paperclip · error

Invalid bridge base64 body.

Error message

Invalid bridge base64 body.

What it means

The bridge decoder does not trust Buffer.from(value, 'base64') because it is permissive and silently skips invalid characters. Before decoding, it validates strict base64 shape (length divisible by 4, only [A-Za-z0-9+/=], padding only as a final = or ==); after decoding, it round-trips (bytes.toString('base64') === value) to catch any residual ambiguity such as misplaced padding or non-canonical encodings. Either check failing throws 'Invalid bridge base64 body.'

Solutions

  1. Re-encode the body on the producer with Buffer.from(bytes).toString('base64') so it is canonical padded standard base64.
  2. Strip whitespace/newlines and convert URL-safe characters ('-' -> '+', '_' -> '/') before sending, or switch the producer to standard base64.
  3. Check for truncation: length must be a multiple of 4 with at most two trailing '=' characters.
  4. Diff the round-trip: compare Buffer.from(value, 'base64').toString('base64') with the original string locally to pinpoint the corruption.

Example fix

// before
const body = raw.toString("base64url").replace(/=+$/, ""); // non-canonical, unpadded
// after
const body = raw.toString("base64"); // canonical padded standard base64
const envelope = { body, bodyEncoding: "base64" };
Defensive patterns

Strategy: validation

Validate before calling

function isCanonicalBase64(value) {
  return typeof value === "string" && value.length % 4 === 0 &&
    /^[A-Za-z0-9+/]*={0,2}$/.test(value) &&
    Buffer.from(value, "base64").toString("base64") === value;
}
if (envelope.bodyEncoding === "base64" && !isCanonicalBase64(envelope.body)) throw new Error("body must be canonical padded base64");

Type guard

function isCanonicalBase64(value: unknown): value is string {
  return typeof value === "string" && value.length % 4 === 0 &&
    /^[A-Za-z0-9+/]*={0,2}$/.test(value) &&
    Buffer.from(value, "base64").toString("base64") === value;
}

Try / catch

try {
  const bytes = decodeSandboxBridgeBody(envelope, maxBodyBytes);
} catch (err) {
  if (err instanceof Error && err.message === "Invalid bridge base64 body.") {
    throw new Error("Producer sent malformed base64; re-encode with Buffer.toString('base64')");
  } else throw err;
}

Prevention

When it happens

Trigger: bodyEncoding is 'base64' and the body string: has length not divisible by 4; contains characters outside the base64 alphabet; has padding characters not exactly at the end (0, 1, or 2 trailing '=' allowed); or decodes to bytes whose re-encoding does not reproduce the original string (non-canonical encoding, misplaced '=').

Common situations: A producer truncates a base64 string mid-encoding; URL-safe base64 ('-' and '_') sent instead of standard base64; padding stripped by a JSON layer or manual string manipulation; whitespace/newlines embedded in the base64 body; double-encoding or corrupting the payload during transport.

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


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/4bc31e1e6f0a4a53. Report an issue: GitHub.

Appendix: source

Thrown at packages/adapter-utils/src/sandbox-callback-bridge-body.ts:29

export function encodeSandboxBridgeBody(body: string | Buffer, maxBodyBytes: number): SandboxCallbackBridgeBody {
  if (Buffer.byteLength(body) > maxBodyBytes) throw new Error("Bridge body exceeded the configured size limit.");
  return Buffer.isBuffer(body) ? { body: body.toString("base64"), bodyEncoding: "base64" } : { body };
}

/** Self-contained so the same decoder can be embedded in the remote gateway. */
export function decodeSandboxBridgeBody(envelope: SandboxCallbackBridgeBody, maxBodyBytes: number): Buffer {
  if (!envelope || typeof envelope.body !== "string") throw new Error("Invalid bridge body.");
  if (envelope.bodyEncoding === undefined || envelope.bodyEncoding === "utf8") {
    if (Buffer.byteLength(envelope.body, "utf8") > maxBodyBytes) throw new Error("Bridge body exceeded the configured size limit.");
    return Buffer.from(envelope.body, "utf8");
  }
  if (envelope.bodyEncoding !== "base64") throw new Error("Unsupported bridge body encoding.");
  const value = envelope.body;
  if (value.length > 4 * Math.ceil(maxBodyBytes / 3)) throw new Error("Bridge body exceeded the configured size limit.");
  // Buffer.from is permissive; reject malformed input before allocating bytes.
  if (value.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(value) || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {
    throw new Error("Invalid bridge base64 body.");
  }
  const bytes = Buffer.from(value, "base64");
  if (bytes.length > maxBodyBytes) throw new Error("Bridge body exceeded the configured size limit.");
  if (bytes.toString("base64") !== value) throw new Error("Invalid bridge base64 body.");
  return bytes;
}

export function sandboxBridgeBodyCodecSource(): string {
  return [sandboxBridgeEnvelopeLimit, encodeSandboxBridgeBody, decodeSandboxBridgeBody]
    .map(fn => `const ${fn.name} = ${fn.toString()};`).join("\n");
}

View on GitHub (pinned to 3f1d897a7c)