Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ab2f21c
fix: preserve JSON grammar for decoded bundle views
yashrajp22 Nov 5, 2026
46a7d7a
fix: keep security rule applicability independent of bundle grammar
yashrajp22 Nov 5, 2026
5542a69
fix: preserve default marker grammar selection for ordinary files
yashrajp22 Nov 5, 2026
caf306b
fix: preserve optional grammar context during marker composition
yashrajp22 Nov 5, 2026
51eb029
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 5, 2026
9ba5201
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 5, 2026
9c3ccb2
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 6, 2026
b850adb
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 6, 2026
80b6625
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 6, 2026
fab641f
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 6, 2026
b0e4339
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 6, 2026
1304717
Merge main into yashraj/oms-projected-json-context
yashrajp22 Nov 6, 2026
7bc67a8
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 6, 2026
8d7d90d
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
1535e6c
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
9b7c676
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
ba76090
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
1ddc00b
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
1ed56b6
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
5719e39
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
81dc66b
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
49c6c75
Merge branch 'main' into yashraj/oms-projected-json-context
github-actions[bot] Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/skillspector/nodes/analyzers/static_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2227,6 +2227,7 @@ def _scan_declared_marker_views(
prepared_analyses: Mapping[int, object] | None = None,
parser_diagnostics: list[StaticParserDiagnostic] | None = None,
python_import_bounds: PythonImportBounds | None = None,
parser_file_type: str | None = None,
parser_analyses: Mapping[int, object] | None = None,
) -> tuple[list[Finding], bool, bool, _StaticResourceLimitError | None]:
"""Reconstruct marker payloads with directive-relative context windows."""
Expand All @@ -2243,6 +2244,7 @@ def check_runtime() -> None:
) from exc

check_runtime()
bounded_file_type = parser_file_type or _infer_file_type(path)
projection_limited = False
bounded_parse_limited = False
seen_views: set[tuple[str, int, int]] = set()
Expand Down Expand Up @@ -2281,7 +2283,7 @@ def defer_output_limit(
# The proof covers the whole artifact, so it holds in every window. It is
# computed only when a loose quoted directive header reaches a quote.
python_strings = (
PythonStringClosers(content, check_runtime) if _infer_file_type(path) == "python" else None
PythonStringClosers(content, check_runtime) if bounded_file_type == "python" else None
)

for owned_start, raw_start in zip(owned_starts, raw_starts, strict=True):
Expand Down Expand Up @@ -2440,7 +2442,7 @@ def defer_output_limit(
module,
marker_view,
check_runtime,
file_type=_infer_file_type(path),
file_type=bounded_file_type,
complete_context=complete_context,
source_start=raw_start,
diagnostics=parser_diagnostics,
Expand Down Expand Up @@ -2620,6 +2622,9 @@ def _scan_all_views_with_parser_diagnostics(
if isinstance(diagnostic, StaticParserDiagnostic):
parser_diagnostics.append(diagnostic.enclosed(0, len(content)))
return findings, reason, metrics
# Decode ownership uses JSON grammar; rule applicability retains the carrier
# type so opaque and unknown bundle fields keep their original security checks.
parser_file_type = "json" if _oms_projected else _infer_file_type(path)
ast_modules = [module for module in pattern_modules if _uses_python_ast(module)]
lexical_modules = [module for module in pattern_modules if not _uses_python_ast(module)]
if python_source is None:
Expand Down Expand Up @@ -2809,6 +2814,7 @@ def limited_result(
prepared_analyses=prepared_analyses,
parser_diagnostics=parser_diagnostics,
python_import_bounds=python_import_bounds,
parser_file_type="json" if _oms_projected else None,
parser_analyses=parser_analyses,
)
bounded_parse_limited = bounded_parse_limited or marker_bounded_parse_limited
Expand Down Expand Up @@ -2850,7 +2856,7 @@ def limited_result(
module,
SecurityTextView("raw", content),
finding_budget.check_runtime,
file_type=_infer_file_type(path),
file_type=parser_file_type,
complete_context=True,
diagnostics=parser_diagnostics,
path=path,
Expand Down Expand Up @@ -2973,10 +2979,12 @@ def limited_result(
)
if callable(exhaustion_hook) and full_view.name != "raw":
finding_budget.check_runtime()
module_file_type = _effective_module_file_type(
path,
module,
python_source=python_source,
module_file_type = (
parser_file_type
if _oms_projected
else _effective_module_file_type(
path, module, python_source=python_source
)
)
bounded_parse_limited = bounded_parse_limited or bool(
_bounded_parser_incomplete(
Expand Down
57 changes: 57 additions & 0 deletions tests/nodes/analyzers/test_oms_projected_file_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Decoded bundle syntax is independent of the carrier filename."""

import json

import pytest

from skillspector.artifacts import SecurityTextView
from skillspector.nodes.analyzers import static_patterns_tool_misuse as tm
from skillspector.nodes.analyzers import static_runner
from skillspector.oms import OMSProjection


def _scan(monkeypatch, path, projected, *, complete=True):
carrier = '{"dsseEnvelope": "synthetic carrier"}'
monkeypatch.setattr(
static_runner,
"project_oms_content",
lambda _content: OMSProjection(SecurityTextView("oms-decoded", projected), complete),
)
return static_runner._scan_all_views_detailed(path, carrier, [tm], None)


@pytest.mark.parametrize("path", ["skill.oms.sig", "bundle.json", "notes.md", "bundle.py"])
def test_projected_json_owns_quotes_regardless_of_carrier_name(monkeypatch, path):
projected = json.dumps({"readable": ["label`with'apostrophe", "ordinary reference"]})
findings, reason, metrics = _scan(monkeypatch, path, projected)
assert findings == []
assert reason is None
assert getattr(metrics, "parser_diagnostic", None) is None


@pytest.mark.parametrize("complete", [False, True])
@pytest.mark.parametrize("path", ["skill.oms.sig", "bundle.json", "bundle.py"])
def test_projected_commands_keep_findings_and_original_carrier(monkeypatch, path, complete):
projected = json.dumps({"instruction": "rm -rf /"})
findings, _, _ = _scan(monkeypatch, path, projected, complete=complete)
destructive = [finding for finding in findings if finding.rule_id == "TM1"]
assert destructive
assert all(
finding.file == path and finding.start_line == 1 and finding.evidence.get("oms_projection")
for finding in destructive
)


def test_unrecognized_raw_bundle_keeps_runtime_diagnostic(monkeypatch):
monkeypatch.setattr(
static_runner,
"project_oms_content",
lambda content: OMSProjection(SecurityTextView("raw", content), False),
)
_, _, metrics = static_runner._scan_all_views_detailed(
"skill.oms.sig", 'sh -c "$CMD -rf /"', [tm], None
)
assert metrics.parser_diagnostic is not None
Loading