From bc01847536e628442c738ac967b3fb4d1c450e3a Mon Sep 17 00:00:00 2001
From: WenJing95 <126436871+WenJing95@users.noreply.github.com>
Date: Wed, 21 Oct 2026 17:16:11 +0900
Subject: [PATCH 1/6] fix(reports): keep HTML entities literal in report text
---
plugins/codex-security/scripts/report_projection.py | 4 ++--
.../codex-security/tests/test_report_projection.py | 11 +++++++++++
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/plugins/codex-security/scripts/report_projection.py b/plugins/codex-security/scripts/report_projection.py
index 552f6e106d..ff94cae09f 100644
--- a/plugins/codex-security/scripts/report_projection.py
+++ b/plugins/codex-security/scripts/report_projection.py
@@ -32,7 +32,7 @@ def _text(value: Any, fallback: str) -> str:
if re.match(r"^(?:#{1,6}\s|[-*+]\s|>\s|```|~~~|\d+[.)]\s|\|)", normalized):
normalized = f"Text: {normalized}"
return "".join(
- part if index % 2 else re.sub(r"([\\`*_\[\]<>])", r"\\\1", part)
+ part if index % 2 else re.sub(r"([\\`*_\[\]<>&])", r"\\\1", part)
for index, part in enumerate(re.split(r"((? str:
if label != value:
marker = _code_fence(label, minimum=1)
return f"{marker}{label}{marker}"
- return re.sub(r"([\\`*_\[\]<>])", r"\\\1", value)
+ return re.sub(r"([\\`*_\[\]<>&])", r"\\\1", value)
def _cell(value: Any) -> str:
diff --git a/plugins/codex-security/tests/test_report_projection.py b/plugins/codex-security/tests/test_report_projection.py
index 4a04015af0..4c49911865 100644
--- a/plugins/codex-security/tests/test_report_projection.py
+++ b/plugins/codex-security/tests/test_report_projection.py
@@ -803,6 +803,17 @@ def test_projection_escapes_raw_html_in_markdown() -> None:
assert r'\
' in markdown_text
+def test_projection_keeps_html_entities_literal_in_markdown() -> None:
+ manifest, findings, coverage = canonical_documents()
+ findings["findings"][0]["summary"] = "Sanitizer decodes <script> and <"
+ coverage["includePaths"] = ["docs/<b>.md"]
+
+ markdown_text = PROJECTION.generate_report_markdown(manifest, findings, coverage).decode()
+
+ assert r"Sanitizer decodes \<script\> and \<" in markdown_text
+ assert r"- Included paths: docs/\<b\>.md" in markdown_text
+
+
def test_projection_omits_informational_findings() -> None:
manifest, findings, coverage = canonical_documents()
findings["findings"][0]["severity"]["level"] = "informational"
From ea8d066f0b19a762424de8d6218f727bfdc522a1 Mon Sep 17 00:00:00 2001
From: Codex
Date: Fri, 23 Oct 2026 18:18:25 +0000
Subject: [PATCH 2/6] fix(reports): preserve ampersands in Markdown autolinks
---
.../scripts/report_projection.py | 26 ++++++++---
.../tests/test_report_projection.py | 45 +++++++++++++++++++
2 files changed, 66 insertions(+), 5 deletions(-)
diff --git a/plugins/codex-security/scripts/report_projection.py b/plugins/codex-security/scripts/report_projection.py
index ff94cae09f..69b362b906 100644
--- a/plugins/codex-security/scripts/report_projection.py
+++ b/plugins/codex-security/scripts/report_projection.py
@@ -20,21 +20,37 @@
"needs_follow_up": "Needs follow-up",
}
WRITEUP_REPORT_PATH_RE = re.compile(r"^findings/([a-z0-9][a-z0-9._-]*)/\1\.md$")
+INLINE_CODE_RE = r"(?]+"
class ReportProjectionError(ValueError):
"""Raised when a canonical scan cannot be projected into a valid report."""
+def _escape_markdown(value: str, *, inline_code: bool = False) -> str:
+ # Locate whole URLs before matching code spans: a URL can contain backticks.
+ urls = [match.span() for match in re.finditer(AUTOLINK_RE, value)]
+ punctuation = r"[\\`*_\[\]<>&]"
+ pattern = f"(?P{INLINE_CODE_RE})|{punctuation}" if inline_code else punctuation
+
+ def escape(match: re.Match[str]) -> str:
+ if match.lastgroup == "code":
+ return match[0]
+ # GFM autolinks keep backslashes literally, including before query separators.
+ if match[0] == "&" and any(start <= match.start() < end for start, end in urls):
+ return "&"
+ return "\\" + match[0]
+
+ return re.sub(pattern, escape, value)
+
+
def _text(value: Any, fallback: str) -> str:
candidate = value if isinstance(value, str) and value.strip() else fallback
normalized = " ".join(candidate.split())
if re.match(r"^(?:#{1,6}\s|[-*+]\s|>\s|```|~~~|\d+[.)]\s|\|)", normalized):
normalized = f"Text: {normalized}"
- return "".join(
- part if index % 2 else re.sub(r"([\\`*_\[\]<>&])", r"\\\1", part)
- for index, part in enumerate(re.split(r"((? list[str]:
@@ -57,7 +73,7 @@ def _path_text(value: str) -> str:
if label != value:
marker = _code_fence(label, minimum=1)
return f"{marker}{label}{marker}"
- return re.sub(r"([\\`*_\[\]<>&])", r"\\\1", value)
+ return _escape_markdown(value)
def _cell(value: Any) -> str:
diff --git a/plugins/codex-security/tests/test_report_projection.py b/plugins/codex-security/tests/test_report_projection.py
index 4c49911865..10c32c01c9 100644
--- a/plugins/codex-security/tests/test_report_projection.py
+++ b/plugins/codex-security/tests/test_report_projection.py
@@ -814,6 +814,51 @@ def test_projection_keeps_html_entities_literal_in_markdown() -> None:
assert r"- Included paths: docs/\<b\>.md" in markdown_text
+@pytest.mark.parametrize(
+ "url",
+ [
+ "https://example.com/?first=1&second=2",
+ "http://example.com/?payload=<tag>&mode=1",
+ "www.example.com/?payload=<tag>&mode=1",
+ "ftp://example.com/?first=1&second=2",
+ "https://example.com/`a`?first=1&second=2",
+ "www.example.com/`a`?first=1&second=2",
+ ],
+)
+def test_projection_preserves_bare_url_ampersands_in_prose_and_tables(url: str) -> None:
+ manifest, findings, coverage = canonical_documents()
+ finding = findings["findings"][0]
+ finding["summary"] = f"R&D decoded <tag>. Reproduce with {url} then compare &."
+ finding["confidence"]["rationale"] = f"Request: {url}"
+
+ markdown = PROJECTION.generate_report_markdown(manifest, findings, coverage).decode()
+
+ assert f"Reproduce with {url} then compare \\&." in markdown
+ assert f"| Confidence rationale | Request: {url} |" in markdown
+ assert r"R\&D decoded \<tag\>." in markdown
+
+
+def test_projection_preserves_inline_code_and_existing_url_escapes() -> None:
+ manifest, findings, coverage = canonical_documents()
+ findings["findings"][0]["summary"] = (
+ "Compare `&` and `https://example.com/?a=1&b=2 &` with "
+ "https://example.com/a_b?first=1&second=2 and &. "
+ "Keep https://example.com/`a _b_` as written."
+ )
+ coverage["includePaths"] = [
+ "docs/www.example.com/<tag>.md",
+ "docs/`<tag>`.md",
+ ]
+
+ markdown = PROJECTION.generate_report_markdown(manifest, findings, coverage).decode()
+
+ assert "Compare `&` and `https://example.com/?a=1&b=2 &`" in markdown
+ assert r"https://example.com/a\_b?first=1&second=2 and \&." in markdown
+ assert "Keep https://example.com/`a _b_` as written." in markdown
+ assert r"- Included paths: docs/www.example.com/\<tag\>.md" in markdown
+ assert r"docs/\`\<tag\>\`.md" in markdown
+
+
def test_projection_omits_informational_findings() -> None:
manifest, findings, coverage = canonical_documents()
findings["findings"][0]["severity"]["level"] = "informational"
From 3f6aa7919906dd7344607f6c31e92d091b1a678c Mon Sep 17 00:00:00 2001
From: Codex
Date: Fri, 23 Oct 2026 18:39:51 +0000
Subject: [PATCH 3/6] fix(reports): align native metadata escaping and URL case
---
.../mcp-app/src/threat-model-format.ts | 38 ++++++++++++++++---
.../mcp-app/tests/test_threat_model_render.ts | 36 +++++++++++++++++-
.../scripts/report_projection.py | 2 +-
.../tests/test_report_projection.py | 2 +
4 files changed, 69 insertions(+), 9 deletions(-)
diff --git a/plugins/codex-security/mcp-app/src/threat-model-format.ts b/plugins/codex-security/mcp-app/src/threat-model-format.ts
index c57a771963..fd108aba51 100644
--- a/plugins/codex-security/mcp-app/src/threat-model-format.ts
+++ b/plugins/codex-security/mcp-app/src/threat-model-format.ts
@@ -9,6 +9,37 @@ function collapse(value: string): string {
return value.replace(whitespace, " ").replace(/^ | $/gu, "");
}
+/** Match report_projection._escape_markdown without changing URL ampersands. */
+function escapeMarkdown(value: string, inlineCode = false): string {
+ const urls = [...value.matchAll(/(?:https?|ftp):\/\/|(www)\./giu)]
+ .filter((match) => {
+ const previous = value[match.index - 1] ?? "";
+ return match[1]
+ ? previous === "" ||
+ /[\p{White_Space}\u001c-\u001f*_~(]/u.test(previous)
+ : !/[A-Za-z]/u.test(previous);
+ })
+ .map((match) => {
+ const length = value
+ .slice(match.index)
+ .search(/[\p{White_Space}\u001c-\u001f<>]/u);
+ return {
+ start: match.index,
+ end: length < 0 ? value.length : match.index + length,
+ };
+ });
+ const pattern = inlineCode
+ ? /(?&]/gu
+ : /[\\`*_\[\]<>&]/gu;
+ return value.replace(pattern, (match, offset: number) =>
+ match.length > 1 ||
+ (match === "&" &&
+ urls.some(({ start, end }) => start <= offset && offset < end))
+ ? match
+ : "\\" + match,
+ );
+}
+
/** Plain metadata remains text; authored single-backtick spans remain intact. */
export function text(value: unknown, fallback = ""): string {
const candidate =
@@ -20,10 +51,5 @@ export function text(value: unknown, fallback = ""): string {
)
? `Text: ${normalized}`
: normalized;
- return protectedText
- .split(/((?
- index % 2 ? part : part.replace(/([\\`*_\[\]<>])/gu, "\\$1"),
- )
- .join("");
+ return escapeMarkdown(protectedText, true);
}
diff --git a/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts b/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
index 7c189f4e5e..ebbe59d764 100644
--- a/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
+++ b/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
@@ -109,6 +109,38 @@ equivalent(
},
},
);
+equivalent(
+ "footer entities remain literal in prose and scope paths",
+ {
+ ...markdownModel,
+ origin: "provided",
+ scope: {
+ includePaths: [
+ "docs/<tag>.md",
+ "docs/www.example.com/<tag>.md",
+ "docs/`<tag>`.md",
+ "docs/a,<tag>.md",
+ ],
+ excludePaths: ["tmp/&.md"],
+ summary:
+ "R&D decodes <tag> and <. Keep `&` literal.",
+ },
+ },
+ { target: "R&D <workspace>" },
+);
+for (const url of [
+ "https://example.com/?first=1&second=2",
+ "http://example.com/?payload=<tag>&mode=1",
+ "ftp://example.com/?first=1&second=2",
+ "www.example.com/?payload=<tag>&mode=1",
+ "WWW.example.com/?first=1&second=2",
+ "WwW.example.com/?first=1&second=2",
+ "https://example.com/`a`?first=1&second=2",
+]) {
+ equivalent(`footer URL ampersands ${url}`, markdownModel, {
+ target: `${url} then & and \`https://example.com/?a=1&b=2 &\`.`,
+ });
+}
for (const ending of ["", "\n", "\n\n", "\r\n"]) {
equivalent(`authored Markdown ending ${JSON.stringify(ending)}`, {
...markdownModel,
@@ -180,10 +212,10 @@ test("Python finalizer recognizes native renderer bytes as the current projectio
status: "running",
threatModel,
target: {
- displayName: "Example [workspace]",
+ displayName: "R&D <workspace>",
revision: "example-revision",
},
- scope: { includePaths: ["src"], excludePaths: [] },
+ scope: { includePaths: ["src", "docs/<tag>.md"], excludePaths: [] },
};
const markdown = renderThreatModel({
threatModel,
diff --git a/plugins/codex-security/scripts/report_projection.py b/plugins/codex-security/scripts/report_projection.py
index 69b362b906..72e3d90018 100644
--- a/plugins/codex-security/scripts/report_projection.py
+++ b/plugins/codex-security/scripts/report_projection.py
@@ -21,7 +21,7 @@
}
WRITEUP_REPORT_PATH_RE = re.compile(r"^findings/([a-z0-9][a-z0-9._-]*)/\1\.md$")
INLINE_CODE_RE = r"(?]+"
+AUTOLINK_RE = r"(?:(?]+"
class ReportProjectionError(ValueError):
diff --git a/plugins/codex-security/tests/test_report_projection.py b/plugins/codex-security/tests/test_report_projection.py
index 10c32c01c9..74f2dfc3e6 100644
--- a/plugins/codex-security/tests/test_report_projection.py
+++ b/plugins/codex-security/tests/test_report_projection.py
@@ -820,6 +820,8 @@ def test_projection_keeps_html_entities_literal_in_markdown() -> None:
"https://example.com/?first=1&second=2",
"http://example.com/?payload=<tag>&mode=1",
"www.example.com/?payload=<tag>&mode=1",
+ "WWW.example.com/?first=1&second=2",
+ "WwW.example.com/?first=1&second=2",
"ftp://example.com/?first=1&second=2",
"https://example.com/`a`?first=1&second=2",
"www.example.com/`a`?first=1&second=2",
From fdf38268f3e85f595ee13c49d04ec16bf1ade618 Mon Sep 17 00:00:00 2001
From: Codex
Date: Fri, 23 Oct 2026 19:41:26 +0000
Subject: [PATCH 4/6] fix(reports): preserve fuzzy links and email ampersands
---
.../codex-security/mcp-app/src/threat-model-format.ts | 9 +++++++++
.../mcp-app/tests/test_threat_model_render.ts | 11 +++++++++++
plugins/codex-security/scripts/report_projection.py | 11 ++++++++++-
.../codex-security/tests/test_report_projection.py | 10 ++++++++++
4 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/plugins/codex-security/mcp-app/src/threat-model-format.ts b/plugins/codex-security/mcp-app/src/threat-model-format.ts
index 4481ff1edd..3394cbf654 100644
--- a/plugins/codex-security/mcp-app/src/threat-model-format.ts
+++ b/plugins/codex-security/mcp-app/src/threat-model-format.ts
@@ -28,6 +28,15 @@ function escapeMarkdown(value: string, inlineCode = false): string {
end: length < 0 ? value.length : match.index + length,
};
});
+ // Conservatively retain fuzzy links without a TLD or Unicode domain table.
+ const domain = String.raw`[^\p{White_Space}\u001c-\u001f<>/@:?&.#\\]+(?:\.[^\p{White_Space}\u001c-\u001f<>/@:?&.#\\]+)+`;
+ const bareLink = new RegExp(
+ String.raw`(?/@]+@${domain})[^\p{White_Space}\u001c-\u001f<>]*`,
+ "gu",
+ );
+ for (const match of value.matchAll(bareLink)) {
+ urls.push({ start: match.index, end: match.index + match[0].length });
+ }
const pattern = inlineCode
? /(?&]/gu
: /[\\`*_\[\]<>&]/gu;
diff --git a/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts b/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
index d2c4fd8093..a37275cd5c 100644
--- a/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
+++ b/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
@@ -136,6 +136,17 @@ for (const url of [
"WWW.example.com/?first=1&second=2",
"WwW.example.com/?first=1&second=2",
"https://example.com/`a`?first=1&second=2",
+ "example.com/?first=1&second=2",
+ "//example.com/?first=1&second=2",
+ '"example.com/?first=1&second=2"',
+ "[example.com/?first=1&second=2]",
+ "éxample.com/?first=1&second=2",
+ "例子.com/?first=1&second=2",
+ "example.com/?payload=<tag>&mode=1",
+ "example.com/?first=1&word;=2",
+ "//example.com/?first=1&word;=2",
+ "user&tag@example.com",
+ "mailto:user&tag@example.com",
]) {
equivalent(`footer URL ampersands ${url}`, markdownModel, {
target: `${url} then & and \`https://example.com/?a=1&b=2 &\`.`,
diff --git a/plugins/codex-security/scripts/report_projection.py b/plugins/codex-security/scripts/report_projection.py
index 26d61684a6..d88dcf82c4 100644
--- a/plugins/codex-security/scripts/report_projection.py
+++ b/plugins/codex-security/scripts/report_projection.py
@@ -22,6 +22,11 @@
WRITEUP_REPORT_PATH_RE = re.compile(r"^findings/([a-z0-9][a-z0-9._-]*)/\1\.md$")
INLINE_CODE_RE = r"(?]+"
+LINK_DOMAIN_RE = r"[^\s<>/@:?&.#\\]+(?:\.[^\s<>/@:?&.#\\]+)+"
+BARE_LINK_RE = (
+ rf"(?/@]+@{LINK_DOMAIN_RE})[^\s<>]*"
+)
class ReportProjectionError(ValueError):
@@ -30,7 +35,11 @@ class ReportProjectionError(ValueError):
def _escape_markdown(value: str, *, inline_code: bool = False) -> str:
# Locate whole URLs before matching code spans: a URL can contain backticks.
- urls = [match.span() for match in re.finditer(AUTOLINK_RE, value)]
+ urls = [
+ match.span()
+ for pattern in (AUTOLINK_RE, BARE_LINK_RE)
+ for match in re.finditer(pattern, value)
+ ]
punctuation = r"[\\`*_\[\]<>&]"
pattern = f"(?P{INLINE_CODE_RE})|{punctuation}" if inline_code else punctuation
diff --git a/plugins/codex-security/tests/test_report_projection.py b/plugins/codex-security/tests/test_report_projection.py
index 237bb46511..157eaf4d68 100644
--- a/plugins/codex-security/tests/test_report_projection.py
+++ b/plugins/codex-security/tests/test_report_projection.py
@@ -825,6 +825,16 @@ def test_projection_keeps_html_entities_literal_in_markdown() -> None:
"ftp://example.com/?first=1&second=2",
"https://example.com/`a`?first=1&second=2",
"www.example.com/`a`?first=1&second=2",
+ "example.com/?first=1&second=2",
+ "//example.com/?first=1&second=2",
+ '"example.com/?first=1&second=2"',
+ "éxample.com/?first=1&second=2",
+ "例子.com/?first=1&second=2",
+ "example.com/?payload=<tag>&mode=1",
+ "example.com/?first=1&word;=2",
+ "//example.com/?first=1&word;=2",
+ "user&tag@example.com",
+ "mailto:user&tag@example.com",
],
)
def test_projection_preserves_bare_url_ampersands_in_prose_and_tables(url: str) -> None:
From 74ecc2400b47de73dcd7bbaedfbfaf1f8a57b4ee Mon Sep 17 00:00:00 2001
From: Codex
Date: Fri, 23 Oct 2026 19:52:49 +0000
Subject: [PATCH 5/6] fix(reports): separate literal paths from prose links
---
.../mcp-app/src/threat-model-format.ts | 53 +++++++++++--------
.../mcp-app/tests/test_threat_model_render.ts | 14 +++++
.../scripts/report_projection.py | 35 ++++++------
.../tests/test_report_projection.py | 24 +++++++--
4 files changed, 81 insertions(+), 45 deletions(-)
diff --git a/plugins/codex-security/mcp-app/src/threat-model-format.ts b/plugins/codex-security/mcp-app/src/threat-model-format.ts
index 3394cbf654..bfc2170e4d 100644
--- a/plugins/codex-security/mcp-app/src/threat-model-format.ts
+++ b/plugins/codex-security/mcp-app/src/threat-model-format.ts
@@ -1,4 +1,5 @@
const whitespace = /[\p{White_Space}\u001c-\u001f]+/gu;
+const entityAmp = /&(?=(?:#[0-9]+|#[xX][0-9A-Fa-f]+|[A-Za-z][A-Za-z0-9]*);)/u;
/** Match the retained artifact projection's Python whitespace semantics. */
export function nonblank(value: string): boolean {
@@ -10,7 +11,7 @@ function collapse(value: string): string {
}
/** Match report_projection._escape_markdown without changing URL ampersands. */
-function escapeMarkdown(value: string, inlineCode = false): string {
+function escapeMarkdown(value: string): string {
const urls = [...value.matchAll(/(?:https?|ftp):\/\/|(www)\./giu)]
.filter((match) => {
const previous = value[match.index - 1] ?? "";
@@ -28,18 +29,21 @@ function escapeMarkdown(value: string, inlineCode = false): string {
end: length < 0 ? value.length : match.index + length,
};
});
- // Conservatively retain fuzzy links without a TLD or Unicode domain table.
- const domain = String.raw`[^\p{White_Space}\u001c-\u001f<>/@:?&.#\\]+(?:\.[^\p{White_Space}\u001c-\u001f<>/@:?&.#\\]+)+`;
- const bareLink = new RegExp(
- String.raw`(?/@]+@${domain})[^\p{White_Space}\u001c-\u001f<>]*`,
+ // Preserve protocol-relative and fuzzy links without a domain/TLD table.
+ for (const match of value.matchAll(/[^\p{White_Space}\u001c-\u001f<>]+/gu)) {
+ const token = match[0];
+ const dot = token.split("/", 1)[0]!.indexOf(".");
+ if (
+ token.includes("//") ||
+ (dot >= 0 && /[/?#]/u.test(token.slice(dot + 1)))
+ ) {
+ urls.push({ start: match.index, end: match.index + token.length });
+ }
+ }
+ const pattern = new RegExp(
+ /(?]/u.source + "|" + entityAmp.source,
"gu",
);
- for (const match of value.matchAll(bareLink)) {
- urls.push({ start: match.index, end: match.index + match[0].length });
- }
- const pattern = inlineCode
- ? /(?&]/gu
- : /[\\`*_\[\]<>&]/gu;
return value.replace(pattern, (match, offset: number) =>
match.length > 1 ||
(match === "&" &&
@@ -51,25 +55,28 @@ function escapeMarkdown(value: string, inlineCode = false): string {
/** Match report_projection._path_text with ensure_ascii for quoted scope paths. */
export function pathText(value: string): string {
- if (
- !value.includes(",") &&
- value === collapse(value) &&
- !/[\u0000-\u001f]/u.test(value)
- )
- return escapeMarkdown(value);
+ const quoted =
+ value.includes(",") ||
+ value !== collapse(value) ||
+ /[\u0000-\u001f]/u.test(value);
+ if (!quoted && !entityAmp.test(value))
+ return value.replace(/([\\`*_\[\]<>])/gu, "\\$1");
// Escape UTF-16 units so quoted paths do not depend on Unicode versions.
- const label = JSON.stringify(value).replace(
- /[\u007f-\uffff]/g,
- (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`,
- );
+ const label = quoted
+ ? JSON.stringify(value).replace(
+ /[\u007f-\uffff]/g,
+ (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`,
+ )
+ : value;
const marker = "`".repeat(
(label.match(/`+/gu) ?? []).reduce(
(length, run) => Math.max(length, run.length + 1),
1,
),
);
- return `${marker}${label}${marker}`;
+ const padding = label.startsWith("`") || label.endsWith("`") ? " " : "";
+ return `${marker}${padding}${label}${padding}${marker}`;
}
/** Plain metadata remains text; authored single-backtick spans remain intact. */
@@ -83,5 +90,5 @@ export function text(value: unknown, fallback = ""): string {
)
? `Text: ${normalized}`
: normalized;
- return escapeMarkdown(protectedText, true);
+ return escapeMarkdown(protectedText);
}
diff --git a/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts b/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
index a37275cd5c..d002553fbd 100644
--- a/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
+++ b/plugins/codex-security/mcp-app/tests/test_threat_model_render.ts
@@ -120,6 +120,12 @@ equivalent(
"docs/www.example.com/<tag>.md",
"docs/`<tag>`.md",
"docs/a,<tag>.md",
+ "dir<tag>.example/file.md",
+ "dir.example/file<tag>.md",
+ "www.example.com/file&.md",
+ "`&`",
+ "`&.md",
+ "file&`",
],
excludePaths: ["tmp/&.md"],
summary:
@@ -147,6 +153,10 @@ for (const url of [
"//example.com/?first=1&word;=2",
"user&tag@example.com",
"mailto:user&tag@example.com",
+ "//localhost:8080/?first=1&word;=2",
+ "{//example.com/?first=1&word;=2}",
+ "?example.com/?first=1&word;=2",
+ "mailto:user&tag@localhost",
]) {
equivalent(`footer URL ampersands ${url}`, markdownModel, {
target: `${url} then & and \`https://example.com/?a=1&b=2 &\`.`,
@@ -275,6 +285,10 @@ test("Python finalizer recognizes native renderer bytes as the current projectio
"\u{1fae9},notes.md",
"docs/<tag>.md",
"docs/a,<tag>.md",
+ "dir<tag>.example/file.md",
+ "dir.example/file<tag>.md",
+ "www.example.com/file&.md",
+ "`&`",
],
excludePaths: [" "],
};
diff --git a/plugins/codex-security/scripts/report_projection.py b/plugins/codex-security/scripts/report_projection.py
index d88dcf82c4..61c8876e10 100644
--- a/plugins/codex-security/scripts/report_projection.py
+++ b/plugins/codex-security/scripts/report_projection.py
@@ -21,32 +21,30 @@
}
WRITEUP_REPORT_PATH_RE = re.compile(r"^findings/([a-z0-9][a-z0-9._-]*)/\1\.md$")
INLINE_CODE_RE = r"(?]+"
-LINK_DOMAIN_RE = r"[^\s<>/@:?&.#\\]+(?:\.[^\s<>/@:?&.#\\]+)+"
-BARE_LINK_RE = (
- rf"(?/@]+@{LINK_DOMAIN_RE})[^\s<>]*"
-)
class ReportProjectionError(ValueError):
"""Raised when a canonical scan cannot be projected into a valid report."""
-def _escape_markdown(value: str, *, inline_code: bool = False) -> str:
+def _escape_markdown(value: str) -> str:
# Locate whole URLs before matching code spans: a URL can contain backticks.
- urls = [
- match.span()
- for pattern in (AUTOLINK_RE, BARE_LINK_RE)
- for match in re.finditer(pattern, value)
- ]
- punctuation = r"[\\`*_\[\]<>&]"
- pattern = f"(?P{INLINE_CODE_RE})|{punctuation}" if inline_code else punctuation
+ urls = [match.span() for match in re.finditer(AUTOLINK_RE, value)]
+ # Preserve protocol-relative and fuzzy links without a domain/TLD table.
+ for match in re.finditer(r"[^\s<>]+", value):
+ token = match[0]
+ dot = token.split("/", 1)[0].find(".")
+ if "//" in token or (dot >= 0 and re.search(r"[/?#]", token[dot + 1 :])):
+ urls.append(match.span())
+ punctuation = r"[\\`*_\[\]<>]|" + ENTITY_AMP_RE
+ pattern = f"(?P{INLINE_CODE_RE})|{punctuation}"
def escape(match: re.Match[str]) -> str:
if match.lastgroup == "code":
return match[0]
- # GFM autolinks keep backslashes literally, including before query separators.
+ # GFM autolinks retain backslashes literally, including in query strings.
if match[0] == "&" and any(start <= match.start() < end for start, end in urls):
return "&"
return "\\" + match[0]
@@ -59,7 +57,7 @@ def _text(value: Any, fallback: str) -> str:
normalized = " ".join(candidate.split())
if re.match(r"^(?:#{1,6}\s|[-*+]\s|>\s|```|~~~|\d+[.)]\s|\|)", normalized):
normalized = f"Text: {normalized}"
- return _escape_markdown(normalized, inline_code=True)
+ return _escape_markdown(normalized)
def _strings(value: Any) -> list[str]:
@@ -79,10 +77,11 @@ def _path_label(value: str, *, ensure_ascii: bool = False) -> str:
def _path_text(value: str, *, ensure_ascii: bool = False) -> str:
label = _path_label(value, ensure_ascii=ensure_ascii)
- if label != value:
+ if label != value or re.search(ENTITY_AMP_RE, value):
marker = _code_fence(label, minimum=1)
- return f"{marker}{label}{marker}"
- return _escape_markdown(value)
+ padding = " " if label.startswith("`") or label.endswith("`") else ""
+ return f"{marker}{padding}{label}{padding}{marker}"
+ return re.sub(r"([\\`*_\[\]<>])", r"\\\1", value)
def _cell(value: Any) -> str:
diff --git a/plugins/codex-security/tests/test_report_projection.py b/plugins/codex-security/tests/test_report_projection.py
index 157eaf4d68..f50dea2786 100644
--- a/plugins/codex-security/tests/test_report_projection.py
+++ b/plugins/codex-security/tests/test_report_projection.py
@@ -811,7 +811,7 @@ def test_projection_keeps_html_entities_literal_in_markdown() -> None:
markdown_text = PROJECTION.generate_report_markdown(manifest, findings, coverage).decode()
assert r"Sanitizer decodes \<script\> and \<" in markdown_text
- assert r"- Included paths: docs/\<b\>.md" in markdown_text
+ assert "- Included paths: `docs/<b>.md`" in markdown_text
@pytest.mark.parametrize(
@@ -835,6 +835,10 @@ def test_projection_keeps_html_entities_literal_in_markdown() -> None:
"//example.com/?first=1&word;=2",
"user&tag@example.com",
"mailto:user&tag@example.com",
+ "//localhost:8080/?first=1&word;=2",
+ "{//example.com/?first=1&word;=2}",
+ "?example.com/?first=1&word;=2",
+ "mailto:user&tag@localhost",
],
)
def test_projection_preserves_bare_url_ampersands_in_prose_and_tables(url: str) -> None:
@@ -847,7 +851,7 @@ def test_projection_preserves_bare_url_ampersands_in_prose_and_tables(url: str)
assert f"Reproduce with {url} then compare \\&." in markdown
assert f"| Confidence rationale | Request: {url} |" in markdown
- assert r"R\&D decoded \<tag\>." in markdown
+ assert r"R&D decoded \<tag\>." in markdown
def test_projection_preserves_inline_code_and_existing_url_escapes() -> None:
@@ -860,6 +864,12 @@ def test_projection_preserves_inline_code_and_existing_url_escapes() -> None:
coverage["includePaths"] = [
"docs/www.example.com/<tag>.md",
"docs/`<tag>`.md",
+ "dir<tag>.example/file.md",
+ "dir.example/file<tag>.md",
+ "www.example.com/file&.md",
+ "`&`",
+ "`&.md",
+ "file&`",
]
markdown = PROJECTION.generate_report_markdown(manifest, findings, coverage).decode()
@@ -867,8 +877,14 @@ def test_projection_preserves_inline_code_and_existing_url_escapes() -> None:
assert "Compare `&` and `https://example.com/?a=1&b=2 &`" in markdown
assert r"https://example.com/a\_b?first=1&second=2 and \&." in markdown
assert "Keep https://example.com/`a _b_` as written." in markdown
- assert r"- Included paths: docs/www.example.com/\<tag\>.md" in markdown
- assert r"docs/\`\<tag\>\`.md" in markdown
+ assert "- Included paths: `docs/www.example.com/<tag>.md`" in markdown
+ assert "``docs/`<tag>`.md``" in markdown
+ assert "`dir<tag>.example/file.md`" in markdown
+ assert "`dir.example/file<tag>.md`" in markdown
+ assert "`www.example.com/file&.md`" in markdown
+ assert "`` `&` ``" in markdown
+ assert "`` `&.md ``" in markdown
+ assert "`` file&` ``" in markdown
def test_projection_omits_informational_findings() -> None:
From 3da6e6378df23d31d1d6399a2599ede89b907f87 Mon Sep 17 00:00:00 2001
From: Codex
Date: Fri, 23 Oct 2026 20:00:35 +0000
Subject: [PATCH 6/6] refactor(reports): remove redundant URL matching
---
.../mcp-app/src/threat-model-format.ts | 25 ++++++-------------
.../scripts/report_projection.py | 4 +--
2 files changed, 10 insertions(+), 19 deletions(-)
diff --git a/plugins/codex-security/mcp-app/src/threat-model-format.ts b/plugins/codex-security/mcp-app/src/threat-model-format.ts
index bfc2170e4d..d333a4ae57 100644
--- a/plugins/codex-security/mcp-app/src/threat-model-format.ts
+++ b/plugins/codex-security/mcp-app/src/threat-model-format.ts
@@ -12,23 +12,14 @@ function collapse(value: string): string {
/** Match report_projection._escape_markdown without changing URL ampersands. */
function escapeMarkdown(value: string): string {
- const urls = [...value.matchAll(/(?:https?|ftp):\/\/|(www)\./giu)]
- .filter((match) => {
- const previous = value[match.index - 1] ?? "";
- return match[1]
- ? previous === "" ||
- /[\p{White_Space}\u001c-\u001f*_~(]/u.test(previous)
- : !/[A-Za-z]/u.test(previous);
- })
- .map((match) => {
- const length = value
- .slice(match.index)
- .search(/[\p{White_Space}\u001c-\u001f<>]/u);
- return {
- start: match.index,
- end: length < 0 ? value.length : match.index + length,
- };
- });
+ const urls = [
+ ...value.matchAll(
+ /(?]+/giu,
+ ),
+ ].map((match) => ({
+ start: match.index,
+ end: match.index + match[0].length,
+ }));
// Preserve protocol-relative and fuzzy links without a domain/TLD table.
for (const match of value.matchAll(/[^\p{White_Space}\u001c-\u001f<>]+/gu)) {
const token = match[0];
diff --git a/plugins/codex-security/scripts/report_projection.py b/plugins/codex-security/scripts/report_projection.py
index 61c8876e10..86eb38fd08 100644
--- a/plugins/codex-security/scripts/report_projection.py
+++ b/plugins/codex-security/scripts/report_projection.py
@@ -22,7 +22,7 @@
WRITEUP_REPORT_PATH_RE = re.compile(r"^findings/([a-z0-9][a-z0-9._-]*)/\1\.md$")
INLINE_CODE_RE = r"(?]+"
+WWW_LINK_RE = r"(?]+"
class ReportProjectionError(ValueError):
@@ -31,7 +31,7 @@ class ReportProjectionError(ValueError):
def _escape_markdown(value: str) -> str:
# Locate whole URLs before matching code spans: a URL can contain backticks.
- urls = [match.span() for match in re.finditer(AUTOLINK_RE, value)]
+ urls = [match.span() for match in re.finditer(WWW_LINK_RE, value)]
# Preserve protocol-relative and fuzzy links without a domain/TLD table.
for match in re.finditer(r"[^\s<>]+", value):
token = match[0]