Skip to content

ci: check PR bodies for relay image URLs - #6745

Open
hrithiksaini99 wants to merge 2 commits into
block:mainfrom
hrithiksaini99:fix/ci-pr-image-urls
Open

hrithiksaini99 wants to merge 2 commits into
block:mainfrom
hrithiksaini99:fix/ci-pr-image-urls

Conversation

@hrithiksaini99

@hrithiksaini99 hrithiksaini99 commented Aug 25, 2026

Copy link
Copy Markdown

Summary

  • Enforce the existing PR image URL guard whenever a pull request body can change.
  • Read the body from GitHub's event JSON and pass it to the existing checker as data, without evaluating contributor-controlled text.
  • Run only trusted base-branch code in the pull_request_target workflow, with read-only contents permission, a SHA-pinned checkout, and persisted credentials disabled.
  • Add regression coverage for rejected relay media URLs, accepted GitHub attachments, empty bodies, non-PR events, and shell-shaped input.

Related issue

Fixes #6726.

Required-check status

The repository's active default-branch ruleset does not currently list Check PR image URLs as a required status check, so the new workflow is advisory until that repository setting is updated. Maintainer follow-up after merge: add Check PR image URLs to the required checks for the default branch.

The contract test remains part of ci.yml, but that verifies the checker integration rather than enforcing each PR body's current content.

Untrusted-input coverage

The shell-injection fixture places a literal $(touch "...") expression in the PR body and asserts that the target file is never created. The body itself is extracted by jq into a temporary Markdown file; only that generated file path is passed to the checker, so PR text is not evaluated or supplied as command-line arguments.

Testing

  • just ci
  • ./scripts/test-pr-image-url-event.sh
  • actionlint .github/workflows/pr-image-urls.yml
  • shellcheck scripts/check-pr-image-urls-event.sh scripts/test-pr-image-url-event.sh scripts/check-pr-image-urls.sh

No screenshots: this is a CI-only change.

@hrithiksaini99
hrithiksaini99 requested a review from a team as a code owner August 25, 2026 04:15

@Chessing234 Chessing234 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is carefully built — checking out github.event.pull_request.base.sha rather than the head, persist-credentials: false, contents: read, a SHA-pinned checkout action, and reading the body out of GITHUB_EVENT_PATH as data rather than interpolating ${{ github.event.pull_request.body }} into a run: block. that last one is the mistake this class of workflow usually makes, and the jq type-guard (error("pull_request.body must be a string or null")) closes the rest of it. the mktemp + trap and passing a file path rather than a string means the checker never sees contributor text on a command line either.

three things:

is this a required check? a guard that runs on pull_request_target and exits non-zero is advisory until branch protection lists it. scripts/test-pr-image-url-event.sh gets wired into ci.yml, so the contract test is gated — but the actual body check runs in its own pr-image-urls.yml workflow, and if that job name isn't in the required set, a PR with a relay media URL merges with a red check nobody has to look at. worth saying in the pr which it is; if it needs a settings change, that's the follow-up that makes the code useful.

synchronize can't change a body. the trigger list is [opened, edited, reopened, synchronize], and synchronize fires on every push to the branch — which never alters pull_request.body. on a busy PR that's the majority of the runs, all re-checking identical text. dropping it costs nothing and halves the noise; opened/edited/reopened are the three that can produce a new body.

the exit code on "no pull_request object" is a hard failure. jq -er with error("event does not contain a pull_request object") is correct for this workflow, where the trigger guarantees one. but the script is generic enough that someone will reuse it from a workflow_dispatch or a schedule and get a confusing jq stack trace instead of a skip. a named exit code or a short message before the jq call would make that reuse safe.

smaller: the test coverage listed (rejected relay URLs, accepted GitHub attachments, empty bodies, command-shaped input) is the right set — "command-shaped input" in particular is the one worth having, since it's what proves the data-not-code claim. worth naming in the pr what "command-shaped" means concretely (backticks? $(...)? a leading - that could be read as a flag?) so a reader can tell whether the argument-injection case is covered as well as the shell-injection one.

Signed-off-by: Hrithik saini <49943287+hrithiksaini99@users.noreply.github.com>
Signed-off-by: Hrithik saini <49943287+hrithiksaini99@users.noreply.github.com>
@hrithiksaini99

Copy link
Copy Markdown
Author

Addressed in 51bba27:

  • Removed synchronize; the workflow now runs only for opened, edited, and reopened.
  • Added an explicit non-PR event guard that exits non-zero with error: event does not contain a pull_request object, plus a regression that failed against the prior raw-jq behavior.
  • Queried the active Default ruleset and confirmed Check PR image URLs is not currently required. The PR now calls the workflow advisory and records the maintainer follow-up needed to make it merge-blocking.
  • Expanded the untrusted-input description: the fixture embeds a literal $(touch "...") expression and asserts no file is created. PR body text is written by jq to a temporary file; only the generated path reaches the checker argv, so body content cannot become a flag.

The branch is rebased onto current main. just ci, the focused regression, actionlint, and shellcheck pass on the rebased commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: enforce check-pr-image-urls.sh on PR bodies — relay media URLs ship as broken images

2 participants