Skip to content

Commit 0be9383

Browse files
committed
test(tools): filter spurious bandit nosec-bookkeeping warnings
bandit 1.9 emits two classes of stderr WARNING messages that come from its internal `# nosec` bookkeeping and do not correspond to any actual security finding: - "nosec encountered (BXXX), but no failed test": bandit runs each test against every AST node on a line, not just the node that actually produces the finding. When several sub-nodes share a line and only one of them triggers the test, bandit sees the `# nosec BXXX` from the context of the other sub-nodes too and warns that the nosec was never used, even though it was used by the one sub-node that mattered. - "Test in comment: WORD is not a test name or id, ignoring": bandit tokenizes every word after `# nosec` and tries to match it against its test-id catalog. Free-text rationales on the same line trip this; the previous commit already removed them, but the wrapper stays resilient against future regressions. Both warnings are logged at WARNING level on stderr and do not affect bandit's exit code. `tools/run-linter-checks` now captures bandit's stderr and drops lines matching either pattern before echoing the rest to the wrapper's stderr. Real security findings still surface because bandit is invoked at `--severity-level=high --confidence-level=high` and would exit non-zero on any actual issue. The wrapper now reports `All checks passed!` on a clean tree with no stderr noise so the sweep is usable without further grep filtering.
1 parent 8b21891 commit 0be9383

1 file changed

Lines changed: 57 additions & 2 deletions

File tree

tools/run-linter-checks

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,52 @@ Usage:
3131

3232
import argparse
3333
import os
34+
import re
3435
import subprocess
3536
import sys
3637

3738
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
3839

40+
# bandit emits two classes of stderr WARNING messages that come from its
41+
# internal `# nosec` bookkeeping and do not correspond to any actual
42+
# security finding. Both look alarming in the wrapper's output but cannot
43+
# be fixed at the source level without rewriting otherwise-correct code:
44+
#
45+
# 1. `nosec encountered (BXXX), but no failed test` - bandit runs each
46+
# test against every AST node on a given line, not just the node
47+
# that would actually produce the finding. When several sub-nodes
48+
# share a line and only one of them triggers the test, bandit sees
49+
# the `# nosec BXXX` comment from the context of the other sub-nodes
50+
# too and warns that the nosec "was never used", even though it was
51+
# used by the one sub-node that mattered.
52+
#
53+
# 2. `Test in comment: X is not a test name or id, ignoring` - bandit
54+
# tokenizes every word after `# nosec` and tries to match each one
55+
# against its test id catalog. Free-text explanations on the same
56+
# line (e.g. `# nosec B105 - Keycloak install default, ...`) trip
57+
# this. We keep source-level comments free of prose after `# nosec`
58+
# by convention, but upstream libs occasionally slip through.
59+
#
60+
# Both warnings are logged at WARNING level on stderr and do not affect
61+
# bandit's exit code. We filter them here so the linter sweep stays
62+
# readable; real security issues still surface because bandit is run at
63+
# `--severity-level=high --confidence-level=high` and would exit non-zero
64+
# on any actual finding.
65+
BANDIT_NOISE_PATTERNS = (
66+
re.compile(
67+
r'^\[tester\]\s+WARNING\s+nosec encountered \(B\d+\), '
68+
r'but no failed test on file'
69+
),
70+
re.compile(
71+
r'^\[manager\]\s+WARNING\s+Test in comment: .* '
72+
r'is not a test name or id, ignoring'
73+
),
74+
)
75+
76+
77+
def _bandit_line_is_noise(line):
78+
return any(pattern.search(line) for pattern in BANDIT_NOISE_PATTERNS)
79+
3980

4081
def discover_python_files():
4182
"""Discover every Python file in the repo.
@@ -66,10 +107,23 @@ def discover_python_files():
66107
return sorted(python_files)
67108

68109

69-
def run(analyzer, argv):
110+
def run(analyzer, argv, stderr_filter=None):
70111
print(f'\n=== {analyzer} ===', flush=True)
71112
try:
72-
result = subprocess.run(argv, cwd=REPO_ROOT, check=False)
113+
if stderr_filter is not None:
114+
result = subprocess.run(
115+
argv,
116+
cwd=REPO_ROOT,
117+
check=False,
118+
stderr=subprocess.PIPE,
119+
text=True,
120+
)
121+
for line in result.stderr.splitlines():
122+
if not stderr_filter(line):
123+
sys.stderr.write(line + '\n')
124+
sys.stderr.flush()
125+
else:
126+
result = subprocess.run(argv, cwd=REPO_ROOT, check=False)
73127
except FileNotFoundError:
74128
print(f'{analyzer}: not installed, skipping')
75129
return 0
@@ -119,6 +173,7 @@ def main():
119173
'--confidence-level=high',
120174
*python_files,
121175
],
176+
stderr_filter=_bandit_line_is_noise,
122177
)
123178

124179
if selected is None or 'vulture' in selected:

0 commit comments

Comments
 (0)