Skip to content

VNX-321: Hard-coded cryptographic key

A cryptographic key written into the source: a key-named variable assigned key material, a literal assigned to a variable that a key API consumes, or a literal passed straight to AES/DES/HMAC/Fernet/SecretKeySpec/createCipheriv/NewCipher or a JWT call. Node JWT, session and HMAC signing keys are reported by VNX-NODE-019.

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

Overview

VNX-321 maps to CWE-321: Use of Hard-coded Cryptographic Key. It reports, in Python, JavaScript/TypeScript, Java and Go:

  • a variable, constant, field or config key named as a cryptographic key (secret_key, private_key, encryption_key, signing_key, hmac_key, aes_key, des_key, master_key, crypto_key, cipher_key, enc_key, jwt_secret) assigned a literal that looks like key material: at least 16 characters, no spaces, not a placeholder or an environment lookup;
  • a string or byte literal passed straight to a key API: pycryptodome AES/DES/ARC4 .new(), cryptography Fernet/AESGCM/ChaCha20Poly1305/algorithms.AES, hmac.new, Node crypto.createCipheriv/createDecipheriv/createHmac, CryptoJS AES/DES/TripleDES/Rabbit/RC4/Hmac*, Java new SecretKeySpec("...".getBytes(), ...) and byte-array literals, Go aes.NewCipher/des.NewCipher/hmac.New with []byte("...").

Reported by VNX-NODE-019 instead: in JavaScript and TypeScript, a literal key given to jwt.sign()/jwt.verify() or crypto.createHmac(alg, '...'), a config property named for a JWT or session secret (jwtSecret, jwt_secret) set to a literal, and a constant holding a literal that one of those calls uses as its key. That rule is the Node-specific form of this one, so each such key is one finding. In an examples/ directory, which VNX-NODE-019 skips, this rule still reports them. The other JavaScript shapes (createCipheriv, CryptoJS, jwt.encode/decode, key-named properties such as cryptoKey) stay here.

Not reported: names that merely end in key (map keys, cache keys, loop variables), API keys and passwords (credentials, covered by the secret rules), placeholders (changeme, your-..., example, xxxx, ${...}), environment and config lookups, short non-key strings assigned to key names, test files and comments.

Why This Matters

A key in the source is in every clone, build artefact and backup of the repository. Anyone who has it can decrypt the data it protects or forge the signatures and MACs it produces, and it cannot be rotated without a release.

What Gets Flagged

# FLAGGED
cipher = AES.new(b"0123456789abcdef", AES.MODE_GCM)
SECRET_KEY = "9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c"

key = b"Rq7Lx2Mz9Pk4Wn8T"          # FLAGGED: consumed as the key below
cipher = AES.new(key, AES.MODE_GCM)
// FLAGGED
crypto.createCipheriv('aes-256-cbc', 'a3f9c2e17b8d4065a3f9c2e17b8d4065', iv)
const config = { cryptoKey: "c0ffee00deadbeefc0ffee00deadbeef" }

// Reported by VNX-NODE-019, not here
crypto.createHmac('sha256', 'webhook-signing-secret')
jwt.sign({ sub: user.id }, 'Zq8vR2mK9pL4xW7nB3cT6yH1')
// FLAGGED
b, _ := aes.NewCipher([]byte("0123456789abcdef0123456789abcdef"))

Not flagged: const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY, cache_key = "user:profile:1234567890abcdef", secretKey: "your-secret-key-goes-here-please".

Remediation

  1. Load keys at runtime from a secrets manager or KMS, or derive them from a secret that is.
  2. Rotate any key that has been committed: it must be treated as public.

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-321
// nosec vnx-321

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

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