Skip to content

ci: make pkg.pr.new comment flow fork-safe#170

Merged
kongenpei merged 5 commits intolarksuite:mainfrom
kongenpei:codex/pkg-pr-new-fork-safe-comment
Apr 1, 2026
Merged

ci: make pkg.pr.new comment flow fork-safe#170
kongenpei merged 5 commits intolarksuite:mainfrom
kongenpei:codex/pkg-pr-new-fork-safe-comment

Conversation

@kongenpei
Copy link
Copy Markdown
Collaborator

@kongenpei kongenpei commented Apr 1, 2026

What changed

  • Split pkg.pr.new flow into two workflows:
    • untrusted pull_request workflow builds/publishes and uploads a small comment payload artifact
    • trusted workflow_run workflow downloads payload and upserts the PR install guide comment
  • Switched artifact download to official action pinned by SHA:
    • actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c (v8)
  • Kept pinned official upload action:
    • actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 (v4)
  • Preserved existing install guide format and marker (<!-- pkg-pr-new-install-guide -->) to update in place.
  • Test in test ci 2 kongenpei/lark-cli#5, it works

Why

Fork PRs run with restricted token on pull_request, so comment write fails with 403 Resource not accessible by integration.
This change moves write operations to trusted context while keeping PR code execution in untrusted context.

Security model

  • No write permission in untrusted pull_request workflow.
  • No PR code checkout/execution in trusted comment workflow.
  • Payload is validated before comment write (pkg.pr.new URL prefix + integer PR number).

Verification

  • YAML files parse successfully locally.
  • Rebased on latest origin/main and resolved workflow conflict.

Summary by CodeRabbit

  • New Features

    • Automatically creates or updates an install-guide comment on pull requests after the preview package workflow succeeds, including an optional skill-update section when source metadata is safe.
  • Chores

    • Split comment assembly into a separate payload step and added stricter validation for PR, URL, and source metadata.
    • Reduced workflow permissions and moved comment publishing to run only after a successful preview package run.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Producer workflow writes a JSON artifact with {pr, url, sourceRepo, sourceBranch}. A new consumer workflow triggers on the producer's successful run for PRs, downloads and validates the artifact, then creates or updates a bot comment containing the install guide marker on the PR.

Changes

