From 2feb1fee9e2b3faef8fbb80edb9b18d35234feb9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 03:19:57 +0000 Subject: [PATCH] fix(crypto_usage): tighten weak-password-hash regex to eliminate false positives for non-credential variables * Improved the regex `_PW` in `crypto_usage.py` by applying a negative lookahead, which ignores case-insensitive suffixes like "token", "hash", "salt", etc., when they immediately follow "password" strings. This eliminates false positives for benign variables such as `passwordResetToken` or `password_hash`. * Wrote 6 permanent regression tests inside `CryptoUsageFalsePositiveTests` in `tests/test_pentest_regressions.py` to assert that zero false positive findings are generated for such snippets. * Ensured zero existing tests broke and no true positive detections were lost. * Passed metrics consistency check and updated `CHANGELOG.md` accordingly. --- CHANGELOG.md | 3 +++ src/websec_validator/extractors/crypto_usage.py | 2 +- tests/test_pentest_regressions.py | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c0cde..4055176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [Unreleased] +- Improved `weak-password-hash` regex in `crypto_usage.py` to prevent false positives when benign variables matching `password` (e.g. `passwordResetToken`, `password_hash`) are used in fast hashes. + ## [0.19.0] — 2026-09-22 Migration: **none required.** The one new surface is an opt-in flag, and nothing existing changes diff --git a/src/websec_validator/extractors/crypto_usage.py b/src/websec_validator/extractors/crypto_usage.py index e6a93ba..08f0073 100644 --- a/src/websec_validator/extractors/crypto_usage.py +++ b/src/websec_validator/extractors/crypto_usage.py @@ -23,7 +23,7 @@ from .base import Extractor, RepoContext, is_test_file from .syntax import expression_end, in_literal, js_functions, without_comments -_PW = r"(?:password|passwd|passphrase|\bpwd\b|userPassword|plainPassword)" +_PW = r"(?:password|passwd|passphrase|\bpwd\b|userPassword|plainPassword)(?![a-zA-Z0-9_]*?(?:[Tt]oken|[Hh]ash|[Ss]alt|[Aa]ttempt|[Rr]eset|[Cc]ount|[Uu]ri|[Uu]rl|[Ii]d\b|[Ff]ile))" # a fast digest fed a password-shaped value (either arg order, within a small window) WEAK_PW_HASH = re.compile( r"createHash\s*\(\s*['\"](?:md5|sha1|sha256|sha224)['\"]\s*\)[\s\S]{0,160}?\.update\s*\([^)]*" + _PW diff --git a/tests/test_pentest_regressions.py b/tests/test_pentest_regressions.py index c88fc85..6897f0d 100644 --- a/tests/test_pentest_regressions.py +++ b/tests/test_pentest_regressions.py @@ -572,6 +572,22 @@ def test_client_findings_carry_their_own_file(self): self.assertEqual(ctv["file"], "app/Pay.tsx") +class CryptoUsageFalsePositiveTests(unittest.TestCase): + def test_benign_password_variables_ignored(self): + from websec_validator.extractors.crypto_usage import CryptoUsageExtractor + ctx = repo({ + "test1.js": "createHash('sha256').update(passwordResetToken).digest('hex');", + "test2.py": "hashlib.sha256(password_hash.encode()).hexdigest()", + "test3.js": "createHash('md5').update(user.passwordAttemptCount.toString()).digest()", + "test4.js": "sha256(password_salt + user.id).digest()", + "test5.py": "hashlib.md5(password_file_path).hexdigest()", + "test6.js": "createHash('sha256').update(passwordResetUrl).digest()", + }) + res = CryptoUsageExtractor().extract(ctx, {}) + findings = res.get("findings", []) + self.assertEqual(len(findings), 0, f"Expected 0 findings, got {len(findings)}: {findings}") + + class AuthSchemeTests(unittest.TestCase): """P2: HMAC-signed cookies must not misread as api-key (which staged JWT probes for a no-JWT app)."""