Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions .github/workflows/forge-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,39 @@ concurrency:
cancel-in-progress: false

jobs:
# The gate is its own job so it can say why it declined. As a one-line `if:`
# on the work itself, a skipped run is indistinguishable from a broken
# expression — which is exactly what happened: a MEMBER's issue was skipped
# and there was nothing anywhere saying which clause was false.
gate:
runs-on: ubuntu-latest
outputs:
allowed: ${{ steps.check.outputs.allowed }}
steps:
- id: check
env:
EVENT: ${{ github.event_name }}
ASSOC: ${{ github.event.issue.author_association }}
COMMENT_ASSOC: ${{ github.event.comment.author_association }}
IS_PR: ${{ github.event.issue.pull_request != null }}
run: |
assoc="$ASSOC"
[ "$EVENT" = "issue_comment" ] && assoc="$COMMENT_ASSOC"

allowed=false
case "$assoc" in OWNER|MEMBER|COLLABORATOR) allowed=true ;; esac
# Comments on pull requests belong to the PR workflow.
[ "$IS_PR" = "true" ] && allowed=false

echo "allowed=$allowed" >> "$GITHUB_OUTPUT"
echo "::notice::event=$EVENT author_association=$assoc on_pull_request=$IS_PR → allowed=$allowed"
if [ "$allowed" != "true" ]; then
echo "::notice::Declined. Only OWNER, MEMBER or COLLABORATOR can start a run on a public repository."
fi

analyze:
# Issues only — comments on pull requests belong to the PR workflow — and
# only from an author the repository already trusts.
if: >-
${{ !github.event.issue.pull_request
&& ((github.event_name == 'issues'
&& contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association))
|| (github.event_name == 'issue_comment'
&& contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))) }}
needs: gate
if: needs.gate.outputs.allowed == 'true'
runs-on: ubuntu-latest

steps:
Expand Down
56 changes: 45 additions & 11 deletions .github/workflows/forge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,52 @@ concurrency:
cancel-in-progress: false

jobs:
# The gate is its own job so it can say why it declined. As a one-line `if:`
# on the work itself, a skipped run is indistinguishable from a broken
# expression, and there is nothing in the log to tell them apart.
gate:
runs-on: ubuntu-latest
outputs:
allowed: ${{ steps.check.outputs.allowed }}
steps:
- id: check
env:
EVENT: ${{ github.event_name }}
ASSOC: ${{ github.event.comment.author_association }}
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
THIS_REPO: ${{ github.repository }}
IS_PR_COMMENT: ${{ github.event.issue.pull_request != null }}
run: |
allowed=false
case "$EVENT" in
pull_request)
# A fork PR gets no secrets anyway; failing on every drive-by PR
# is noise, so decline it quietly here instead.
[ "$HEAD_REPO" = "$THIS_REPO" ] && allowed=true
reason="head=$HEAD_REPO this=$THIS_REPO"
;;
pull_request_review_comment)
case "$ASSOC" in OWNER|MEMBER|COLLABORATOR) allowed=true ;; esac
reason="association=$ASSOC"
;;
issue_comment)
# Only comments on pull requests; plain issues are the other workflow.
if [ "$IS_PR_COMMENT" = "true" ]; then
case "$ASSOC" in OWNER|MEMBER|COLLABORATOR) allowed=true ;; esac
fi
reason="association=$ASSOC on_pull_request=$IS_PR_COMMENT"
;;
esac

echo "allowed=$allowed" >> "$GITHUB_OUTPUT"
echo "::notice::event=$EVENT $reason → allowed=$allowed"
if [ "$allowed" != "true" ]; then
echo "::notice::Declined. Same-repository pull requests, and comments from OWNER, MEMBER or COLLABORATOR only."
fi

review:
# Two gates, both required on a public repository (see the note above):
# 1. same-repository PRs only — a fork PR gets no secrets anyway, and
# failing loudly on every drive-by PR is just noise.
# 2. trusted authors only — OWNER, MEMBER or COLLABORATOR. Anyone else
# commenting "/review" is a stranger spending your credits.
if: >-
${{ (github.event_name == 'pull_request'
&& github.event.pull_request.head.repo.full_name == github.repository)
|| ((github.event_name == 'pull_request_review_comment'
|| (github.event_name == 'issue_comment' && github.event.issue.pull_request))
&& contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) }}
needs: gate
if: needs.gate.outputs.allowed == 'true'
runs-on: ubuntu-latest

steps:
Expand Down
Loading