Cohort / File(s) Summary
Producer workflow
.github/workflows/pkg-pr-new.yml
Removed pull-requests: write and the in-workflow comment-upsert. Added a Node step that reads output.json, validates/extracts the first package url and PR_NUMBER, writes pkg-pr-new-comment-payload.json, and uploads it as the pkg-pr-new-comment-payload artifact.
Consumer / Comment workflow
.github/workflows/pkg-pr-new-comment.yml
New workflow triggered on workflow_run success for PR events. Lists artifacts, downloads pkg-pr-new-comment-payload, validates pr, url (must be https://pkg.pr.new/...), and safe sourceRepo/sourceBranch before paginating PR comments to update an existing <!-- pkg-pr-new-install-guide --> bot comment or create one.

Sequence Diagram(s)

sequenceDiagram
  participant Producer as "Producer CI\n(pkg-pr-new)"
  participant GH as "GitHub Actions\n(workflow_run)"
  participant Consumer as "Comment CI\n(pkg-pr-new-comment)"
  participant API as "GitHub API\n(issues/comments)"

  Producer->>GH: complete workflow_run (success, PR)
  GH->>Consumer: trigger workflow with run metadata
  Consumer->>Consumer: list artifacts & download payload
  Consumer->>Consumer: validate pr, url, sourceRepo/sourceBranch
  Consumer->>API: list PR comments (paginated)
  API-->>Consumer: return comments
  alt marker found
    Consumer->>API: update existing comment (contains marker)
  else not found
    Consumer->>API: create new bot comment with install/skill content
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • liangshuo-1

Poem

🐇 I hopped an artifact across the stream,
A tiny JSON tucked in a beam.
Workflows whispered, payloads shone,
Bot comments found their rightful home.
Hooray — CI hums and I nibble a crumb.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ci: make pkg.pr.new comment flow fork-safe' accurately describes the main change: splitting the workflow into two jobs to separate untrusted build/publish from trusted comment operations, making the flow compatible with fork PRs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@kongenpei kongenpei force-pushed the codex/pkg-pr-new-fork-safe-comment branch from 08dfd07 to 1347997 Compare April 1, 2026 03:44
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
.github/workflows/pkg-pr-new-comment.yml (2)

11-12: Trim token scope to least privilege.

pull-requests: write appears unnecessary here since the workflow uses github.rest.issues.* comment APIs. Keeping only issues: write reduces write surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pkg-pr-new-comment.yml around lines 11 - 12, Remove the
unnecessary write scope to follow least-privilege: delete the "pull-requests:
write" entry and keep only "issues: write" in the workflow permissions block
(the keys shown as pull-requests: write and issues: write) since the job only
calls github.rest.issues.* comment APIs.

37-39: Harden URL validation beyond startsWith.

startsWith("https://pkg.pr.new/") is too permissive for a trusted write path. Parse with new URL(url), require exact protocol/host, and reject whitespace/control characters before composing the install command.

🔒 Suggested fix
-            if (!url || !url.startsWith("https://pkg.pr.new/")) {
-              throw new Error(`Invalid package URL in payload: ${url}`);
-            }
+            let parsed;
+            try {
+              parsed = new URL(url);
+            } catch {
+              throw new Error(`Invalid package URL in payload: ${url}`);
+            }
+            if (
+              parsed.protocol !== "https:" ||
+              parsed.hostname !== "pkg.pr.new" ||
+              /\s/.test(url)
+            ) {
+              throw new Error(`Invalid package URL in payload: ${url}`);
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pkg-pr-new-comment.yml around lines 37 - 39, Replace the
brittle startsWith check for the payload URL with strict parsing using new
URL(url): validate that the parsed URL.protocol === "https:" and parsed URL.host
=== "pkg.pr.new", ensure the original url has no leading/trailing whitespace or
control characters (e.g. url.trim() === url and no control chars via a regex),
and throw the same error if any check fails; apply these checks before composing
or executing the install command so only exactly "https" + host "pkg.pr.new"
URLs are accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/pkg-pr-new-comment.yml:
- Around line 33-36: The workflow currently derives issueNumber from the
untrusted artifact payload.pr; update the logic to read the trusted PR number
from github.event.workflow_run.pull_requests[0].number (e.g. set issueNumber =
parseInt(process.env.GITHUB_EVENT_WORKFLOW_RUN_PULL_REQUEST_NUMBER) or access
event JSON directly) and only optionally compare it to payload.pr for sanity
checking; locate the block that reads payload (payload, url, issueNumber) and
replace assignment of issueNumber with the trusted workflow_run value (and if
you keep payload.pr, add an assert or warning if they differ).

---

Nitpick comments:
In @.github/workflows/pkg-pr-new-comment.yml:
- Around line 11-12: Remove the unnecessary write scope to follow
least-privilege: delete the "pull-requests: write" entry and keep only "issues:
write" in the workflow permissions block (the keys shown as pull-requests: write
and issues: write) since the job only calls github.rest.issues.* comment APIs.
- Around line 37-39: Replace the brittle startsWith check for the payload URL
with strict parsing using new URL(url): validate that the parsed URL.protocol
=== "https:" and parsed URL.host === "pkg.pr.new", ensure the original url has
no leading/trailing whitespace or control characters (e.g. url.trim() === url
and no control chars via a regex), and throw the same error if any check fails;
apply these checks before composing or executing the install command so only
exactly "https" + host "pkg.pr.new" URLs are accepted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72444251-968a-491c-b78c-ddff79aa6c97

📥 Commits

Reviewing files that changed from the base of the PR and between c4851a5 and 08dfd07.

📒 Files selected for processing (2)
  • .github/workflows/pkg-pr-new-comment.yml
  • .github/workflows/pkg-pr-new.yml

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 1, 2026

Greptile Summary

This PR splits the pkg.pr.new preview comment flow into two workflows to fix 403 Resource not accessible by integration errors on fork PRs. The untrusted pull_request workflow now only builds, publishes, and uploads a validated JSON payload artifact (containing the PR number, package URL, and source metadata). A new trusted workflow_run workflow—which fires after the publisher succeeds—downloads that artifact and upserts the install-guide comment with full write permissions.

Key design decisions:

  • Fork safety: the comment step is driven by the PR number embedded in the artifact, not by workflow_run.pull_requests (which is always empty for fork PRs). The optional cross-check against workflow_run.pull_requests[0].number is correctly skipped when that array is empty.
  • Minimal permissions: the publisher retains only contents: read; the consumer adds actions: read, issues: write, and pull-requests: write.
  • Defense in depth: all payload fields are re-validated in the trusted context before any write occurs (positive-integer PR number, https://pkg.pr.new/ hostname pinning with control-character rejection, allowlist regexes for source repo/branch to prevent markdown injection).
  • Artifact scope: both check and download steps reference the same run_id from the triggering workflow_run event, so there is no cross-run ambiguity.
  • The graceful "artifact not found" path (via core.notice + conditional skip) correctly handles non-PR runs or runs where publishing was skipped.

Confidence Score: 5/5

Safe to merge — the fork-safe split is implemented correctly, all payload fields are re-validated in the trusted context, and the primary goal (posting comments from fork PRs) is verified to work by the author's own test run.

No new P0 or P1 issues were found in this review. The PR correctly handles the empty workflow_run.pull_requests array for fork PRs by using payloadPr from the artifact as the authoritative PR number. The security model (write ops only in trusted context, no PR code execution in comment workflow) is sound. Previously raised concerns are already addressed in this implementation.

No files require special attention.

Important Files Changed

Filename Overview
.github/workflows/pkg-pr-new.yml Publisher workflow stripped of write permissions; now builds a validated JSON payload (positive-integer PR number, https://pkg.pr.new/ URL check) and uploads it as an artifact via a safely-quoted heredoc, eliminating all write operations from the untrusted context.
.github/workflows/pkg-pr-new-comment.yml New trusted workflow_run consumer: downloads the payload artifact by run-id, re-validates the PR number (positive integer), URL (strict hostname + control-char check), and source metadata (allowlist regex) before upserting the PR comment. Fork-safe because the PR number is sourced from the artifact, not the empty workflow_run.pull_requests array.

Sequence Diagram

sequenceDiagram
    participant Fork as Fork PR
    participant UntrustedWF as pkg-pr-new.yml<br/>(pull_request, no write perms)
    participant Artifacts as GitHub Artifacts
    participant TrustedWF as pkg-pr-new-comment.yml<br/>(workflow_run, write perms)
    participant PR as PR Comment

    Fork->>UntrustedWF: push / sync PR
    UntrustedWF->>UntrustedWF: build + publish to pkg.pr.new
    UntrustedWF->>UntrustedWF: validate URL & PR number
    UntrustedWF->>Artifacts: upload pkg-pr-new-comment-payload.json
    UntrustedWF-->>TrustedWF: workflow_run completed (success)

    TrustedWF->>Artifacts: listWorkflowRunArtifacts (actions:read)
    Artifacts-->>TrustedWF: artifact found?
    alt artifact found
        TrustedWF->>Artifacts: download-artifact (run-id scoped)
        Artifacts-->>TrustedWF: pkg-pr-new-comment-payload.json
        TrustedWF->>TrustedWF: validate PR#, URL, sourceRepo/Branch
        TrustedWF->>PR: upsert comment (pull-requests:write)
    else not found
        TrustedWF->>TrustedWF: core.notice + skip
    end
Loading

Reviews (5): Last reviewed commit: "ci: allow PR comment workflow to write p..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
.github/workflows/pkg-pr-new-comment.yml (1)

65-71: ⚠️ Potential issue | 🟠 Major

Do not default to untrusted payload.pr for issueNumber.

At Line 65, the trusted workflow still targets comments using artifact-controlled payloadPr when workflow_run.pull_requests is empty. That keeps the write target partially untrusted in the privileged context.

🔧 Suggested hardening
-            const issueNumber = payloadPr;
             const runPrNumber = context.payload.workflow_run?.pull_requests?.[0]?.number;
-            if (Number.isInteger(runPrNumber) && runPrNumber !== issueNumber) {
+            let issueNumber = runPrNumber;
+            if (!Number.isInteger(issueNumber)) {
+              const headSha = context.payload.workflow_run?.head_sha;
+              if (typeof headSha !== "string" || headSha.length === 0) {
+                throw new Error("Unable to determine trusted PR number: missing workflow_run.head_sha");
+              }
+              const prs = await github.paginate(
+                github.rest.repos.listPullRequestsAssociatedWithCommit,
+                {
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  commit_sha: headSha,
+                  per_page: 100,
+                },
+              );
+              if (prs.length !== 1 || !Number.isInteger(prs[0]?.number)) {
+                throw new Error(`Unable to determine a unique trusted PR for head SHA: ${headSha}`);
+              }
+              issueNumber = prs[0].number;
+            }
+            if (payloadPr !== issueNumber) {
               throw new Error(
                 `PR number mismatch between workflow_run (${runPrNumber}) and artifact payload (${issueNumber})`,
               );
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pkg-pr-new-comment.yml around lines 65 - 71, The code
currently assigns the write target from the untrusted payloadPr (const
issueNumber = payloadPr) even when workflow_run.pull_requests is empty; change
this to trust workflow_run first: compute runPrNumber from
context.payload.workflow_run?.pull_requests?.[0]?.number and, if
Number.isInteger(runPrNumber), set issueNumber = runPrNumber; otherwise do NOT
fall back to payloadPr—fail early or skip the privileged action (throw an Error
or return) so privileged code never uses artifact-controlled payloadPr; update
references to issueNumber accordingly (variables: issueNumber, payloadPr,
runPrNumber, context.payload.workflow_run?.pull_requests).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/pkg-pr-new-comment.yml:
- Around line 65-71: The code currently assigns the write target from the
untrusted payloadPr (const issueNumber = payloadPr) even when
workflow_run.pull_requests is empty; change this to trust workflow_run first:
compute runPrNumber from
context.payload.workflow_run?.pull_requests?.[0]?.number and, if
Number.isInteger(runPrNumber), set issueNumber = runPrNumber; otherwise do NOT
fall back to payloadPr—fail early or skip the privileged action (throw an Error
or return) so privileged code never uses artifact-controlled payloadPr; update
references to issueNumber accordingly (variables: issueNumber, payloadPr,
runPrNumber, context.payload.workflow_run?.pull_requests).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63c3676e-cd4b-4247-b640-ab773108dc34

📥 Commits

Reviewing files that changed from the base of the PR and between 7678b5b and 5e3e156.

📒 Files selected for processing (1)
  • .github/workflows/pkg-pr-new-comment.yml

@kongenpei kongenpei requested a review from liangshuo-1 April 1, 2026 05:55
@kongenpei kongenpei merged commit 6463ab1 into larksuite:main Apr 1, 2026
4 of 5 checks passed
tuxedomm pushed a commit that referenced this pull request Apr 3, 2026
* ci: make pkg.pr.new comment flow fork-safe

* ci: harden trusted comment workflow inputs

* ci: skip comment steps when payload artifact is missing

* ci: use artifact PR number when workflow_run pull_requests is empty

* ci: allow PR comment workflow to write pull requests

---------

Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
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.

2 participants