denoland/deno · error
input length exceeds i32::MAX
Error message
input length exceeds i32::MAX
What it means
Deno's node:crypto chacha20-poly1305 cipher passes each update() input to EVP_CipherUpdate, whose length parameter is a C int. encrypt() converts input.len() to i32 and this expect() panics when a single plaintext chunk handed to cipher.update() is larger than 2147483647 bytes (2 GiB). The panic occurs in native code during the update call and aborts the process.
Solutions
- Encrypt in chunks well under 2 GiB per update() call — e.g. 4–64 MiB slices: `for (let o = 0; o < data.length; o += CHUNK) cipher.update(data.subarray(o, Math.min(o + CHUNK, data.length)));`
- Stream the file instead of buffering it: read with fs.createReadStream and pipe chunks through cipher.update().
- Validate input size before calling update() and throw a clear JS error (you control the message) instead of letting the process abort.
- For one-shot small payloads (the normal case) nothing changes; guard only the large-file path.
Example fix
// before
const data = await Deno.readFile("big.bin"); // 3 GiB
cipher.update(data); // single >2GiB call -> panic
// after
const CHUNK = 64 * 1024 * 1024;
for (let o = 0; o < data.length; o += CHUNK) {
cipher.update(data.subarray(o, Math.min(o + CHUNK, data.length)));
} Defensive patterns
Strategy: validation
Validate before calling
const MAX_I32 = 2 ** 31 - 1;
const CHUNK = 64 * 1024 * 1024;
function updateChunked(cipher, data) {
if (data.byteLength > MAX_I32) {
for (let o = 0; o < data.length; o += CHUNK) {
cipher.update(data.subarray(o, Math.min(o + CHUNK, data.length)));
}
} else {
cipher.update(data);
}
} Prevention
- Never pass a single buffer larger than 2 GiB to cipher.update(); slice into 4–64 MiB chunks.
- Stream large files with fs.createReadStream instead of reading them whole.
- Apply identical chunking on encrypt and decrypt sides to keep AAD flush and tag ordering consistent.
When it happens
Trigger: crypto.createCipheriv("chacha20-poly1305", key, iv) followed by cipher.update(buf) where buf is a single Buffer/TypedArray larger than 2 GiB — typically the result of reading an entire large file into memory (await Deno.readFile / fs.promises.readFile) and encrypting it in one call.
Common situations: Backup/sync scripts that read whole files >2 GiB into one Buffer; test code generating one giant buffer; porting Node scripts after a Deno upgrade where sizes previously worked via a different cipher path; concatenating stream chunks into one buffer before encrypting.
Related errors
- AAD length exceeds i32::MAX
- Failed to allocate EVP_CIPHER_CTX
- Trying to add data in unsupported state
- Attempted to read snapshot data out of range
- Attempted to read the snapshot data at index
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/2881c6d45917b858.
Report an issue: GitHub.
Appendix: source
Thrown at ext/node_crypto/cipher.rs:409
);
assert_eq!(ret, 1, "EVP_CipherUpdate for AAD failed");
}
}
}
}
fn encrypt(&mut self, input: &[u8], output: &mut [u8]) {
assert!(output.len() >= input.len());
self.flush_aad();
// SAFETY: ctx is valid and initialized for encryption. output is
// caller-provided with at least input.len() bytes. EVP_CipherUpdate
// writes at most input.len() bytes for a stream cipher.
// Length is validated to fit in i32 before casting.
unsafe {
let input_len: i32 = input
.len()
.try_into()
.expect("input length exceeds i32::MAX");
let mut outl: i32 = 0;
let ret = aws_lc_sys::EVP_CipherUpdate(
self.ctx,
output.as_mut_ptr(),
&mut outl,
input.as_ptr(),
input_len,
);
assert_eq!(ret, 1, "EVP_CipherUpdate for encryption failed");
}
}
fn decrypt(&mut self, input: &[u8], output: &mut [u8]) {
assert!(output.len() >= input.len());
self.flush_aad();
// SAFETY: ctx is valid and initialized for decryption. output is
// caller-provided with at least input.len() bytes.
// Length is validated to fit in i32 before casting.View on GitHub (pinned to 9ad36f7a2c)