fix(ci): admit fork PRs without repository variables

This commit is contained in:
chichuan
2026-08-25 21:43:27 +08:00
parent 797766ebf7
commit c77046531e
4 changed files with 36 additions and 4 deletions
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Fork pull-request admission** — keeps the read-only Reviewer Router identity check fail-closed while allowing external contributors' CI to use the reviewed public App slug when GitHub withholds repository variables.
+14 -1
View File
@@ -938,7 +938,15 @@ jobs:
const pullNumber = context.payload.pull_request.number;
const eventHeadSha = context.payload.pull_request.head.sha;
const eventBaseSha = context.payload.pull_request.base.sha;
const appSlug = process.env.REVIEWER_ROUTER_APP_SLUG?.trim();
const configuredAppSlug = process.env.REVIEWER_ROUTER_APP_SLUG?.trim();
const reviewedForkAppSlug = 'dingtalk-dws-reviewer-router';
const pullHeadRepository =
context.payload.pull_request.head.repo.full_name?.toLowerCase();
const baseRepository = `${owner}/${repo}`.toLowerCase();
const isForkPull =
Boolean(pullHeadRepository) && pullHeadRepository !== baseRepository;
const appSlug =
configuredAppSlug || (isForkPull ? reviewedForkAppSlug : '');
if (
!appSlug ||
appSlug !== appSlug.toLowerCase() ||
@@ -949,6 +957,11 @@ jobs:
);
return;
}
if (!configuredAppSlug) {
core.info(
`Fork pull request cannot read the repository App slug variable; using the reviewed public slug ${reviewedForkAppSlug}.`,
);
}
const expectedAppOwner = `${appSlug}[bot]`;
const writerRulesetName = 'main-merge-writers';
const skipWorkflowPattern =
+10 -2
View File
@@ -184,8 +184,16 @@ ruleset before it can enable, reconcile, or synchronously merge. The read-only
`Test`
token may receive a repository projection with both merge-default properties
omitted; it accepts only that complete omission or exact `MERGE_MESSAGE` plus
`PR_TITLE`/`BLANK`, while partial or malformed projections fail closed. The
minted App's `Contents: write` token must observe the exact reviewed defaults
`PR_TITLE`/`BLANK`, while partial or malformed projections fail closed.
The same unprivileged `pull_request` job may receive an empty repository-variable
projection for an external fork. Only when the event head repository differs
from the base repository does it substitute the exact reviewed public slug
`dingtalk-dws-reviewer-router` for identity comparison. An empty variable on a
same-repository PR and every malformed non-empty value still fail closed. This
fallback neither mints a token nor grants merge authority; the base-owned
Router continues to require its minted App slug to equal the repository
variable before any mutation. The minted App's `Contents: write` token must
observe the exact reviewed defaults
before either mutation path proceeds. GitHub hides the complete
`bypass_actors` list from low-privilege callers, so the rollout audit must still
keep the writer list at exactly the Reviewer App, `haofeng0705` (ID
@@ -724,7 +724,13 @@ func TestCodeAdmissionEnforcesReviewerRouterWriterBoundary(t *testing.T) {
"name: Verify auto-merge identity",
"if: github.event_name == 'pull_request' && github.event.pull_request.draft == false",
"REVIEWER_ROUTER_APP_SLUG: ${{ vars.REVIEWER_ROUTER_APP_SLUG }}",
"process.env.REVIEWER_ROUTER_APP_SLUG?.trim()",
"const configuredAppSlug = process.env.REVIEWER_ROUTER_APP_SLUG?.trim()",
"const reviewedForkAppSlug = 'dingtalk-dws-reviewer-router'",
"context.payload.pull_request.head.repo.full_name?.toLowerCase()",
"const baseRepository = `${owner}/${repo}`.toLowerCase()",
"Boolean(pullHeadRepository) && pullHeadRepository !== baseRepository",
"configuredAppSlug || (isForkPull ? reviewedForkAppSlug : '')",
"Fork pull request cannot read the repository App slug variable; using the reviewed public slug",
"const expectedAppOwner = `${appSlug}[bot]`",
"github.rest.pulls.get",
"github.rest.repos.get",