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.
This commit is contained in:
teknium1
2026-09-14 15:53:00 -07:00
committed by Teknium
parent 5f6e3a6fe3
commit 1a990f3062
2 changed files with 15 additions and 3 deletions

View File

@@ -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):

View File

@@ -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):