denoland/deno · error
AAD length exceeds i32::MAX
Error message
AAD length exceeds i32::MAX
What it means
node:crypto in Deno implements chacha20-poly1305 with an EVP-based cipher that buffers AAD from setAAD() calls and flushes it to aws-lc right before the first encrypt/decrypt. EVP_CipherUpdate takes a C int length, so the buffered byte count must fit i32; this expect() panics when the cumulative AAD exceeds 2147483647 bytes (2 GiB). The panic originates in native code and crashes the process.
Source
Thrown at ext/node_crypto/cipher.rs:383
fn set_aad(&mut self, aad: &[u8]) {
self.aad_buf.extend_from_slice(aad);
}
/// Flush buffered AAD to EVP context. Called lazily before the first
/// encrypt/decrypt so that multiple setAAD() calls are concatenated.
fn flush_aad(&mut self) {
if !self.aad_flushed {
self.aad_flushed = true;
if !self.aad_buf.is_empty() {
// SAFETY: ctx is valid, aad_buf is a valid slice. Passing NULL
// output tells EVP this is AAD, not plaintext/ciphertext.
// Length is validated to fit in i32 before casting.
unsafe {
let aad_len: i32 = self
.aad_buf
.len()
.try_into()
.expect("AAD length exceeds i32::MAX");
let mut outl: i32 = 0;
let ret = aws_lc_sys::EVP_CipherUpdate(
self.ctx,
std::ptr::null_mut(),
&mut outl,
self.aad_buf.as_ptr(),
aad_len,
);
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 isView on GitHub (pinned to 9ad36f7a2c)
Solutions
- Audit the call site: AAD is metadata (headers/associated data) and should be tiny; if it is gigabytes you almost certainly meant to pass the data to update() instead of setAAD().
- Cap total AAD: reject or chunk-by-redesign before the sum of setAAD() calls reaches 2^31-1 bytes.
- If genuinely huge associated data is required, hash it first and use the digest (fixed 32 bytes) as the AAD.
- Add a unit test asserting AAD size stays under a sane bound (e.g. 64 KiB).
Example fix
// before cipher.setAAD(fileBytes); // oops: whole 3 GiB file as AAD cipher.update(header); // flush_aad() -> panic // after cipher.setAAD(aadHeader); // small associated data cipher.update(fileBytes); // payload goes through update()
Defensive patterns
Strategy: validation
Validate before calling
const MAX_I32 = 2 ** 31 - 1;
let aadTotal = 0;
function safeSetAAD(cipher, aad) {
if (!Buffer.isBuffer(aad)) throw new TypeError("AAD must be a Buffer");
if (aadTotal + aad.length > MAX_I32) {
throw new Error(`total AAD ${aadTotal + aad.length} exceeds 2 GiB limit`);
}
cipher.setAAD(aad);
aadTotal += aad.length;
} Prevention
- Keep AAD to headers/associated data (bytes to a few KB); treat anything larger as a bug.
- Track cumulative setAAD() bytes yourself — remember multiple calls are concatenated until the first update().
- If associated data is huge by design, hash it and use the fixed-size digest as AAD.
When it happens
Trigger: const c = crypto.createCipheriv("chacha20-poly1305", key, iv); c.setAAD(aad); ... where the total bytes passed to setAAD (possibly across multiple calls, since they are concatenated until the first update()) exceed 2 GiB, then the first c.update() triggers flush_aad() and the panic.
Common situations: Swapping arguments and accidentally passing the whole multi-gigabyte file to setAAD() instead of update(); piping an unbounded stream through setAAD in a loop; test harnesses that feed generated data into AAD without bounds. Real AEAD AAD is normally a few dozen bytes, so hitting this almost always indicates a logic bug rather than legitimate data.
Related errors
- input length exceeds i32::MAX
- Trying to add data in unsupported state
- Failed to allocate EVP_CIPHER_CTX
- Cannot access pointer: expected 'ArrayBuffer', 'SharedArrayB
- ERR_HTTP2_ALTSVC_LENGTH
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/a021688bf0b4157e.
Report an issue: GitHub.