Skip to content

Commit 5df7d02

Browse files
authored
Merge pull request #316 from kapildev421/main
ci:adding workflows authorize gate + PR-head context
2 parents b8a8d04 + 54ec50c commit 5df7d02

6 files changed

Lines changed: 238 additions & 66 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: Authorize PR
2+
description: >
3+
Security gate for pull_request_target workflows only. Checks actor
4+
write permission, author association, and label presence for external
5+
fork PRs. Strips the gate label on synchronize events from non-writers
6+
to force re-review after new commits.
7+
8+
inputs:
9+
label:
10+
description: "Label name required for external fork PRs (default: verify)"
11+
required: false
12+
default: "verify"
13+
github-token:
14+
description: "GitHub token for permission check and label API calls"
15+
required: true
16+
17+
outputs:
18+
allowed:
19+
description: "true when the workflow is authorized to proceed"
20+
value: ${{ steps.check.outputs.allowed }}
21+
22+
runs:
23+
using: composite
24+
steps:
25+
# -----------------------------------------------------------------
26+
# 1. Resolve actor permission level
27+
# -----------------------------------------------------------------
28+
- name: Check actor write permission
29+
id: perm
30+
uses: scherermichael-oss/action-has-permission@136e061bfe093832d87f090dd768e14e27a740d3 # 1.0.6
31+
with:
32+
required-permission: write
33+
env:
34+
GITHUB_TOKEN: ${{ inputs.github-token }}
35+
36+
# -----------------------------------------------------------------
37+
# 2. Decide whether the run is authorized
38+
# -----------------------------------------------------------------
39+
- name: Check authorization
40+
id: check
41+
shell: bash
42+
env:
43+
EVENT: ${{ github.event_name }}
44+
ACTION: ${{ github.event.action }}
45+
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
46+
BASE_REPO: ${{ github.repository }}
47+
AUTHOR_ASSOC: ${{ github.event.pull_request.author_association }}
48+
HAS_WRITE: ${{ steps.perm.outputs.has-permission }}
49+
LABEL_NAME: ${{ inputs.label }}
50+
LABELS_JSON: ${{ toJSON(github.event.pull_request.labels.*.name) }}
51+
run: |
52+
# Non-PR events (push, workflow_dispatch, workflow_call) are always trusted
53+
if [ "$EVENT" != "pull_request_target" ]; then
54+
echo "::notice::Non-PR event ($EVENT) — authorized"
55+
echo "allowed=true" >> "$GITHUB_OUTPUT"
56+
exit 0
57+
fi
58+
59+
# Same-repo PRs are always trusted
60+
if [ "$HEAD_REPO" = "$BASE_REPO" ]; then
61+
echo "::notice::Same-repo PR — authorized"
62+
echo "allowed=true" >> "$GITHUB_OUTPUT"
63+
exit 0
64+
fi
65+
66+
echo "::notice::Actor '$GITHUB_ACTOR' write-or-higher: $HAS_WRITE"
67+
68+
# Actor with write (or higher) is trusted even from a fork
69+
if [ "$HAS_WRITE" = "1" ]; then
70+
echo "::notice::Actor has write-or-higher permission — authorized"
71+
echo "allowed=true" >> "$GITHUB_OUTPUT"
72+
exit 0
73+
fi
74+
75+
# Org members / collaborators are trusted even from forks
76+
if [[ "$AUTHOR_ASSOC" =~ ^(MEMBER|OWNER|COLLABORATOR)$ ]]; then
77+
echo "::notice::Trusted author ($AUTHOR_ASSOC) — authorized"
78+
echo "allowed=true" >> "$GITHUB_OUTPUT"
79+
exit 0
80+
fi
81+
82+
# External fork: require the gate label
83+
HAS_LABEL=$(echo "$LABELS_JSON" | jq -r --arg l "$LABEL_NAME" \
84+
'if . == null then "false" elif any(. == $l) then "true" else "false" end')
85+
86+
# On synchronize from a non-writer, reject even if the label is still
87+
# present. The label was granted for the previous commit; new unreviewed
88+
# code must not run with secrets. The strip step below removes the label
89+
# so that a re-review + re-label is required for the next run.
90+
if [ "$ACTION" = "synchronize" ]; then
91+
echo "::warning::External fork synchronize from non-writer — blocking until re-review"
92+
echo "allowed=false" >> "$GITHUB_OUTPUT"
93+
exit 0
94+
fi
95+
96+
if [ "$HAS_LABEL" = "true" ]; then
97+
echo "::notice::Fork PR with '$LABEL_NAME' label — authorized"
98+
echo "allowed=true" >> "$GITHUB_OUTPUT"
99+
else
100+
echo "::warning::External fork PR without '$LABEL_NAME' label — skipping"
101+
echo "::warning::A team member must review the code and apply the '$LABEL_NAME' label"
102+
echo "allowed=false" >> "$GITHUB_OUTPUT"
103+
fi
104+
105+
# -----------------------------------------------------------------
106+
# 3. Strip the gate label on synchronize from non-writers
107+
# This forces a maintainer to re-review before the NEXT run.
108+
# -----------------------------------------------------------------
109+
- name: Strip label on new pushes from non-writers
110+
if: |
111+
steps.perm.outputs.has-permission != '1' &&
112+
github.event.action == 'synchronize' &&
113+
github.event.pull_request.head.repo.full_name != github.repository
114+
shell: bash
115+
env:
116+
GH_TOKEN: ${{ inputs.github-token }}
117+
PR_NUMBER: ${{ github.event.pull_request.number }}
118+
LABEL: ${{ inputs.label }}
119+
run: |
120+
gh api -X DELETE \
121+
"repos/${{ github.repository }}/issues/${PR_NUMBER}/labels/${LABEL}" \
122+
2>/dev/null || true
123+
echo "::warning::Removed '${LABEL}' label — new commits from non-writer. Re-review required."

