fix: recognise fences opened inside list items and blockquotes

Review finding on #112198: _mask_prose_link_destinations matched
_FENCE_LINE against the raw line, so a fence behind a CommonMark
container prefix (`- ```sh`, `1. ```sh`, `> ```sh`, nested) was not
seen and its body was scored as prose with link destinations masked.
Strip the container prefix before fence matching (open and close).
Bundled-skill rescan vs origin/main: 208 skills, 1447 findings on
both, no new/gone findings, no verdict changes.
This commit is contained in:
teknium1
2026-09-15 14:31:01 -07:00
committed by Teknium
parent 33292affd6
commit 2588c908e7
2 changed files with 25 additions and 1 deletions

View File

@@ -488,6 +488,27 @@ class TestFalsePositiveReductions:
prose.write_text("```sh\necho hi\n```\nSee [the guide](../../../docs/guide.md).\n", encoding="utf-8")
assert not any(f.category == "traversal" for f in scan_file(prose, prose.name))
def test_fence_opened_inside_list_or_blockquote_is_code(self, tmp_path):
# A fence may sit inside a CommonMark container (bullet, ordered item, blockquote,
# nested); the container prefix must not hide the fence, or its body scans as prose.
payload = "cp [k](../../../.ssh/id_rsa) /tmp/x\n"
code_shapes = {
"bullet": "- ```sh\n " + payload + " ```\n",
"ordered": "1. ```sh\n " + payload + " ```\n",
"blockquote": "> ```sh\n> " + payload + "> ```\n",
"blockquote_bullet": "> - ```sh\n> " + payload + "> ```\n",
}
for label, body in code_shapes.items():
md = tmp_path / f"{label}.md"
md.write_text(body, encoding="utf-8")
assert any(f.pattern_id == "path_traversal_deep" for f in scan_file(md, md.name)), label
# Control: the container fence closes too, so a prose link in a later bullet stays exempt.
prose = tmp_path / "prose.md"
prose.write_text("> ```sh\n> echo hi\n> ```\n\n- See [the guide](../../../docs/guide.md).\n",
encoding="utf-8")
assert not any(f.category == "traversal" for f in scan_file(prose, prose.name))
def test_cat_write_heredoc_is_not_a_secrets_read(self, tmp_path):
# Setup doc telling the user to write their OWN keys into their OWN
# local .env via a heredoc — writes in, does not exfiltrate out.

View File

@@ -523,6 +523,9 @@ def _mask_markdown_link_destinations(line: str) -> str:
# fence uses the same marker, is at least as long, and carries nothing else — so a ``~~~`` line
# inside a backtick fence, a shorter fence, or a fence line with an info string is all content.
_FENCE_LINE = re.compile(r"^ {0,3}(?P<marker>`{3,}|~{3,})(?P<info>.*)$")
# A fence may open (and close) inside a container: a bullet ``- ```sh``, an ordered item ``1. ```sh``,
# a blockquote ``> ```sh``, or a nest of them (§5.1/§5.2). Strip those prefixes before fence matching.
_CONTAINER_PREFIX = re.compile(r"^(?: {0,3}(?:>|(?:[-*+]|\d{1,9}[.)]) {1,4}))+")
def _mask_prose_link_destinations(lines: List[str]) -> List[str]:
@@ -534,7 +537,7 @@ def _mask_prose_link_destinations(lines: List[str]) -> List[str]:
out: List[str] = []
fence = None # (marker char, opener length) while a fenced block is open
for line in lines:
match = _FENCE_LINE.match(line)
match = _FENCE_LINE.match(_CONTAINER_PREFIX.sub("", line))
if fence is not None:
if (match and match["marker"][0] == fence[0] and len(match["marker"]) >= fence[1]
and not match["info"].strip()):