Hmbown/CodeWhale · error · Error
invalid-base64
invalid-base64
Error message
invalid-base64
What it means
b64ToBytes validates base64 input before decoding: the value must be a non-empty string, fit the maxBytes budget, have length divisible by 4, match the standard base64 alphabet with at most two padding '='s. It throws "invalid-base64" if any structural check fails, before atob allocates.
Solutions
- Re-encode the value with standard base64 (btoa/Buffer.toString('base64')), which pads to a multiple of 4.
- Convert URL-safe base64 to standard: replace '-' with '+' and '_' with '/', then pad with '=' to a multiple of 4.
- Trim surrounding whitespace and ensure no internal line breaks before passing the value.
- Check the decoded byte size is within maxBytes for the field (keys, signatures, envelopes have separate caps).
Example fix
// before const token = btoaUrlSafe(raw).replace(/=+$/, ""); // after const token = btoa(raw); // standard alphabet, padded, length % 4 === 0
Defensive patterns
Strategy: validation
Validate before calling
function isStandardBase64(v: unknown, maxLen: number): boolean {
return typeof v === "string" && v.length > 0 && v.length <= maxLen &&
v.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(v);
} Type guard
function isBase64String(v: unknown): v is string {
return typeof v === "string" && v.length > 0 && v.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(v);
} Try / catch
try {
const bytes = b64ToBytes(value, maxBytes);
} catch (err) {
if (err.message === "invalid-base64") {
// Re-encode or reject the input; do not retry the same value.
value = btoa(String(value).replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, ""));
}
} Prevention
- Always use standard (padded) base64 encoders, not base64url, for values sent to this library.
- Strip whitespace/newlines from base64 strings before transmitting or storing them.
- Prefer btoa/Buffer.toString('base64') over hand-built encoders so padding is correct.
When it happens
Trigger: Passing a non-string, empty string, URL-safe base64 (using - and _), base64 without padding, whitespace/newlines inside the value, or a string longer than 4*ceil(maxBytes/3) to any consumer of b64ToBytes (hasActiveKeys, validSignature, verifyEnvelope, publicKey).
Common situations: Generating tokens with base64url (JWT-style) encoders; hand-trimming or concatenating base64 chunks; copying values with trailing whitespace or quotes; oversized values from upstream.
Related errors
- invalid base64
- image base64 is not canonical
- image has invalid base64
- invalid tool image evidence
- operation_key cannot contain control characters
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/44e1d9c75bfa3dfb.
Report an issue: GitHub.
Appendix: source
Thrown at web/lib/cloud-facts.ts:95
fetchImpl?: typeof fetch;
keys?: readonly TrustedKey[];
timeoutMs?: number;
now?: () => number;
}
export function isValidChannel(slug: string): boolean {
return CHANNEL_RE.test(slug);
}
function isObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
/** Reject noncanonical/oversized encodings before either decoder allocates. */
function b64ToBytes(value: unknown, maxBytes: number): Uint8Array {
if (typeof value !== "string" || !value.length || value.length > 4 * Math.ceil(maxBytes / 3) ||
(value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value))) {
throw new Error("invalid-base64");
}
const bin = atob(value);
if (bin.length > maxBytes || btoa(bin) !== value) throw new Error("invalid-base64");
return Uint8Array.from(bin, (char) => char.charCodeAt(0));
}
export async function sha256Hex(bytes: Uint8Array): Promise<string> {
const digest = await crypto.subtle.digest("SHA-256", bytes as BufferSource);
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
}
export function signingMessage(keyId: string, payload: Uint8Array): Uint8Array {
const prefix = new TextEncoder().encode(`${DOMAIN}${keyId}\0`);
const out = new Uint8Array(prefix.length + payload.length);
out.set(prefix);
out.set(payload, prefix.length);
return out;
}View on GitHub (pinned to 433685b202)