Skip to content

VNX-256: Plaintext Storage of a Password

Reports a password taken from the request and stored unhashed (a model's password field, setPassword, a create/insert call, an SQL INSERT/UPDATE), and any password written to web storage or a cookie.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-256
Severity High
SARIF level error
Kind sast
Languages Go, Java, JavaScript / TypeScript, PHP, Python, Ruby
CWE CWE-256
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 password-storage plaintext cwe-256
Source internal/sast/rules/vnx-256.rego

Overview

A password stored as it arrived is disclosed to anyone who can read the database, a backup or a log of it, and is reusable on every other site where the user chose the same one. This maps to CWE-256: Plaintext Storage of a Password.

What Gets Flagged

A request value (Flask/Django request.form/POST/GET…, Express req.body, servlet getParameter() or a form/DTO getPassword(), PHP $_POST/$request->input(), Go FormValue/PostFormValue, gin c.PostForm, Rails params[...]), used directly or through a variable assigned from one up to 15 lines above, that is written to:

  • an object’s password field (user.password = ..., $user->password = ..., u.Password = ...);
  • a setPassword(...) setter;
  • the password key of a create/insert call (Model.create, objects.create, insertOne, new Model({...}), Model(...).save(), or a Python obj = Model(..., password=pw) saved with obj.save() within three lines);
  • an SQL INSERT or UPDATE that names a password column.

Regardless of the source, a password written to localStorage/sessionStorage, a response cookie or document.cookie under a key named password/passwd/pwd/pass is also reported.

newpassword = request.form["newpassword"]
u.password = newpassword                                              # flagged
User.objects.create(username=u, password=request.POST["password"])    # flagged
const user = new User({ email: req.body.email, password: req.body.password });  // flagged
localStorage.setItem("password", password);                                     // flagged

Not flagged: a value passed through a hash (bcrypt, argon2, scrypt, PBKDF2, password_hash, Hash::make, make_password, generate_password_hash, PasswordEncoder.encode, set_password, create_user) or re-assigned from one first; lookups and comparisons; projects that hash in a model hook or setter (Mongoose pre('save'), Sequelize/TypeORM hooks, Rails has_secure_password/Devise/before_save, Laravel setPasswordAttribute or a hashed cast, a Python @password.setter); hard-coded literals (VNX-SEC-014, VNX-259); comments, strings and test code.

Remediation

from werkzeug.security import generate_password_hash
u.password = generate_password_hash(request.form["newpassword"])
const user = new User({ email, password: await bcrypt.hash(req.body.password, 12) });

Keep passwords out of the browser entirely: authenticate once and store a session token.

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

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

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