Skip to content

Commit 199f6bf

Browse files
committed
fix: address Copilot review round 12 findings
- Install packaging explicitly in both validate workflow jobs so PEP 440 validation is consistent across runner images - Restrict parse_issue_body() to only split on known form labels, preventing user-typed ### headings in textareas from corrupting field parsing - Restrict URL reachability checks to GitHub domains only (github.com, raw.githubusercontent.com, etc.) to mitigate DNS-rebinding TOCTOU risks — issue templates already require GitHub URLs - Validate, deduplicate, and sort preset requires.extensions IDs using the same ID regex, ensuring clean catalog output
1 parent 3740d4a commit 199f6bf

2 files changed

Lines changed: 38 additions & 11 deletions

File tree

.github/scripts/catalog-validate.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,33 @@
3535
# Issue body parser
3636
# ---------------------------------------------------------------------------
3737

38-
def parse_issue_body(body: str) -> dict[str, str]:
38+
def parse_issue_body(body: str, known_labels: set[str] | None = None) -> dict[str, str]:
3939
"""Parse a GitHub issue form body into {label: value} pairs.
4040
4141
GitHub issue forms render as markdown with ``### Label`` headers
4242
followed by the user's input. Checkbox groups render as lists of
4343
``- [X]`` / ``- [ ]`` items.
44+
45+
When *known_labels* is provided, only ``### Label`` lines whose text
46+
matches a known label start a new field. Other ``###`` headings
47+
inside textarea content are preserved as-is.
4448
"""
4549
fields: dict[str, str] = {}
4650
current_label: str | None = None
4751
current_lines: list[str] = []
4852

4953
for line in body.splitlines():
5054
if line.startswith("### "):
51-
# Store previous field
52-
if current_label is not None:
53-
fields[current_label] = "\n".join(current_lines).strip()
54-
current_label = line[4:].strip()
55-
current_lines = []
56-
else:
57-
current_lines.append(line)
55+
heading = line[4:].strip()
56+
# Only split on known form labels (if provided)
57+
if known_labels is None or heading in known_labels:
58+
# Store previous field
59+
if current_label is not None:
60+
fields[current_label] = "\n".join(current_lines).strip()
61+
current_label = heading
62+
current_lines = []
63+
continue
64+
current_lines.append(line)
5865

5966
# Don't forget the last field
6067
if current_label is not None:
@@ -295,6 +302,17 @@ def check_url_reachable(
295302
hostname = parsed.hostname
296303
if not hostname:
297304
return False, f"{field_name} URL has no hostname."
305+
306+
# Restrict to known hosts to mitigate DNS-rebinding TOCTOU risks
307+
_allowed_hosts = {
308+
"github.com", "www.github.com", "codeload.github.com",
309+
"raw.githubusercontent.com", "objects.githubusercontent.com",
310+
}
311+
if hostname not in _allowed_hosts:
312+
return False, (
313+
f"{field_name} URL must be on a GitHub domain "
314+
f"(got `{hostname}`)."
315+
)
298316
try:
299317
addr_info = socket.getaddrinfo(hostname, None)
300318
for _family, _type, _proto, _canonname, sockaddr in addr_info:
@@ -764,9 +782,11 @@ def _build_preset_entry(
764782
for line in extensions_raw.splitlines():
765783
line = line.strip().lstrip("-*").strip()
766784
for part in line.split(","):
767-
part = part.strip()
768-
if part:
785+
part = part.strip().lower()
786+
if part and _ID_RE.match(part):
769787
ext_list.append(part)
788+
# Deduplicate and sort for stable catalog output
789+
ext_list = sorted(set(ext_list))
770790
if ext_list:
771791
requires["extensions"] = ext_list
772792
elif is_update and "extensions" in existing.get("requires", {}):
@@ -936,7 +956,8 @@ def main() -> None:
936956
catalog = json.load(f)
937957

938958
# Parse and normalize
939-
raw_fields = parse_issue_body(issue_body)
959+
known_labels = set(LABEL_MAPS[args.type].keys())
960+
raw_fields = parse_issue_body(issue_body, known_labels=known_labels)
940961
fields = normalize_fields(raw_fields, args.type)
941962

942963
if not fields:

.github/workflows/catalog-validate.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ jobs:
2222
with:
2323
python-version: "3.12"
2424

25+
- name: Install dependencies
26+
run: pip install packaging
27+
2528
- name: Validate submission
2629
id: validate
2730
env:
@@ -140,6 +143,9 @@ jobs:
140143
with:
141144
python-version: "3.12"
142145

146+
- name: Install dependencies
147+
run: pip install packaging
148+
143149
- name: Validate submission
144150
id: validate
145151
env:

0 commit comments

Comments
 (0)