Repository navigation
Escape repository-controlled values in text and Markdown output - #125
Merged
Merged
Conversation
thossullivan
force-pushed
the
fix/escape-report-output
branch
from
October 24, 2026 01:44
941f3df to
b1b5be3
Compare
alert --format markdown printed file paths, matched strings, and waiver owners and reasons as raw Markdown, so a crafted file name or waiver could inject links, images, headings, details blocks, or mentions into job summaries and PR comments. The text formats (check, inventory, schedule) and the stderr coverage notes passed control characters through, so a file name containing a newline could emit Actions workflow commands such as ::warning:: into the job log. Markdown alerts now render untrusted values as code spans whose fence outruns any backtick run in the value. Text output and stderr notes run those values through printable(). The GitHub annotation format keeps its existing escaping. The bot's hidden metadata comment now also encodes < and >, because HTML closes a comment at --!> as well as -->. Implemented by an Opus subagent; reviewed by Claude.
Review of the first pass found the same bug class on paths it did not touch: config and policy errors on stderr echoed .model-eol.json text, apply printed plan file paths raw, bot issue evidence and stale-work comments kept newlines, mentions, table pipes, and autolinks, U+2028 or U+2029 in feed text broke the bot's metadata parse (duplicate issues and permanent PR conflicts), and the runner's legacy ##[ command form matches anywhere in a line. printable() moves to lib/printable.mjs and also escapes bidi controls, zero-width marks, and line separators, and breaks ##[. printableLines() keeps intended line breaks in multi-line errors but stops any line from starting a :: command. The bot's markdownText now produces one inline line with @ and | encoded and bare autolinks broken; repository paths, matched strings, and waiver text in bot bodies are code spans. metadataLine encodes U+2028 and U+2029. JSON outputs, GitHub annotations, and ordinary-input text are byte-identical to main. Implemented by an Opus subagent; reviewed by Claude.
GitHub resolves @mentions and #references after decoding HTML entities, so the @ and # encoding from the previous commit still produced live mentions and issue links when rendered. markdownText now inserts a zero-width space after @ and # (and in www. and ://) instead. The Action prints JSON reports into the job log, and the runner's legacy command form matches ##[ anywhere in a line, so a file named "x ##[set-output ...]" reached the log through check --json, plan, inventory, schedule, alert, and verify --json. Printed JSON now writes the second hash as #, which JSON.parse reads back unchanged; objects, digests, receipts, and files written by apply are untouched. The bot's evaluate output, its feed-download and GitHub API warnings, and markdownLink labels (which could show github.com while linking elsewhere) now go through the same escaping. Implemented by an Opus subagent; reviewed by Claude.
Rendering bot text through GitHub's Markdown API showed three gaps. owner/repo/issues/N paths (with or without a scheme), /issues/N, and GH-N still became issue links, which leave cross-references on the target issue; markdownText now puts a zero-width space after every slash and inside GH-N. GitHub detects www. on the raw text, so the zero-width space moves before the dot. A value starting with -, +, =, leading spaces, or N. could still open a rule, list, or code block inside a list item; the leading marker is now encoded and leading spaces dropped. Fifteen hostile and ordinary samples rendered through the API produce no links, mentions, rules, or lists, and read unchanged. Written by Claude after three implementer rounds.
thossullivan
force-pushed
the
fix/escape-report-output
branch
from
October 24, 2026 02:05
b1b5be3 to
895cd98
Compare
thossullivan
added a commit
that referenced
this pull request
Oct 24, 2026
#125 escapes text output and stderr, JSON printed to logs, Markdown alerts, and bot-written bodies, in addition to the annotations and badge JSON the scope already listed.
thossullivan
added a commit
that referenced
this pull request
Oct 24, 2026
…130) * Rewrite the docs for human readers and move agent rules to AGENTS.md The README had grown to 714 lines mixing user docs, the spec pitch, and refresh-pipeline internals, and much of the prose read as machine written. It is now the user path (quick start, commands, Action, config, how retirement dates work, the bot, data sources, limits), with reference material split into docs/CONFIGURATION.md, docs/BOT.md, docs/RECEIPTS.md, and docs/FEEDS.md, and parser behaviour in refresh/README.md. docs/CONTEXT.md, docs/DESIGN_BOT.md, and docs/PRODUCT_PLAN.md are removed. Their settled design decisions now live in CONTRIBUTING.md, the current bot architecture in docs/BOT.md, and agent-only rules in AGENTS.md, which CLAUDE.md imports. Two fact-check passes against the code corrected claims that were wrong before this rewrite too: waiver expiry does not undo a human dismissal, earliest dates can fail CI, the bot opens PRs without a configured eval, cloud and gateway references open issues under scope direct, routes do not make a routed alias fail under scope direct, the refresh never drops models, and amazon.json is carried forward. Stale Azure and Bedrock dates and the demo GIF are refreshed. SPEC.md and SECURITY.md change wording only, not requirements or scope. The two tests that read README text now read docs/BOT.md and docs/RECEIPTS.md. Written by Claude; fact-checked and prose-reviewed by Opus subagents. * Describe the merged parser and fetch behaviour in refresh/README.md Updates the parser notes for #126 through #129: section-scoped earliest precision and the retired-models archive rule, the stricter OpenAI skip rule and the alias | snapshot family rule, the Bedrock regional legacy rule, and bounded fetches with redirect refusal for keyed requests. * Name every escaped output in the security scope #125 escapes text output and stderr, JSON printed to logs, Markdown alerts, and bot-written bodies, in addition to the annotations and badge JSON the scope already listed.
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.
Summary
alert --format markdownnow renders file paths, matched strings, waiver owners and reasons, and channel names as code spans, so repository content can't add links, images, headings,<details>, or mentions to job summaries and PR comments.check,inventory, andscheduletext output and the stderr coverage notes now escape control characters, so a file name with a newline can't emit Actions workflow commands into the job log.<and>, since HTML closes a comment at--!>as well as-->.alert --format github, JSON outputs, CycloneDX, and the badge already escaped correctly and are unchanged.Tests
test/run.mjsand 1 inbot/test/run.mjs. Each fails on the old code.npm testpasses.Visible change: paths in Markdown alerts now render in code font.
🤖 Generated with Claude Code