Gap
scripts/prepare_agent_pr_message.py is the boundary where model-authored pull-request metadata becomes text a human reviewer reads. Its own docstring states the purpose: "Safely parse untrusted OpenCode pull-request metadata for a trusted publisher", and _reject_unsafe_text promises to "Reject control and bidirectional characters that can spoof metadata".
It rejects twelve named characters. Measured just now, every one of them is Unicode category Cf, and seven other Cf characters pass straight through:
| character |
name |
result |
| U+200B |
ZERO WIDTH SPACE |
accepted |
| U+200C |
ZERO WIDTH NON-JOINER |
accepted |
| U+200D |
ZERO WIDTH JOINER |
accepted |
| U+2060 |
WORD JOINER |
accepted |
| U+FEFF |
ZERO WIDTH NO-BREAK SPACE (BOM) |
accepted |
| U+00AD |
SOFT HYPHEN |
accepted |
| U+180E |
MONGOLIAN VOWEL SEPARATOR |
accepted |
Verified by calling _reject_unsafe_text("fix: harmless<CHAR>hidden") for each.
The existing Cc check catches C0 and C1 controls and correctly exempts \n and \t. The Cf handling is the part that is an enumeration where a category test belongs.
Why it matters at this particular boundary
A title carrying a zero-width space or a soft hyphen renders as ordinary text. The reviewer who approves that pull request reads the rendered title, so invisible characters can split a word, hide a fragment, or make two different titles look identical. That is the same failure the bidirectional check exists to prevent, and the module already pays the cost of scanning every character.
A BOM surviving into the first line is a second, smaller case: it is invisible and it is part of the title the publisher writes out.
The fix is a simplification, not an addition
unicodedata.category(character) == "Cf" subsumes the entire twelve-character _BIDIRECTIONAL_CONTROLS set and closes all seven gaps. The frozenset and the membership test can both go, leaving one category test beside the existing Cc one.
Interaction with #39, which decides whether this matters
refactor/orchestrator-free-runtime deletes this file, and it deletes .github/workflows/hourly-nim-product-development.yml with it. I checked before writing this: the only surviving mention on that branch is a historical planning document, and the workflow that called the script is gone entirely. So that branch removes the whole model-authored publication path rather than removing its guard, which is coherent and is not a security regression.
On main today the path is live. The workflow installs the script to $RUNNER_TEMP with mode 0500 and runs it on PR_MESSAGE.md before the publisher consumes the output.
So the decision is simple. If #39 lands, this closes itself. If it does not land, or lands later, the gap is open on main in the meantime and the fix is a two-line simplification.
I have not edited the file, because #39 occupies it.
Gap
scripts/prepare_agent_pr_message.pyis the boundary where model-authored pull-request metadata becomes text a human reviewer reads. Its own docstring states the purpose: "Safely parse untrusted OpenCode pull-request metadata for a trusted publisher", and_reject_unsafe_textpromises to "Reject control and bidirectional characters that can spoof metadata".It rejects twelve named characters. Measured just now, every one of them is Unicode category
Cf, and seven otherCfcharacters pass straight through:Verified by calling
_reject_unsafe_text("fix: harmless<CHAR>hidden")for each.The existing
Cccheck catches C0 and C1 controls and correctly exempts\nand\t. TheCfhandling is the part that is an enumeration where a category test belongs.Why it matters at this particular boundary
A title carrying a zero-width space or a soft hyphen renders as ordinary text. The reviewer who approves that pull request reads the rendered title, so invisible characters can split a word, hide a fragment, or make two different titles look identical. That is the same failure the bidirectional check exists to prevent, and the module already pays the cost of scanning every character.
A BOM surviving into the first line is a second, smaller case: it is invisible and it is part of the title the publisher writes out.
The fix is a simplification, not an addition
unicodedata.category(character) == "Cf"subsumes the entire twelve-character_BIDIRECTIONAL_CONTROLSset and closes all seven gaps. The frozenset and the membership test can both go, leaving one category test beside the existingCcone.Interaction with #39, which decides whether this matters
refactor/orchestrator-free-runtimedeletes this file, and it deletes.github/workflows/hourly-nim-product-development.ymlwith it. I checked before writing this: the only surviving mention on that branch is a historical planning document, and the workflow that called the script is gone entirely. So that branch removes the whole model-authored publication path rather than removing its guard, which is coherent and is not a security regression.On
maintoday the path is live. The workflow installs the script to$RUNNER_TEMPwith mode 0500 and runs it onPR_MESSAGE.mdbefore the publisher consumes the output.So the decision is simple. If #39 lands, this closes itself. If it does not land, or lands later, the gap is open on
mainin the meantime and the fix is a two-line simplification.I have not edited the file, because #39 occupies it.