Rootsystem2101 Claude Sonnet 4.6 commited on
Commit
1d6ebcd
Β·
1 Parent(s): 77238d9

Add permanent FP suppression layer (_FP_SUPPRESSIONS)

Browse files

Eliminates all recurring false positive categories at the architecture
level β€” no more per-repo patches after every scan:

- Dynamic ? placeholder: emails.map(()=>'?') β†’ bound values, not injected
- Ternary SQL keyword: ${flag ? 'UNIQUE' : ''} β†’ fixed literals
- file:// SSRF: local module loading, not HTTP vector
- Type-only File Upload: import type / : BusboyFileStream declarations
- CREATE INDEX boolean flag: internal flag, not user input
- Open Redirect: variable assignment line β‰  redirect issuance

Architecture: _is_suppressed(vuln, line) called once per match before
any finding is emitted. New FP categories can be added to _FP_SUPPRESSIONS
without touching any other code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +64 -0
app.py CHANGED
@@ -1522,6 +1522,65 @@ _SQL_KEYWORDS = re.compile(r'\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN)\b',
1522
  _TEMPLATE_VAR = re.compile(r'[$][{][^}]+[}]') # JS/TS template literals: ${var}
1523
  _FSTRING_VAR = re.compile(r'[{][a-zA-Z_]\w*[^}]*[}]') # Python f-strings: {var}
1524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1525
 
1526
  def scan_file_content(content, filename):
1527
  findings = []
@@ -1537,6 +1596,11 @@ def scan_file_content(content, filename):
1537
  if not re.search(pattern, line):
1538
  continue
1539
 
 
 
 
 
 
1540
  # ── Fix 1: SQL Injection mislabeled as Hardcoded Secret ──
1541
  # If the matched line contains SQL keywords AND a template/f-string
1542
  # interpolation with user input, it's SQL Injection, not a secret.
 
1522
  _TEMPLATE_VAR = re.compile(r'[$][{][^}]+[}]') # JS/TS template literals: ${var}
1523
  _FSTRING_VAR = re.compile(r'[{][a-zA-Z_]\w*[^}]*[}]') # Python f-strings: {var}
1524
 
1525
+ # ══════════════════════════════════════════════════════════
1526
+ # PERMANENT FALSE-POSITIVE SUPPRESSION LAYER
1527
+ # Applied per-line before any finding is emitted.
1528
+ # Each entry: (vuln_type_or_None, compiled_regex, reason)
1529
+ # vuln_type=None β†’ applies to ALL vulnerability types
1530
+ # ══════════════════════════════════════════════════════════
1531
+ _FP_SUPPRESSIONS = [
1532
+
1533
+ # ── SQL: dynamic ? placeholder builder ─────────────────
1534
+ # emails.map(() => '?') / ids.map((_) => '?')
1535
+ # Generates "?,?,?" bound values β€” never injects user data
1536
+ (None,
1537
+ re.compile(r'\$\{[^}]+\.map\s*\(\s*[^)]*\)\s*\.?(join\s*\(|map\s*\()?[^}]*\?[^}]*\}', re.IGNORECASE),
1538
+ "Dynamic ? placeholder builder β€” values are bound, not interpolated"),
1539
+
1540
+ # ── SQL: ternary returning SQL keyword literals only ────
1541
+ # ${isUnique ? 'UNIQUE ' : ''} / ${desc ? 'DESC' : 'ASC'}
1542
+ (None,
1543
+ re.compile(r'\$\{\s*\w+\s*\?\s*[\'"][A-Z_\s]*[\'"]\s*:\s*[\'"][A-Z_\s]*[\'"]\s*\}', re.IGNORECASE),
1544
+ "Ternary returning fixed SQL keyword literals β€” not user-controlled"),
1545
+
1546
+ # ── SSRF: file:// scheme used for module/script loading ─
1547
+ # file://${entrypointPath} β€” local filesystem, not HTTP
1548
+ ("SSRF",
1549
+ re.compile(r'file://\$\{', re.IGNORECASE),
1550
+ "file:// URL scheme for local module loading β€” not an HTTP SSRF vector"),
1551
+
1552
+ # ── File Upload: type-only declarations, not handlers ───
1553
+ # import type { BusboyFileStream } / : BusboyFileStream
1554
+ ("Insecure File Upload",
1555
+ re.compile(r'(?:import\s+type\b|:\s*(?:BusboyFileStream|Readable|Stream|FileStream)\b)', re.IGNORECASE),
1556
+ "Type-only reference β€” no actual upload handler present on this line"),
1557
+
1558
+ # ── SQL: CREATE INDEX with boolean flag only ────────────
1559
+ # CREATE ${flag ? 'UNIQUE ' : ''}INDEX β€” flag is internal boolean
1560
+ (None,
1561
+ re.compile(r'CREATE\s+\$\{\s*\w+\s*\?\s*[\'"]UNIQUE\s*[\'"]\s*:\s*[\'"][\'"]', re.IGNORECASE),
1562
+ "CREATE INDEX with internal boolean flag β€” not user-controlled"),
1563
+
1564
+ # ── Open Redirect: variable assigned but not yet used ───
1565
+ # let redirect = req.query[...] β€” assignment line alone is not a redirect
1566
+ ("Open Redirect",
1567
+ re.compile(r'^\s*(let|const|var)\s+\w+\s*=\s*req\.(query|body|params)', re.IGNORECASE),
1568
+ "Variable assignment only β€” redirect not issued on this line"),
1569
+ ]
1570
+
1571
+
1572
+ def _is_suppressed(vuln: str, line: str) -> tuple:
1573
+ """
1574
+ Returns (True, reason) if this finding should be suppressed as a known false positive.
1575
+ Returns (False, '') otherwise.
1576
+ """
1577
+ for fp_vuln, fp_re, reason in _FP_SUPPRESSIONS:
1578
+ if fp_vuln and fp_vuln != vuln:
1579
+ continue
1580
+ if fp_re.search(line):
1581
+ return True, reason
1582
+ return False, ""
1583
+
1584
 
1585
  def scan_file_content(content, filename):
1586
  findings = []
 
1596
  if not re.search(pattern, line):
1597
  continue
1598
 
1599
+ # ── Permanent FP suppression layer ──────────────
1600
+ suppressed, _ = _is_suppressed(vuln, line)
1601
+ if suppressed:
1602
+ continue
1603
+
1604
  # ── Fix 1: SQL Injection mislabeled as Hardcoded Secret ──
1605
  # If the matched line contains SQL keywords AND a template/f-string
1606
  # interpolation with user input, it's SQL Injection, not a secret.