{"record":{"id":"4fd43cc713d35eff","repo":"denoland/deno","slug":"err-crypto-invalid-state","errorCode":"ERR_CRYPTO_INVALID_STATE","errorMessage":"Invalid state for operation final","messagePattern":"Invalid state for operation final","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/internal/crypto/cipher.ts","lineNumber":262,"sourceCode":"  this._needsBlockCache = !this._isAesWrap &&\n    !(cipher == \"aes-128-gcm\" || cipher == \"aes-256-gcm\" ||\n      cipher == \"aes-128-ctr\" || cipher == \"aes-192-ctr\" ||\n      cipher == \"aes-256-ctr\" || cipher == \"chacha20\" ||\n      cipher == \"chacha20-poly1305\");\n  this._authTag = undefined;\n  this._autoPadding = true;\n  this._finalized = false;\n  this._decoder = undefined;\n}\n\nObjectSetPrototypeOf(Cipheriv.prototype, getTransform().prototype);\nObjectSetPrototypeOf(Cipheriv, getTransform());\n\nCipheriv.prototype.final = function (\n  encoding: string = getDefaultEncoding(),\n): Buffer | string {\n  if (this._finalized) {\n    throw new ERR_CRYPTO_INVALID_STATE(\"final\");\n  }\n\n  _lazyInitCipherDecoder(this, encoding);\n\n  if (this._isAesWrap) {\n    this._finalized = true;\n    return encoding === \"buffer\" ? Buffer.from([]) : this._decoder!.end();\n  }\n\n  const bs = this._blockSize;\n  const buf = new FastBuffer(bs);\n  const hasNoBufferedData =\n    TypedArrayPrototypeGetByteLength(this._cache.cache) === 0;\n  const shouldPadEmptyBlock = this._needsBlockCache && this._autoPadding;\n\n  if (hasNoBufferedData && !shouldPadEmptyBlock) {\n    const maybeTag = op_node_cipheriv_take(this._context);\n    if (maybeTag) this._authTag = Buffer.from(maybeTag);","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/internal/crypto/cipher.ts#L244-L280","documentation":"Cipheriv is a one-shot state machine: final() flushes the last block, sets _finalized = true, and (at the native layer, op_node_cipheriv_final takes the context out of the resource table) consumes the context. A second final() call hits `if (this._finalized)` and throws ERR_CRYPTO_INVALID_STATE('final'). The same flag also blocks update()/setAAD() after final, but the 'final' operation name means final() itself ran twice.","triggerScenarios":"Calling final() inside a helper and again in the caller; a `finally { cipher.final() }` block that runs after an earlier return already finalized; reusing one cipher object across loop iterations or queued async jobs.","commonSituations":"Double-cleanup error paths; libraries that auto-finalize cipher streams they are handed; copy-pasted encrypt helpers where ownership of final() is unclear.","solutions":["Call final() exactly once per cipher instance; create a fresh createCipheriv() for every message.","Wrap the cipher so final() is idempotent (track a local boolean) or move final() out of finally blocks.","Make ownership explicit: the function that creates the cipher is the only one that finalizes it."],"exampleFix":"// before\nfunction encrypt(msg) {\n  const c = crypto.createCipheriv('aes-256-gcm', key, iv);\n  c.update(msg);\n  helperFlush(c);          // calls c.final()\n  return c.final();        // ERR_CRYPTO_INVALID_STATE('final')\n}\n\n// after\nfunction encrypt(msg) {\n  const c = crypto.createCipheriv('aes-256-gcm', key, iv);\n  c.update(msg);\n  return c.final();        // single, owned by this function\n}","handlingStrategy":"validation","validationCode":"function once<T extends (...args: any[]) => any>(fn: T): T {\n  let called = false;\n  return ((...args: any[]) => {\n    if (called) throw new Error('final() called twice');\n    called = true;\n    return fn(...args);\n  }) as T;\n}\ncipher.final = once(cipher.final.bind(cipher)); // idempotent-by-failure wrapper","typeGuard":null,"tryCatchPattern":"try { out = cipher.final(); } catch (e) { if (e.code === 'ERR_CRYPTO_INVALID_STATE' && e.message.includes('final')) { throw new LogicError('cipher already finalized — check double final()'); } throw e; }","preventionTips":["One creator, one finalizer: keep cipher construction and final() in the same function scope.","Never put final() in a finally block that can follow an early return.","Build ciphers per message; do not pool or cache them."],"tags":["crypto","cipher","lifecycle","state-machine","node-compat"],"backgroundTag":"cipher-already-finalized","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}