denoland/deno · error · TypeError

ERR_INVALID_ARG_VALUE

ERR_INVALID_ARG_VALUE

Error message

The property 'options.ticketKeys' must be exactly 48 bytes. Received ${getViewByteLength(options.ticketKeys)}

What it means

After options.ticketKeys passes the type check, its byte length must be exactly 48 — the TLS session-ticket key format is 16 bytes name + 16 bytes HMAC key + 16 bytes AES key (ext/node/polyfills/_tls_wrap.js:1407). Any other length throws ERR_INVALID_ARG_VALUE with reason 'must be exactly 48 bytes', matching Node.

Source

Thrown at ext/node/polyfills/_tls_wrap.js:1407

  if (options.sessionTimeout != null) {
    validateInt32(
      options.sessionTimeout,
      "options.sessionTimeout",
      0,
    );
  }

  if (options.ticketKeys != null) {
    if (!isArrayBufferView(options.ticketKeys)) {
      throw new ERR_INVALID_ARG_TYPE(
        "options.ticketKeys",
        ["Buffer", "TypedArray", "DataView"],
        options.ticketKeys,
      );
    }
    if (getViewByteLength(options.ticketKeys) !== 48) {
      throw new ERR_INVALID_ARG_VALUE(
        "options.ticketKeys",
        getViewByteLength(options.ticketKeys),
        "must be exactly 48 bytes",
      );
    }
  }
  this._ticketKeys = options.ticketKeys == null
    ? Buffer.alloc(48)
    : Buffer.from(
      getViewBuffer(options.ticketKeys),
      getViewByteOffset(options.ticketKeys),
      getViewByteLength(options.ticketKeys),
    );

  this.setSecureContext(options);

  if (options.handshakeTimeout != null) {
    validateNumber(options.handshakeTimeout, "options.handshakeTimeout");

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Generate exactly 48 bytes: crypto.randomBytes(48)
  2. Verify length after decoding from hex/base64: buf.length === 48 or throw early with your own message
  3. If your store holds multiple keys, pass only one 48-byte key per server or rotate via server.setTicketKeys(oneKey)

Example fix

// before
const server = tls.createServer({ ticketKeys: crypto.randomBytes(64) });

// after
const server = tls.createServer({ ticketKeys: crypto.randomBytes(48) });
Defensive patterns

Strategy: validation

Validate before calling

if (opts.ticketKeys != null && opts.ticketKeys.length !== 48) throw new RangeError(`ticketKeys must be 48 bytes, got ${opts.ticketKeys.length}`);

Type guard

function is48Bytes(v) { return ArrayBuffer.isView(v) && v.byteLength === 48; }

Try / catch

try { tls.createServer(opts); } catch (e) { if (e.code === 'ERR_INVALID_ARG_VALUE' && /48 bytes/.test(e.message)) { delete opts.ticketKeys; /* fall back to generated keys */ return tls.createServer(opts); } throw e; }

Prevention

When it happens

Trigger: tls.createServer({ ticketKeys: Buffer.alloc(32) }); hex string decoded to 96 bytes because it was 96 hex chars; base64 blob truncated or padded incorrectly; sharing keys generated for a different library that uses another size.

Common situations: Generating ticket keys with crypto.randomBytes(N) where N != 48; key-rotation pipelines that store keys with a prefix/version byte, making 49 bytes; copying a 'ticket key' that is actually a pair of keys (96 bytes).

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/4030fe8ebd7bd335. Report an issue: GitHub.