From 1a990f30628c25fb83d29c4d3b3d18dcb085406e Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:53:00 -0700 Subject: [PATCH] fix(skills): keep scanning link-shaped arguments inside fenced code blocks Masking every balanced [x](dest) in a .md file also blanked `cp [k](../../../.ssh/id_rsa) /tmp` inside a ```sh fence, which main scored caution and the branch let through as safe. A link inside a code fence is a command argument, not a hyperlink: toggle masking off between fence markers. --- tests/tools/test_skills_guard.py | 4 +++- tools/skills_guard.py | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/tools/test_skills_guard.py b/tests/tools/test_skills_guard.py index 80d4dc4405..35d5d9122d 100644 --- a/tests/tools/test_skills_guard.py +++ b/tests/tools/test_skills_guard.py @@ -430,8 +430,10 @@ class TestFalsePositiveReductions: readme.write_text( "See [guide](../../../docs/guide.md) then run `cat ../../../etc/passwd`\n", encoding="utf-8") + fenced = tmp_path / "SKILL.md" + fenced.write_text("```sh\ncp [k](../../../.ssh/id_rsa) /tmp\n```\n", encoding="utf-8") - for path in (script, readme): + for path in (script, readme, fenced): assert any(f.pattern_id == "path_traversal_deep" for f in scan_file(path, path.name)), path.name def test_cat_write_heredoc_is_not_a_secrets_read(self, tmp_path): diff --git a/tools/skills_guard.py b/tools/skills_guard.py index 2cb873b7d4..e14ee6a4fd 100644 --- a/tools/skills_guard.py +++ b/tools/skills_guard.py @@ -512,6 +512,17 @@ def _mask_markdown_link_destinations(line: str) -> str: return "".join(masked) +def _mask_prose_link_destinations(lines: List[str]) -> List[str]: + """Mask link destinations only in Markdown prose. Inside a fenced code block a ``[x](../..)`` is + an argument to whatever command surrounds it, not a hyperlink, so those lines scan verbatim.""" + out, in_fence = [], False + for line in lines: + if line.lstrip().startswith(("```", "~~~")): + in_fence = not in_fence + out.append(line if in_fence else _mask_markdown_link_destinations(line)) + return out + + def scan_file(file_path: Path, rel_path: str = "") -> List[Finding]: """Threat-pattern + invisible-unicode scan of one file; *rel_path* is the display path (default: file name). Regex findings dedupe per pattern per line; invisible chars yield one per line.""" @@ -524,8 +535,7 @@ def scan_file(file_path: Path, rel_path: str = "") -> List[Finding]: return [] findings = [] docstring_lines = _compute_docstring_lines(lines) # so code patterns don't fire on prose - traversal_lines = ([_mask_markdown_link_destinations(line) for line in lines] - if file_path.suffix.lower() == ".md" else lines) + traversal_lines = _mask_prose_link_destinations(lines) if file_path.suffix.lower() == ".md" else lines suffix, owners = file_path.suffix.lower(), _statement_owners(lines) # per-file context for the demotion for pattern, pid, severity, category, description in _COMPILED_THREAT_PATTERNS: for i, line in enumerate(lines, start=1):