Repository: denoland/deno
Pull request: fix(ext/node): CBC decipher setAutoPadding(false) incorrectly strips bytes

## Summary

- `Decipheriv.final()` unconditionally stripped PKCS7 padding from decrypted output regardless of the `#autoPadding` flag, corrupting data when `setAutoPadding(false)` was used
- Made the PKCS7 padding stripping conditional on `this.#autoPadding`
- Added tests for `aes-256-cbc` and `aes-128-cbc` roundtrips with `setAutoPadding(false)`

Closes #28381

## Test plan

- [ ] `aes-256-cbc` encrypt/decrypt roundtrip with `setAutoPadding(false)` returns original plaintext
- [ ] `aes-128-cbc` encrypt/decrypt roundtrip with `setAutoPadding(false)` returns original plaintext
- [ ] Existing cipher tests still pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/node/polyfills/internal/crypto/cipher.ts
patch: @@ -485,7 +485,13 @@ export class Decipheriv extends Transform implements Cipher {
       throw new Error("Invalid final block size");
     }
 
-    buf = buf.subarray(0, 16 - buf.at(-1)); // Padded in Pkcs7 mode
+    if (this.#autoPadding) {
+      const padLen = buf.at(-1);
+      if (padLen === 0 || padLen > 16) {
+        throw new Error("bad decrypt");
+      }
+      buf = buf.subarray(0, 16 - padLen); // Padded in Pkcs7 mode
+    }
     this.#finalized = true;
     if (encoding !== "buffer") {
       return this.#decoder!.end(buf);

filename: tests/unit_node/crypto/crypto_cipher_test.ts
patch: @@ -655,6 +655,29 @@ Deno.test({
   },
 });
 
+Deno.test({
+  name: "aes-256-cbc setAutoPadding(false) roundtrip",
+  fn() {
+    const key = Buffer.alloc(32, 0x01);
+    const iv = Buffer.alloc(16, 0x02);
+    // 32 bytes = exactly 2 blocks, no PKCS7 padding needed
+    const plaintext = Buffer.from("a]B$y;^&5}0[e-k+D7zIn9Co*q#m!LpX");
+
+    const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
+    cipher.setAutoPadding(false);
+    const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
+
+    const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
+    decipher.setAutoPadding(false);
+    const decrypted = Buffer.concat([
+      decipher.update(encrypted),
+      decipher.final(),
+    ]);
+
+    assertEquals(decrypted, plaintext);
+  },
+});
+
 Deno.test({
   name: "publicEncrypt/privateDecrypt with DER keys",
   fn() {
@@ -682,6 +705,29 @@ Deno.test({
   },
 });
 
+Deno.test({
+  name: "aes-128-cbc setAutoPadding(false) roundtrip",
+  fn() {
+    const key = Buffer.alloc(16, 0x03);
+    const iv = Buffer.alloc(16, 0x04);
+    // 16 bytes = exactly 1 block
+    const plaintext = Buffer.from("0123456789abcdef");
+
+    const cipher = crypto.createCipheriv("aes-128-cbc", key, iv);
+    cipher.setAutoPadding(false);
+    const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
+
+    const decipher = crypto.createDecipheriv("aes-128-cbc", key, iv);
+    decipher.setAutoPadding(false);
+    const decrypted = Buffer.concat([
+      decipher.update(encrypted),
+      decipher.final(),
+    ]);
+
+    assertEquals(decrypted, plaintext);
+  },
+});
+
 Deno.test({
   name: "publicEncrypt/privateDecrypt with PKCS#1 DER keys",
   fn() {
@@ -878,3 +924,35 @@ Deno.test({
     }
   },
 });
+
+Deno.test({
+  name: "createDecipheriv - invalid PKCS7 padding throws bad decrypt",
+  fn() {
+    const key = Buffer.alloc(16, 0x01);
+    const iv = Buffer.alloc(16, 0x02);
+
+    // Encrypt without padding so we control the raw plaintext bytes
+    const cipher = crypto.createCipheriv("aes-128-cbc", key, iv);
+    cipher.setAutoPadding(false);
+
+    // Last byte 0x00 is invalid PKCS7 padding (valid range is 1-16)
+    const plaintext = Buffer.from(
+      "0123456789abcde\x00",
+    );
+    const encrypted = Buffer.concat([
+      cipher.update(plaintext),
+      cipher.final(),
+    ]);
+
+    // Decrypt with autoPadding=true (default) should throw
+    const decipher = crypto.createDecipheriv("aes-128-cbc", key, iv);
+    assertThrows(
+      () => {
+        decipher.update(encrypted);
+        decipher.final();
+      },
+      Error,
+      "bad decrypt",
+    );
+  },
+});