{"record":{"id":"073b29217aedd865","repo":"denoland/deno","slug":"session-ticket-keys-must-be-a-48-byte-buffer","errorCode":null,"errorMessage":"Session ticket keys must be a 48-byte buffer","messagePattern":"Session ticket keys must be a 48-byte buffer","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/_tls_wrap.js","lineNumber":1519,"sourceCode":"    throw new ERR_TLS_REQUIRED_SERVER_NAME();\n  }\n  ArrayPrototypePush(this._contexts, [servername, context]);\n};\n\nServer.prototype.getTicketKeys = function getTicketKeys() {\n  return Buffer.from(this._ticketKeys);\n};\n\nServer.prototype.setTicketKeys = function setTicketKeys(keys) {\n  if (!isArrayBufferView(keys)) {\n    throw new ERR_INVALID_ARG_TYPE(\n      \"keys\",\n      [\"Buffer\", \"TypedArray\", \"DataView\"],\n      keys,\n    );\n  }\n  if (getViewByteLength(keys) !== 48) {\n    throw new Error(\"Session ticket keys must be a 48-byte buffer\");\n  }\n  this._ticketKeys = Buffer.from(\n    getViewBuffer(keys),\n    getViewByteOffset(keys),\n    getViewByteLength(keys),\n  );\n};\n\n// ---------------------------------------------------------------------------\n// connect\n// ---------------------------------------------------------------------------\n\nfunction onConnectEnd() {\n  if (!this._hadError) {\n    const options = this[kConnectOptions];\n    this._hadError = true;\n    const error = connResetException(\n      \"Client network socket disconnected \" +","sourceCodeStart":1501,"sourceCodeEnd":1537,"githubUrl":"https://github.com/denoland/deno/blob/89f33cbef296a2b287f323d42de54c871fa69c77/ext/node/polyfills/_tls_wrap.js#L1501-L1537","documentation":"The second guard in setTicketKeys enforces the TLS session-ticket key size: exactly 48 bytes (16 name + 16 HMAC key + 16 AES key, ext/node/polyfills/_tls_wrap.js:1519). Unlike the constructor path, which throws the coded ERR_INVALID_ARG_VALUE, this runtime-rotation path throws a plain Error('Session ticket keys must be a 48-byte buffer').","triggerScenarios":"server.setTicketKeys(Buffer.alloc(47)); rotating with a key blob whose hex decoded to 96 bytes (two keys concatenated); rotation pipeline that appends a key-id byte, yielding 49.","commonSituations":"Automated key rotation where new material is generated with a different length than the original; copying ticket key formats from other TLS stacks that use 32- or 80-byte keys; test fixtures with arbitrary-length buffers.","solutions":["Validate length before rotating: if (keys.length !== 48) throw new Error('bad rotation key')","Generate rotation material with crypto.randomBytes(48) from the same source as initial keys","Check how the secret store encodes keys (hex doubles the byte count) and decode accordingly"],"exampleFix":"// before\nserver.setTicketKeys(Buffer.from(secretManager.get('ticketKeys'), 'utf8'));\n\n// after\nconst keys = Buffer.from(secretManager.get('ticketKeys'), 'hex');\nif (keys.length === 48) server.setTicketKeys(keys);","handlingStrategy":"validation","validationCode":"if (keys.length !== 48) throw new RangeError(`ticket keys must be 48 bytes, got ${keys.length}`);","typeGuard":"function isValidRotationKey(v) { return ArrayBuffer.isView(v) && v.byteLength === 48; }","tryCatchPattern":"try { server.setTicketKeys(keys); } catch (e) { if (/48-byte buffer/.test(e.message)) { /* keep old keys, alert rotation pipeline */ } else throw e; }","preventionTips":["Unit-test the rotation pipeline asserting exactly 48-byte output","Use one generator function for initial and rotated keys"],"tags":["tls","session-resumption","tickets","length","rotation"],"backgroundTag":null,"analyzedSha":"89f33cbef296a2b287f323d42de54c871fa69c77","analyzedAt":"2026-08-16T07:54:21.310Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}