.github/workflows/ci-pr.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: PR CI
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, reopened, synchronize, labeled]
6+
branches: [main]
7+
8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: pr-ci-${{ github.event.pull_request.number }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
authorize:
17+
if: github.event.action != 'labeled' || github.event.label.name == 'verify'
18+
runs-on: ubuntu-latest
19+
permissions:
20+
contents: read
21+
pull-requests: write
22+
issues: write
23+
outputs:
24+
allowed: ${{ steps.auth.outputs.allowed }}
25+
26+
steps:
27+
- name: Checkout workflow code
28+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
29+
30+
- name: Authorize PR execution
31+
id: auth
32+
uses: ./.github/actions/authorize-pr
33+
with:
34+
github-token: ${{ github.token }}
35+
36+
- name: Summarize skipped run
37+
if: steps.auth.outputs.allowed != 'true'
38+
run: |
39+
echo "PR CI is limited to members, collaborators, or external PRs labeled 'verify' (re-review required after each push)." >> "$GITHUB_STEP_SUMMARY"
40+
41+
ci:
42+
needs: authorize
43+
if: needs.authorize.outputs.allowed == 'true'
44+
uses: ./.github/workflows/ci-reusable.yml
45+
with:
46+
checkout_ref: ${{ github.event.pull_request.head.sha }}
47+
checkout_repository: ${{ github.event.pull_request.head.repo.full_name }}

.github/workflows/ci-push.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Push CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
permissions:
8+
contents: read
9+
10+
concurrency:
11+
group: push-ci-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
jobs:
15+
ci:
16+
uses: ./.github/workflows/ci-reusable.yml
17+
with:
18+
checkout_ref: ${{ github.sha }}
19+
checkout_repository: ${{ github.repository }}

.github/workflows/ci-reusable.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Reusable CI
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
checkout_ref:
7+
description: Git ref or SHA to validate
8+
required: true
9+
type: string
10+
checkout_repository:
11+
description: Repository that contains the ref to validate
12+
required: true
13+
type: string
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
quality:
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
25+
with:
26+
ref: ${{ inputs.checkout_ref }}
27+
repository: ${{ inputs.checkout_repository }}
28+
persist-credentials: false
29+
30+
- name: Setup Node
31+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
32+
with:
33+
node-version-file: .nvmrc
34+
cache: npm
35+
36+
- name: Install dependencies
37+
run: npm ci --legacy-peer-deps
38+
39+
- name: Compile locale files
40+
run: npm run lingui:compile
41+
42+
- name: Run ESLint
43+
run: npm run lint
44+
45+
- name: Run TypeScript typecheck
46+
run: npm run typecheck
47+
48+
- name: Run tests
49+
run: npm test

.github/workflows/lint.yml

Lines changed: 0 additions & 33 deletions
This file was deleted.

.github/workflows/test.yml

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)