Comment Bar's diagnosis on the pull request from a write-scoped job - #64
Merged
Conversation
Splits the workflow into a read-only investigation job and a comment job that holds only contents:read and issues:write. The comment job receives no Bar credentials, no evidence text, and no Actions-read token: the two jobs communicate through one base64url job output whose alphabet cannot express a newline or a workflow command, capped at 8 KiB after encoding. The comment destination is the pull request the collector already validated against GitHub. Bar's own pull request number is cross-checked against it and never used to choose where to comment. Model text is treated as hostile. Diagnosis prose is NFKC-normalized, stripped of control and format characters, cleared of URLs and bare domains, has mention and HTML characters folded to lookalikes, and is Markdown-escaped, inside a comment bounded to 12 KiB. Polling only ever issues GET, so it cannot start, retry, or duplicate a Workflow or model call. A total polling timeout still produces a bounded "diagnosis unavailable" comment rather than turning successful evidence ingestion into a failed job, and a rerun updates that comment in place. TLS certificate failures are permanent rather than retried. Marker comments count only when github-actions[bot] wrote them, so a user pasting the public marker cannot wedge Bar's updates; two bot-authored markers still fail closed. Allowlists the two new CI-only scripts in the release-hygiene network check. The comment tests drive fakes and need no exemption.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Posts Bar's diagnosis back to the pull request, from a job that can write
comments and nothing else.
Trust boundary
permissions: {}at the workflow level, granted per job:investigateactions: read,contents: read,pull-requests: readcommentcontents: read,issues: writeThe comment job receives no
BAR_ACCESS_*, no evidence text, and noActions read, since
GITHUB_TOKENis scoped by the job's ownpermissions. Both jobs check out
github.workflow_shawith a SHA-pinnedactions/checkoutandpersist-credentials: false.The payload crosses as a job output consumed through
env:, neverinterpolated into a
run:line. It is base64url with padding stripped,so its alphabet cannot express a newline or a workflow command, and the
8 KiB cap is enforced on the encoded length.
Destination authority
The comment target is the pull request the collector already validated
against GitHub: same repository, head SHA matching the run. Bar's
returned pull request number is cross-checked against that and rejected
on mismatch. It is never used to select where to comment, so a wrong
answer from Bar cannot redirect the comment to another issue.
Hostile model text
Diagnosis prose is NFKC-normalized first, so fullwidth forms unfold
before filtering. Line breaks become spaces; other control and format
characters are deleted, which keeps zero-width and bidi obfuscation from
surviving as separate words. URLs and bare domains are replaced,
@,<,>,&fold to lookalikes, and the result is Markdown-escaped.Verified against 19 attack classes — mentions, HTML, Markdown links and
images, bare URLs and domains,
javascript:,data:, fullwidth-encodedURLs, bidi overrides, zero-width splits, newline and marker injection,
control characters, entities, tables, quotes, backticks. Zero escapes.
Worst-case rendering, four jobs with every field at maximum length and
entirely escapable, is 6,007 bytes against the 12 KiB cap.
Polling
GET only, so it cannot start, retry, or duplicate a Workflow or model
call. Bounded by a deadline, an 8 KiB response cap, and at most four
investigations. Summaries are checked field by field against the
collector's run, attempt, and pull request.
A total timeout produces a bounded "diagnosis unavailable" comment
instead of failing the job, because evidence ingestion succeeded and a
rerun updates the same comment in place. TLS certificate failures are
permanent rather than retried, so an intercepted connection is not
reported as an ordinary timeout.
Comment identity
One marker comment per pull request. A marker registers only when
github-actions[bot]authored it, so a user pasting the public markeris ignored rather than wedging updates; two bot-authored markers still
fail closed. Ambiguous writes re-look-up and verify comment identity and
body before retrying, so a lost response cannot duplicate.
Release hygiene
Two narrow allowlist entries for the CI-only scripts that make outbound
calls.
tests/test_bar_pr_comment.pydrives fakes and needs noexemption. Neither script ships: the wheel packages only
link_mcpandlink_core.Verification
Release hygiene passed, hygiene tests 11 passed, comment tests 24
passed, collector tests 37 passed, full suite 1071 passed, ruff clean,
actionlint clean,
git diff --checkclean.