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

  1. Decode both sides to Buffers with matching encodings: Buffer.from(a, 'hex') and Buffer.from(b, 'hex')
  2. If either input may be a string, normalize first: const toBuf = (v) => Buffer.isBuffer(v) ? v : Buffer.from(String(v), 'hex')
  3. 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

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.