Skip to content

VNX-323: Reusing a Nonce, Key Pair in Encryption

An IV or nonce generated once (module, package or class level, or in a constructor) and reused by the encryption call for every message.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-323
Severity Medium
SARIF level warning
Kind sast
Languages Go, Java, JavaScript / TypeScript, Python
CWE CWE-323
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags nonce-reuse cwe-323
Source internal/sast/rules/vnx-323.rego

Overview

VNX-323 maps to CWE-323: Reusing a Nonce, Key Pair in Encryption. It reports an encryption call inside a function or method whose IV or nonce argument was created once and is never refreshed there:

  • a Python module-level variable assigned from os.urandom, get_random_bytes or secrets.token_bytes, or a self. attribute set in __init__;
  • a JavaScript/TypeScript module-level variable assigned from crypto.randomBytes or getRandomValues, or a this. property set in the constructor;
  • a Go package-level variable, or a struct field declared as []byte;
  • a Java byte[] field, used in an ENCRYPT_MODE initialisation.

Sinks: Python AESGCM/ChaCha20Poly1305/AESCCM .encrypt(nonce, ...), pycryptodome .new(..., nonce=/iv=), cryptography modes.CBC/CTR/GCM/CFB/OFB; Node crypto.createCipheriv(alg, key, iv) and WebCrypto { name: 'AES-GCM', iv }; Go AEAD .Seal(dst, nonce, ...) and cipher.NewCBCEncrypter/NewCTR/NewOFB/NewCFBEncrypter; Java IvParameterSpec, GCMParameterSpec and ChaCha20ParameterSpec.

Not reported: an IV or nonce assigned, filled (rand.Read, io.ReadFull, nextBytes, randomFillSync, getRandomValues) or incremented inside the encrypting function; decryption calls; test files. Hard-coded constant IVs are VNX-CRYPTO-010’s concern. Flows through helpers and across files are not followed.

Why This Matters

A random nonce generated at start-up is still one nonce. Every message encrypted under the same key reuses it: with GCM, CTR and ChaCha20-Poly1305 this leaks the XOR of plaintexts and lets an attacker forge authenticated messages; with CBC it reveals which messages share a prefix.

What Gets Flagged

# FLAGGED
NONCE = os.urandom(12)

def seal(data):
    return AESGCM(KEY).encrypt(NONCE, data, None)
// FLAGGED
const iv = crypto.randomBytes(16);
function encrypt(text) {
  const cipher = crypto.createCipheriv('aes-256-ctr', key, iv);
// FLAGGED
func (s *Sealer) Encrypt(dst, src []byte) {
	stream := cipher.NewCTR(s.block, s.iv)

Not flagged: nonce = os.urandom(12) inside the function before encrypt(nonce, ...), random.nextBytes(iv) before new GCMParameterSpec(128, iv), io.ReadFull(rand.Reader, b.nonce) before Seal.

Remediation

  1. Generate a fresh random nonce for every encryption (or use a counter that never repeats under one key) and send it with the ciphertext.
  2. Prefer a misuse-resistant AEAD (AES-GCM-SIV, XChaCha20-Poly1305 with random 192-bit nonces) when nonce management is hard.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-323
// nosec vnx-323

Or record an ignore rule: vulnetix ignore add --rule vnx-323 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-323.rego, cli/internal/sast/docs/vnx-323.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.