denoland/deno · error · TypeError
ERR_INVALID_ARG_TYPE
ERR_INVALID_ARG_TYPE
Error message
The "${name}" argument must be an instance of Buffer, ArrayBuffer, TypedArray, or DataView What it means
Deno's internal crypto.timingSafeEqual binding only accepts Buffer, ArrayBuffer/SharedArrayBuffer, TypedArray, or DataView inputs — validated by validateBuffer in ext/node/polyfills/internal_binding/_timingSafeEqual.ts. Strings, numbers, and wrapper objects throw ERR_INVALID_ARG_TYPE listing the accepted types. (The public node:crypto API in Node itself only accepts Buffers; Deno's binding is deliberately broader.)
Source
Thrown at ext/node/polyfills/internal_binding/_timingSafeEqual.ts:35
ArrayBufferIsView,
ArrayBufferPrototypeGetByteLength,
DataView,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
DataViewPrototypeGetUint8,
ObjectPrototypeIsPrototypeOf,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetByteOffset,
} = primordials;
function validateBuffer(
buf: unknown,
name: string,
): asserts buf is ArrayBufferLike | ArrayBufferView {
if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) {
throw new ERR_INVALID_ARG_TYPE(
name,
["Buffer", "ArrayBuffer", "TypedArray", "DataView"],
buf,
);
}
}
function byteLengthOf(
ab: ArrayBufferView | ArrayBufferLike | DataView,
): number {
if (isDataView(ab)) {
return DataViewPrototypeGetByteLength(ab);
}
if (ArrayBufferIsView(ab)) {
return TypedArrayPrototypeGetByteLength(ab);
}
return ArrayBufferPrototypeGetByteLength(ab);
}View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Decode both sides to Buffers with matching encodings: Buffer.from(a, 'hex') and Buffer.from(b, 'hex')
- If either input may be a string, normalize first: const toBuf = (v) => Buffer.isBuffer(v) ? v : Buffer.from(String(v), 'hex')
- Do not pass substrings of encoded values with prefixes (e.g. 'Bearer ') — strip first, then decode
Example fix
// before
crypto.timingSafeEqual(req.header('x-signature'), computedHex); // strings -> throws
// after
crypto.timingSafeEqual(
Buffer.from(req.header('x-signature'), 'hex'),
Buffer.from(computedHex, 'hex'),
); Defensive patterns
Strategy: type-guard
Validate before calling
const toBuf = (v: unknown) =>
Buffer.isBuffer(v) ? v
: v instanceof ArrayBuffer || ArrayBuffer.isView(v) ? Buffer.from(v as any)
: Buffer.from(String(v), 'hex');
if (!Buffer.isBuffer(a) && !ArrayBuffer.isView(a) && !(a instanceof ArrayBuffer)) {
throw new TypeError('timingSafeEqual inputs must be buffer-like');
}
crypto.timingSafeEqual(toBuf(a), toBuf(b)); Type guard
function isBufferLike(v: unknown): v is Buffer | ArrayBufferView | ArrayBuffer {
return Buffer.isBuffer(v) || ArrayBuffer.isView(v) || v instanceof ArrayBuffer;
} Try / catch
try {
ok = crypto.timingSafeEqual(a, b);
} catch (e: any) {
if (e?.code === 'ERR_INVALID_ARG_TYPE') {
ok = crypto.timingSafeEqual(toBuf(a), toBuf(b));
} else throw e;
} Prevention
- Decode string tokens/mac signatures to Buffers at the API boundary, not at compare time
- Keep a single toBuf normalizer for security-comparison code paths
- Type the compare helper's parameters as (a: Buffer, b: Buffer) so TS rejects strings
When it happens
Trigger: crypto.timingSafeEqual(token, storedToken) where both are hex/base64 strings; one side a Buffer and the other a base64url string; passing { data } wrapper objects or numbers; comparing JWT signature strings without decoding.
Common situations: Auth code comparing user-supplied tokens, API keys, HMAC digests, or webhooks signatures still in their encoded string form; data crossing a JSON boundary so buffers arrive as strings.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/1bd2c656dc76b572.
Report an issue: GitHub.