Summary
There is an inconsistency between what ash config lint tells the user about a suppression with only line_start set, and what _line_range_matches actually does at scan time.
- Lint message (
config_linter.py::_check_suppression_issues):
"Suppression has 'line_start' (N) but missing 'line_end' — will default to line_start value"
- Matcher (
suppression_matcher.py::_line_range_matches) treats line_end=None as open-ended ("from line_start to end of file"), not as line_end=line_start.
The matcher and the suppression-id generator also disagree with each other:
_line_range_matches (matching) treats missing line_end as +∞.
_get_suppression_id (in both sarif_utils.py and unused_suppressions_reporter.py) substitutes line_start for missing line_end when building the dedupe key.
Why it matters
- A user sets
line_start: 42 expecting "only line 42", but every finding on line ≥ 42 in that file matches. This silently over-suppresses.
- The lint warning gives users false confidence about the runtime semantics.
Reproduction
global_settings:
suppressions:
- path: "src/foo.py"
rule_id: "B201"
line_start: 42
reason: "False positive"
A finding at src/foo.py:99 with rule_id=B201 is suppressed, even though the user only specified line 42.
Relevant code
automated_security_helper/utils/suppression_matcher.py::_line_range_matches — branch if suppression.line_start is not None and suppression.line_end is None: returns true for finding_end >= suppression.line_start.
automated_security_helper/config/config_linter.py::_check_suppression_issues — emits the misleading "will default to line_start value" message.
automated_security_helper/utils/sarif_utils.py::_get_suppression_id and automated_security_helper/plugin_modules/ash_builtin/reporters/unused_suppressions_reporter.py::_get_suppression_id — substitute line_start for missing line_end in the ID.
automated_security_helper/models/core.py::AshSuppression — line_end description is just (Optional) Ending line number; doesn't document missing-value semantics.
Proposed fix (pick one and align everything)
Option A — make missing line_end mean "single line" (matches lint message, matches user intuition, matches _get_suppression_id):
- Change
_line_range_matches to default line_end = line_start when missing.
- Update
AshSuppression.line_end docstring to say so.
- Keep current lint message and
_get_suppression_id behavior.
Option B — keep open-ended semantics (current matcher behavior):
- Update
_line_range_matches docstring to document open-ended behavior.
- Update lint message in
_check_suppression_issues to:
"Suppression has 'line_start' (N) but missing 'line_end' — will match all findings from line N to end of file. Set line_end explicitly."
- Update
_get_suppression_id in both sarif_utils.py and unused_suppressions_reporter.py to use a sentinel like * (or EOF) instead of line_start for missing line_end, so unused-suppression dedupe matches the actual semantics.
- Update
AshSuppression.line_end docstring.
Option A is the lower-risk choice; Option B is a behavior change for anyone relying on the current open-ended behavior.
Acceptance criteria
ash config lint message and the runtime matcher behavior agree.
_get_suppression_id is consistent across sarif_utils.py and unused_suppressions_reporter.py and reflects the chosen semantics.
AshSuppression.line_end docstring documents the semantics of the missing case.
- Tests in
tests/unit/utils/test_suppression_matcher_extended.py cover the missing-line_end case explicitly.
Summary
There is an inconsistency between what
ash config linttells the user about a suppression with onlyline_startset, and what_line_range_matchesactually does at scan time.config_linter.py::_check_suppression_issues):suppression_matcher.py::_line_range_matches) treatsline_end=Noneas open-ended ("fromline_startto end of file"), not asline_end=line_start.The matcher and the suppression-id generator also disagree with each other:
_line_range_matches(matching) treats missingline_endas+∞._get_suppression_id(in bothsarif_utils.pyandunused_suppressions_reporter.py) substitutesline_startfor missingline_endwhen building the dedupe key.Why it matters
line_start: 42expecting "only line 42", but every finding on line ≥ 42 in that file matches. This silently over-suppresses.Reproduction
A finding at
src/foo.py:99withrule_id=B201is suppressed, even though the user only specified line 42.Relevant code
automated_security_helper/utils/suppression_matcher.py::_line_range_matches— branchif suppression.line_start is not None and suppression.line_end is None:returns true forfinding_end >= suppression.line_start.automated_security_helper/config/config_linter.py::_check_suppression_issues— emits the misleading "will default to line_start value" message.automated_security_helper/utils/sarif_utils.py::_get_suppression_idandautomated_security_helper/plugin_modules/ash_builtin/reporters/unused_suppressions_reporter.py::_get_suppression_id— substituteline_startfor missingline_endin the ID.automated_security_helper/models/core.py::AshSuppression—line_enddescription is just(Optional) Ending line number; doesn't document missing-value semantics.Proposed fix (pick one and align everything)
Option A — make missing
line_endmean "single line" (matches lint message, matches user intuition, matches_get_suppression_id):_line_range_matchesto defaultline_end = line_startwhen missing.AshSuppression.line_enddocstring to say so._get_suppression_idbehavior.Option B — keep open-ended semantics (current matcher behavior):
_line_range_matchesdocstring to document open-ended behavior._check_suppression_issuesto:_get_suppression_idin bothsarif_utils.pyandunused_suppressions_reporter.pyto use a sentinel like*(orEOF) instead ofline_startfor missingline_end, so unused-suppression dedupe matches the actual semantics.AshSuppression.line_enddocstring.Option A is the lower-risk choice; Option B is a behavior change for anyone relying on the current open-ended behavior.
Acceptance criteria
ash config lintmessage and the runtime matcher behavior agree._get_suppression_idis consistent acrosssarif_utils.pyandunused_suppressions_reporter.pyand reflects the chosen semantics.AshSuppression.line_enddocstring documents the semantics of the missing case.tests/unit/utils/test_suppression_matcher_extended.pycover the missing-line_endcase explicitly.