From 26e97ec64bb02ab775748ffeb67c0c1ed58127de Mon Sep 17 00:00:00 2001 From: yashrajbasav Date: Fri, 6 Nov 2026 03:27:29 +0530 Subject: [PATCH 1/3] fix: confine failed shell words to proven Python values Signed-off-by: yashrajbasav --- .../analyzers/static_patterns_tool_misuse.py | 17 +++ .../analyzers/test_python_host_word_bounds.py | 109 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 tests/nodes/analyzers/test_python_host_word_bounds.py diff --git a/src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py b/src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py index f8702c868..56e41e67a 100644 --- a/src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py +++ b/src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py @@ -3690,6 +3690,23 @@ def _has_shell_command_word_exhaustion( # spans and runtime-selected operands keep their existing # conservative treatment. continue + if python_word_source is not None and ownership_boundary < parsed.end: + # A raw shell word may pair Python delimiters across unrelated + # tokens. Recheck only the complete value the native owner proves. + value_source_range: list[int] = [] + value = python_word_source.string_value( + start, parsed.end, source_range=value_source_range + ) + if value is not None: + value_diagnostic = _has_shell_command_word_exhaustion( + value[0], + check_runtime, + _command_string_depth=_command_string_depth, + _quoted_printf_allowed=quoted_printf_allowed, + ) + if value_diagnostic is not None: + return value_diagnostic.enclosed(*value_source_range) + continue if not _quoted_candidate_is_complete( content, start, quote_limits, check_runtime, quoted_printf_allowed ): diff --git a/tests/nodes/analyzers/test_python_host_word_bounds.py b/tests/nodes/analyzers/test_python_host_word_bounds.py new file mode 100644 index 000000000..067c8509c --- /dev/null +++ b/tests/nodes/analyzers/test_python_host_word_bounds.py @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""A failed shell word cannot borrow unrelated Python string delimiters.""" + +from __future__ import annotations + +import pytest + +from skillspector.inspection_ledger import LedgerOutcome, LedgerReason, ParserCause +from skillspector.nodes.analyzers import static_patterns_tool_misuse as tm +from skillspector.nodes.analyzers import static_runner + +# Alternating Python delimiters make a raw shell word charge later token bodies +# to the first value. Each native value remains short and independent. +_TAIL = "".join(f'FIELD_{index} = "ordinary_value_{index}"\n' for index in range(600)) + + +def _diagnostic(source: str): + return tm.bounded_parse_diagnostic(source, lambda: None, file_type="python") + + +@pytest.mark.parametrize("newline", ["\n", "\r\n"]) +@pytest.mark.parametrize( + "statement", + [ + 'def read(access: str = "read"):\n return access\n', + 'RECORD = {"status": "diagnostic_only"}\n', + 'STATES = {"resource_limited", "completed"}\n', + 'PREFIXES = ("run_recomputed_reference_", "run_default_")\n', + 'MESSAGE = "café 🧪 literal"\n', + ], +) +def test_short_python_values_do_not_consume_later_token_bodies( + statement: str, newline: str +) -> None: + source = (statement + _TAIL).replace("\n", newline) + + assert _diagnostic(source) is None + + +@pytest.mark.parametrize( + "extension", [" + unknown", " % unknown", ".format(unknown)", " * count", "[part:]"] +) +def test_unproved_value_extensions_keep_the_original_failure(extension: str) -> None: + source = 'VALUE = "ordinary"' + extension + "\n" + _TAIL + + assert _diagnostic(source) is not None + + +@pytest.mark.parametrize( + "statement", + [ + 'COMMAND = ("$TOOL " "-rf /")\n', + 'COMMAND = "$TOOL " + "-rf /"\n', + 'COMMAND = f"$TOOL {flags} /"\n', + 'COMMAND = "$TOOL {} /".format(flags)\n', + 'COMMAND = "$TOOL %s /" % flags\n', + 'COMMAND = ("$TOOL ") + suffix\n', + ], +) +def test_later_dynamic_command_values_remain_independently_partial(statement: str) -> None: + source = 'FIRST = "ordinary"\n' + _TAIL + statement + + assert _diagnostic(source) is not None + + +def test_a_genuinely_over_budget_python_value_keeps_the_limit() -> None: + source = 'VALUE = "' + "r" * 4097 + '"\n' + _TAIL + + diagnostic = _diagnostic(source) + assert diagnostic is not None + assert diagnostic.cause is ParserCause.RESOURCE_LIMIT + assert diagnostic.measurements["observed_characters"] == 4097 + assert diagnostic.measurements["limit_characters"] == 4096 + + +def test_native_ownership_requires_complete_valid_python() -> None: + source = 'VALUE = "ordinary"\n' + _TAIL + "def broken(:\n" + + assert _diagnostic(source) is not None + assert ( + tm.bounded_parse_diagnostic( + source, lambda: None, file_type="python", complete_context=False + ) + is not None + ) + + +def test_later_raw_high_findings_are_preserved_after_value_reassessment() -> None: + source = 'FIRST = "ordinary"\n' + _TAIL + 'DANGEROUS = "rm -rf /"\n' + result = static_runner.run_static_patterns_with_ledger( + {"components": ["script.py"], "file_cache": {"script.py": source}}, [tm] + ) + + assert any(f.rule_id == "TM1" and f.severity == "HIGH" for f in result["findings"]) + assert result["inspection_ledger"][0]["outcome"] is LedgerOutcome.COMPLETED + + +def test_later_runtime_limit_reaches_the_ledger() -> None: + source = 'FIRST = "ordinary"\n' + _TAIL + 'COMMAND = "$TOOL -rf /"\n' + result = static_runner.run_static_patterns_with_ledger( + {"components": ["script.py"], "file_cache": {"script.py": source}}, [tm] + ) + + assert any( + event.get("reason_code") is LedgerReason.STATIC_PARSE_LIMIT + for event in result["inspection_ledger"] + ) From f73de0419c539b3cdc9a318470a0d03348e22b05 Mon Sep 17 00:00:00 2001 From: yashrajbasav Date: Fri, 6 Nov 2026 03:36:05 +0530 Subject: [PATCH 2/3] test: exercise guarded Python value boundaries with executable operands Signed-off-by: yashrajbasav --- tests/nodes/analyzers/test_python_host_word_bounds.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/nodes/analyzers/test_python_host_word_bounds.py b/tests/nodes/analyzers/test_python_host_word_bounds.py index 067c8509c..121cf9cd9 100644 --- a/tests/nodes/analyzers/test_python_host_word_bounds.py +++ b/tests/nodes/analyzers/test_python_host_word_bounds.py @@ -14,6 +14,7 @@ # Alternating Python delimiters make a raw shell word charge later token bodies # to the first value. Each native value remains short and independent. _TAIL = "".join(f'FIELD_{index} = "ordinary_value_{index}"\n' for index in range(600)) +_OPTION_TEXT = 'BUILD_ARGS = ["--recursive", "--force", "/tmp/build"]\n' def _diagnostic(source: str): @@ -43,7 +44,10 @@ def test_short_python_values_do_not_consume_later_token_bodies( "extension", [" + unknown", " % unknown", ".format(unknown)", " * count", "[part:]"] ) def test_unproved_value_extensions_keep_the_original_failure(extension: str) -> None: - source = 'VALUE = "ordinary"' + extension + "\n" + _TAIL + source = 'def read(access: str = "read"' + extension + "):\n return access\n" + _TAIL + owner = tm._PythonSourceOwnership(source, lambda: None) + start = source.index('read"') + assert owner.string_value(start, start + len("read")) is None assert _diagnostic(source) is not None @@ -60,7 +64,7 @@ def test_unproved_value_extensions_keep_the_original_failure(extension: str) -> ], ) def test_later_dynamic_command_values_remain_independently_partial(statement: str) -> None: - source = 'FIRST = "ordinary"\n' + _TAIL + statement + source = _OPTION_TEXT + 'FIRST = "ordinary"\n' + _TAIL + statement + _TAIL assert _diagnostic(source) is not None @@ -76,7 +80,8 @@ def test_a_genuinely_over_budget_python_value_keeps_the_limit() -> None: def test_native_ownership_requires_complete_valid_python() -> None: - source = 'VALUE = "ordinary"\n' + _TAIL + "def broken(:\n" + source = 'FENCE = "\\n```\\n"\n' + _TAIL + "def broken(:\n" + assert tm._python_literal_spans(source, lambda: None) is None assert _diagnostic(source) is not None assert ( From 701a20f32064ae8dde13f751f5a9422cae00a0a6 Mon Sep 17 00:00:00 2001 From: yashrajbasav Date: Fri, 6 Nov 2026 03:43:00 +0530 Subject: [PATCH 3/3] test: retain evidenced later command and word-limit controls Signed-off-by: yashrajbasav --- .../analyzers/test_python_host_word_bounds.py | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/tests/nodes/analyzers/test_python_host_word_bounds.py b/tests/nodes/analyzers/test_python_host_word_bounds.py index 121cf9cd9..fb4e4c11e 100644 --- a/tests/nodes/analyzers/test_python_host_word_bounds.py +++ b/tests/nodes/analyzers/test_python_host_word_bounds.py @@ -14,7 +14,6 @@ # Alternating Python delimiters make a raw shell word charge later token bodies # to the first value. Each native value remains short and independent. _TAIL = "".join(f'FIELD_{index} = "ordinary_value_{index}"\n' for index in range(600)) -_OPTION_TEXT = 'BUILD_ARGS = ["--recursive", "--force", "/tmp/build"]\n' def _diagnostic(source: str): @@ -52,25 +51,17 @@ def test_unproved_value_extensions_keep_the_original_failure(extension: str) -> assert _diagnostic(source) is not None -@pytest.mark.parametrize( - "statement", - [ - 'COMMAND = ("$TOOL " "-rf /")\n', - 'COMMAND = "$TOOL " + "-rf /"\n', - 'COMMAND = f"$TOOL {flags} /"\n', - 'COMMAND = "$TOOL {} /".format(flags)\n', - 'COMMAND = "$TOOL %s /" % flags\n', - 'COMMAND = ("$TOOL ") + suffix\n', - ], -) -def test_later_dynamic_command_values_remain_independently_partial(statement: str) -> None: - source = _OPTION_TEXT + 'FIRST = "ordinary"\n' + _TAIL + statement + _TAIL +def test_later_runtime_command_remains_independently_partial() -> None: + source = 'FIRST = "ordinary"\n' + _TAIL + 'COMMAND = "$TOOL -rf /"\n' - assert _diagnostic(source) is not None + diagnostic = _diagnostic(source) + assert diagnostic is not None + assert diagnostic.cause is ParserCause.RUNTIME_DEPENDENT -def test_a_genuinely_over_budget_python_value_keeps_the_limit() -> None: - source = 'VALUE = "' + "r" * 4097 + '"\n' + _TAIL +@pytest.mark.parametrize("prefix", ["", 'FIRST = "ordinary"\n' + _TAIL], ids=["first", "later"]) +def test_a_genuinely_over_budget_python_value_keeps_the_limit(prefix: str) -> None: + source = prefix + 'VALUE = "' + "r" * 4097 + '"\n' + _TAIL diagnostic = _diagnostic(source) assert diagnostic is not None