VNX-212: Improper Removal of Sensitive Information Before Storage or Transfer
Detects user and account records serialised whole, password hash included: Express/Prisma handlers that res.json() a user fetched a few lines above (or res.json(await User.findById(...)) directly) with no projection or removal, Flask/Django responses built from user.__dict__ or vars(user), Django REST serializers exposing a User's password, Django serialize() of User.objects and model_to_dict(user), Laravel user models without $hidden, and Go model structs whose JSON tag exposes a password or its hash.
| Field | Value |
|---|---|
| Rule ID | VNX-212 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Go, JavaScript / TypeScript, PHP, Python |
| CWE | CWE-212 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | data-handling serialization password cwe-212 |
| Source | internal/sast/rules/vnx-212.rego |
Overview
VNX-212 maps to CWE-212: Improper Removal of Sensitive Information Before Storage or Transfer. A user record usually carries a password hash, reset tokens and other fields that must never leave the server. Serialising the record as it came from the database sends them to whoever receives the response or the export; a leaked hash can be cracked offline.
| Language | Reported |
|---|---|
| JavaScript, TypeScript | res.json(user) / res.send({ user }) where user was assigned a few lines above from a user/account/member/customer/admin lookup (findById, findOne, findByPk, find, findAll, prisma.user.findUnique/findFirst/findMany), with no .select(), attributes/exclude/omit/select: option, delete user.password or password = undefined in between |
| Python | DRF ModelSerializer for a User/Account model with fields = "__all__" or a fields list naming password, unless the password is write_only; serializers.serialize("json", User.objects...) without fields= |
| PHP | Laravel models extending Authenticatable (or with password in $fillable) and no $hidden naming password, $visible, #[Hidden] or makeHidden |
| Go | struct fields holding a password hash (PasswordHash, HashedPassword, PassHash, Salt) with a JSON tag other than "-", and a Password field with a JSON tag on a persisted model (gorm/db/bson tag on the same line) |
What Gets Flagged
const user = await User.findById(req.params.id);
res.json(user); // FLAGGED
const saved = await user?.update({ lastLoginAt: new Date() });
res.json(saved); // FLAGGED
return JsonResponse(model_to_dict(request.user)) # FLAGGED
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__" # FLAGGED
PasswordHash []byte `json:"password_hash,omitempty"` // FLAGGED
What Is Not Flagged
User.findById(id).select('-password'),attributes: { exclude: ['password'] },delete user.password,res.json(user.toPublicJSON()), destructuring the password away.- Serializers with
extra_kwargs = {"password": {"write_only": True}}, or field lists withoutpassword. - Laravel models with
protected $hidden = ['password', ...]. json:"-"tags, request structs (LoginRequest) that only decode a password, and aSaltin key-derivation parameters (only a user or account struct counts).- Models related to a user rather than the user model itself:
Profile,UserProfile,UserSettings,AccountActivity. - Test code. A field removed by a schema option or model transform in another file is not seen; a Mongoose schema in the same file with
select: falsesuppresses the file.
Remediation
Return a projection or a DTO, never the database record.
const user = await User.findById(id).select('name email role');
res.json(user);
References
- CWE-212: Improper Removal of Sensitive Information Before Storage or Transfer
- OWASP API3:2023 Broken Object Property Level Authorization
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-212
// nosec vnx-212
Or record an ignore rule: vulnetix ignore add --rule vnx-212 --reason "...". See Suppressing findings.