Skip to content

Commit 8314fa6

Browse files
author
CometAPI
committed
fix: reject wrapped and bidirectional release claims
1 parent a940723 commit 8314fa6

4 files changed

Lines changed: 89 additions & 6 deletions

File tree

scripts/_checks.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
r")(?=$|[/?#>])"
133133
)
134134
_HTTP_URL = re.compile(r"https?://[^\s<>)\]]+", re.IGNORECASE)
135+
_URLISH_TOKEN = re.compile(r"https?:[^\s<>\"']+", re.IGNORECASE)
135136
_RECOVERY_TAGS = {"0.1.0a1": "v0.1.0-alpha.1+recovery.1"}
136137
_FULL_COMMIT = re.compile(r"(?<![0-9a-f])[0-9a-f]{40}(?![0-9a-f])", re.IGNORECASE)
137138
_ACTIONS_PREFIX = f"{CANONICAL_REPOSITORY}/actions/runs/"
@@ -140,10 +141,58 @@
140141
re.IGNORECASE,
141142
)
142143
_CANONICAL_ACTIONS_URL = re.compile(
143-
rf"(?<![^\s<(]){re.escape(_ACTIONS_PREFIX)}(?P<run>[1-9]\d*)"
144+
rf"(?<![^\s<(\"']){re.escape(_ACTIONS_PREFIX)}(?P<run>[1-9]\d*)"
144145
r"(?:/attempts/(?P<attempt>[1-9]\d*))?"
145146
r"(?=$|[\s<>\"')\]}]|[.,;:!?](?=$|\s))"
146147
)
148+
_RAW_HTML_URL_ATTRIBUTE = re.compile(
149+
r"(?is)\b(?:href|src)\s*=\s*(?P<quote>['\"])(?P<url>.*?)(?P=quote)"
150+
)
151+
152+
153+
def _actions_path_has_canonical_url(
154+
text: str,
155+
path: re.Match[str],
156+
canonical_matches: list[re.Match[str]],
157+
) -> bool:
158+
canonical = next(
159+
(
160+
match
161+
for match in canonical_matches
162+
if match.start() <= path.start() and path.end() <= match.end()
163+
),
164+
None,
165+
)
166+
if canonical is None:
167+
return False
168+
169+
for attribute in _RAW_HTML_URL_ATTRIBUTE.finditer(text):
170+
if attribute.start("url") <= path.start() and path.end() <= attribute.end("url"):
171+
return _CANONICAL_ACTIONS_URL.fullmatch(attribute.group("url")) is not None
172+
173+
containing_tokens = [
174+
match
175+
for match in _URLISH_TOKEN.finditer(text)
176+
if match.start() <= path.start() and path.end() <= match.end()
177+
]
178+
if containing_tokens and min(match.start() for match in containing_tokens) < canonical.start():
179+
return False
180+
181+
if canonical.start() == 0:
182+
return True
183+
boundary = text[canonical.start() - 1]
184+
if boundary.isspace():
185+
return True
186+
if boundary == "<":
187+
return canonical.start() >= 2 and text[canonical.start() - 2].isspace()
188+
if boundary == "(":
189+
before_boundary = canonical.start() - 2
190+
return (
191+
before_boundary < 0 or text[before_boundary].isspace() or text[before_boundary] == "]"
192+
)
193+
return False
194+
195+
147196
_WHEEL_DIGEST = re.compile(
148197
r"\bwheel\s+sha256\b[^0-9a-f]{0,96}(?P<digest>[0-9a-f]{64})(?![0-9a-f])",
149198
re.IGNORECASE | re.DOTALL,
@@ -551,7 +600,7 @@ def _identity_violations(
551600
),
552601
"exact release commit": re.compile(re.escape(identity.commit), re.IGNORECASE),
553602
"exact release workflow URL": re.compile(
554-
rf"(?<![^\s<(]){re.escape(_ACTIONS_PREFIX)}{identity.workflow_run}"
603+
rf"(?<![^\s<(\"']){re.escape(_ACTIONS_PREFIX)}{identity.workflow_run}"
555604
r"(?:/attempts/[1-9]\d*)?"
556605
r"(?=$|[\s<>\"')\]}]|[.,;:!?](?=$|\s))"
557606
),
@@ -701,12 +750,9 @@ def _canonical_actions_run_violations(
701750
path_matches = list(_ACTIONS_PATH.finditer(direct))
702751
canonical_matches = list(_CANONICAL_ACTIONS_URL.finditer(direct))
703752
malformed = any(
704-
not any(
705-
url.start() <= path.start() and path.end() <= url.end() for url in canonical_matches
706-
)
753+
not _actions_path_has_canonical_url(direct, path, canonical_matches)
707754
for path in path_matches
708755
)
709-
710756
direct_paths = Counter((match.group("run"), match.group("attempt")) for match in path_matches)
711757
variant_paths = [
712758
Counter(

scripts/check_version.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ def _close_heading(self) -> None:
261261

262262
def _visible_heading_is_unreleased(label: str) -> bool:
263263
label = html.unescape(unicodedata.normalize("NFKC", label))
264+
if any(
265+
unicodedata.bidirectional(value)
266+
in {"LRE", "RLE", "LRO", "RLO", "PDF", "LRI", "RLI", "FSI", "PDI"}
267+
for value in label
268+
):
269+
return True
264270
label = "".join(
265271
""
266272
if unicodedata.category(value) == "Cf"

tests/test_changelog_gate.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ def test_changelog_parser_rejects_obfuscated_mutable_versions(claim: str) -> Non
9797
"## Un&#x72;eleased",
9898
"## \uff35\uff4e\uff52\uff45\uff4c\uff45\uff41\uff53\uff45\uff44",
9999
"## Un\u034freleased",
100+
"## Un\u202edesaeler\u202c",
101+
"## Un\u2067released\u2069",
100102
"## Current Unreleased",
101103
"Next release (Unreleased)\n-------------------------",
102104
"Unreleased\n----------",

tests/test_release_documents.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,12 @@ def test_release_evidence_rejects_obsolete_workflow_reference_marker(
700700
"actions/runs/30511373822",
701701
"https://evil.example/#https://github.com/cometapi-dev/cometapi-python/"
702702
"actions/runs/30511373822",
703+
"https://evil.example/?next=(https://github.com/cometapi-dev/cometapi-python/"
704+
"actions/runs/30515861246)",
705+
"https://evil.example/#(https://github.com/cometapi-dev/cometapi-python/"
706+
"actions/runs/30515861246)",
707+
"mailto:(https://github.com/cometapi-dev/cometapi-python/actions/runs/30515861246)",
708+
"prefix<https://github.com/cometapi-dev/cometapi-python/actions/runs/30515861246>",
703709
"http://github.com/cometapi-dev/cometapi-python/actions/runs/30511373822",
704710
"https://evil.example/?next=https%3A%2F%2Fgithub.com%2Fcometapi-dev%2F"
705711
"cometapi-python%2Factions%2Fruns%2F30511373822",
@@ -748,6 +754,29 @@ def test_release_evidence_rejects_noncanonical_workflow_reference_url(
748754
assert "/actions/runs/<positive-id>" in str(caught.value)
749755

750756

757+
def test_release_evidence_accepts_canonical_raw_html_anchor(
758+
releasable_documents: Path,
759+
) -> None:
760+
evidence = _release_evidence_block().replace(
761+
"- Release workflow https://github.com/cometapi-dev/cometapi-python/actions/runs/"
762+
"30515861246",
763+
'- Release workflow <a href="https://github.com/cometapi-dev/cometapi-python/'
764+
'actions/runs/30515861246">canonical publication run</a>',
765+
1,
766+
)
767+
for name in ("ROADMAP.md", "RELEASING.md"):
768+
with (releasable_documents / name).open("a", encoding="utf-8") as stream:
769+
stream.write(evidence)
770+
_replace(
771+
releasable_documents,
772+
"CHANGELOG.md",
773+
"# Changelog\n",
774+
"# Changelog\n\n## [0.1.2] - 2026-07-30\n\nHistory.\n",
775+
)
776+
777+
require_public_preview_docs()
778+
779+
751780
def test_fenced_release_evidence_is_not_accepted_as_history(
752781
releasable_documents: Path,
753782
) -> None:

0 commit comments

Comments
 (0)