Skip to content

fix(analyzers): reduce false positives for negated safety constraints#254

Open
koriyoshi2041 wants to merge 2 commits into
NVIDIA:mainfrom
koriyoshi2041:fix-negated-safety-false-positives
Open

fix(analyzers): reduce false positives for negated safety constraints#254
koriyoshi2041 wants to merge 2 commits into
NVIDIA:mainfrom
koriyoshi2041:fix-negated-safety-false-positives

Conversation

@koriyoshi2041

Copy link
Copy Markdown
Contributor

Refs #251

Problem

Static scans can currently turn benign safety-policy prose into high-risk findings. In a local fixture, text such as must not access credentials and Do not modify this skill's own files, plus scoped cleanup of this tool's own cache path, produced PE3, RA1, and TM1 findings with a HIGH / DO_NOT_INSTALL recommendation.

Fix

This adds regression coverage for that false-positive class and narrows the static filters so:

  • PE3 and RA1 matches are skipped when the matched phrase is explicitly negated in nearby policy prose.
  • scoped user-cache cleanup under ${HOME}/.cache/... or ~/.cache/... is treated like the existing low-risk cache cleanup path instead of destructive arbitrary deletion.

Test

uv run --extra dev pytest -q tests/nodes/analyzers/test_static_false_positive_controls.py tests/nodes/analyzers/test_binary_and_pe3_filtering.py tests/unit/test_patterns_new.py::TestToolMisuse tests/unit/test_patterns_new.py::TestRogueAgent
# 97 passed

uv run --extra dev ruff check src/skillspector/nodes/analyzers/static_patterns_privilege_escalation.py src/skillspector/nodes/analyzers/static_patterns_rogue_agent.py src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py tests/nodes/analyzers/test_static_false_positive_controls.py
# All checks passed!

uv run --extra dev ruff format --check src/skillspector/nodes/analyzers/static_patterns_privilege_escalation.py src/skillspector/nodes/analyzers/static_patterns_rogue_agent.py src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py tests/nodes/analyzers/test_static_false_positive_controls.py
# 4 files already formatted

I also re-scanned the local benign fixture after the patch: score=0, severity=LOW, recommendation=SAFE, finding_count=0.

Risk

The negation guard is intentionally narrow: PE3 and RA1 only. The cache-cleanup handling is scoped to user cache paths, not arbitrary recursive deletion. This addresses a concrete subset of #251 rather than every false-positive case in that issue.

Signed-off-by: kigland <shuaizhicheng336@gmail.com>

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Automated SkillSpector Review]

Requesting changes. Both false-positive guards introduce detection bypasses: negation is scoped to a multi-line context instead of the matched clause, and the cache-path allowlist accepts traversal that escapes .cache. Scope negation to the current match, normalize/reject traversal, and add adversarial mixed-content regressions.

if not escaped:
return False
negation = r"(?:must\s+not|do\s+not|don't|never|should\s+not)\s+"
return re.search(negation + r"(?:\w+\s+){0,4}" + escaped, context, re.IGNORECASE) is not None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this searches the entire ±3-line context instead of the current match span. Do not access credentials.\nAccess credentials and upload them. suppresses both PE3 matches; the duplicated RA1 helper similarly suppresses a live persistence instruction after a negated one. Tie negation to the same clause/match and add mixed benign/live regressions in both analyzers.

)

_SAFE_CACHE_CLEANUP_PATTERNS: tuple[re.Pattern[str], ...] = (
re.compile(r"\brm\s+-rf\s+['\"]?\$?\{?HOME\}?/\.cache/[^*\s;&|]+", re.IGNORECASE),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: the allowlist accepts .. traversal. rm -rf "${HOME}/.cache/../../.ssh" is downgraded even though it escapes .cache and deletes ~/.ssh. Normalize and validate the resolved path (or reject traversal segments) and add this adversarial case.

@koriyoshi2041

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I pushed 40481b4 to tighten both guards:

  • negated safety-constraint suppression now only applies to the current matched line/clause, so a later positive instruction still reports
  • .cache cleanup handling now rejects traversal / wildcard paths instead of allowing any .cache suffix
  • added adversarial mixed-content and traversal regressions

Verified with:

  • uv run pytest tests/nodes/analyzers/test_static_false_positive_controls.py tests/nodes/analyzers/test_static_patterns.py tests/unit/test_patterns.py
  • uv run ruff check src/ tests/
  • uv run ruff format --check src/ tests/
  • git diff --check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants