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:
@@ -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.
|
||||
|
||||
@@ -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()):
|
||||
|
||||
Reference in New Issue
Block a user