From 14a8063522b7c4d19c31e85743084219a7a51afc Mon Sep 17 00:00:00 2001 From: Daliys Date: Thu, 23 Jul 2026 01:28:15 +0200 Subject: [PATCH 1/2] security: harden external PR replay workflow with actor gate and pre-scan (#144) --- .github/workflows/approve-external-pr.yml | 57 +++++++++++++++++++++++ CHANGELOG.md | 3 ++ 2 files changed, 60 insertions(+) diff --git a/.github/workflows/approve-external-pr.yml b/.github/workflows/approve-external-pr.yml index 3ae3799..5a6a252 100644 --- a/.github/workflows/approve-external-pr.yml +++ b/.github/workflows/approve-external-pr.yml @@ -79,6 +79,63 @@ jobs: exit 1 fi + # ── 0.1. Authorize maintainer actor ───────────────────────────────────── + - name: Verify maintainer authorization + env: + ACTOR: ${{ github.actor }} + REPOSITORY: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + echo "Verifying authorization for actor @$ACTOR..." + + # Explicit maintainer bypass + if [ "$ACTOR" = "Daliys" ]; then + echo "Actor @$ACTOR is authorized maintainer." + exit 0 + fi + + # Check collaborator permission via GitHub API + PERM=$(gh api "repos/$REPOSITORY/collaborators/$ACTOR/permission" --jq '.permission' 2>/dev/null || echo "none") + case "$PERM" in + admin|write) + echo "Actor @$ACTOR authorized with permission: $PERM" + ;; + *) + echo "::error::Actor @$ACTOR does not have maintainer authorization (permission: $PERM)." + exit 1 + ;; + esac + + # ── 0.2. Content & Security Pre-scan ──────────────────────────────────── + - name: Run security pre-scan on PR files + env: + REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ inputs.pr_number }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + echo "Scanning changed files in PR #$PR_NUMBER..." + FILES=$(gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename') + + SUSPICIOUS_COUNT=0 + for file in $FILES; do + case "$file" in + .github/workflows/*|.githooks/*|scripts/*) + echo "::warning::PR #$PR_NUMBER modifies critical CI/script file: $file" + SUSPICIOUS_COUNT=$((SUSPICIOUS_COUNT + 1)) + ;; + esac + done + + if [ "$SUSPICIOUS_COUNT" -gt 0 ]; then + echo "::notice::PR #$PR_NUMBER modifies $SUSPICIOUS_COUNT critical file(s). Review diff before merging." + else + echo "Content pre-scan passed cleanly: no critical CI/workflow files modified." + fi + # ── 1. Create trusted branch with the fork's commits ─────────────────── - name: Create trusted/pr-${{ inputs.pr_number }} branch env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 039e0d7..543c17b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable public changes to Nexus Unity are documented here. ## [Unreleased] +### Security +- Hardened external PR replay workflow (`approve-external-pr.yml`) with an in-workflow actor authorization gate and a PR content security pre-scan to prevent unauthorized execution and flag critical CI modifications on self-hosted runners (#144). + ### Changed - Consolidated duplicate internal Ollama-review and serialized-property write helpers. From af6611e1478cfd70655f0bf8042893741b2f5ddd Mon Sep 17 00:00:00 2001 From: Daliys Date: Thu, 23 Jul 2026 01:37:42 +0200 Subject: [PATCH 2/2] security: block trusted replay when critical CI files are touched The content pre-scan added in #144 only logged a warning, so a fork PR editing .github/workflows/*, .githooks/*, or scripts/* still got force- pushed into a trusted branch and executed on the self-hosted runner unattended. Make it a required checkpoint: fail the job unless the maintainer explicitly re-runs with acknowledge_critical_files: true after reviewing the diff. Also fix word-splitting on filenames in the scan loop. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/approve-external-pr.yml | 18 +++++++++++++++--- CHANGELOG.md | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/approve-external-pr.yml b/.github/workflows/approve-external-pr.yml index 5a6a252..47b522c 100644 --- a/.github/workflows/approve-external-pr.yml +++ b/.github/workflows/approve-external-pr.yml @@ -29,6 +29,11 @@ on: description: "External PR number to replay (e.g. 30)" required: true type: string + acknowledge_critical_files: + description: "Set true only after reviewing the diff, to proceed even though the PR touches .github/workflows, .githooks, or scripts" + required: false + type: boolean + default: false permissions: contents: write # push the trusted/pr-N branch @@ -113,6 +118,7 @@ jobs: env: REPOSITORY: ${{ github.repository }} PR_NUMBER: ${{ inputs.pr_number }} + ACKNOWLEDGE: ${{ inputs.acknowledge_critical_files }} GH_TOKEN: ${{ github.token }} run: | set -euo pipefail @@ -121,17 +127,23 @@ jobs: FILES=$(gh api "repos/$REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename') SUSPICIOUS_COUNT=0 - for file in $FILES; do + while IFS= read -r file; do + [ -z "$file" ] && continue case "$file" in .github/workflows/*|.githooks/*|scripts/*) echo "::warning::PR #$PR_NUMBER modifies critical CI/script file: $file" SUSPICIOUS_COUNT=$((SUSPICIOUS_COUNT + 1)) ;; esac - done + done <<< "$FILES" if [ "$SUSPICIOUS_COUNT" -gt 0 ]; then - echo "::notice::PR #$PR_NUMBER modifies $SUSPICIOUS_COUNT critical file(s). Review diff before merging." + echo "::notice::PR #$PR_NUMBER modifies $SUSPICIOUS_COUNT critical file(s)." + if [ "$ACKNOWLEDGE" != "true" ]; then + echo "::error::Refusing to replay PR #$PR_NUMBER: it modifies critical CI/script file(s) (see warnings above). Review the diff, then re-run this workflow with 'acknowledge_critical_files' set to true if it's safe to proceed." + exit 1 + fi + echo "Critical file changes acknowledged by @${{ github.actor }} — proceeding." else echo "Content pre-scan passed cleanly: no critical CI/workflow files modified." fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 543c17b..d0c0c9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable public changes to Nexus Unity are documented here. ### Security - Hardened external PR replay workflow (`approve-external-pr.yml`) with an in-workflow actor authorization gate and a PR content security pre-scan to prevent unauthorized execution and flag critical CI modifications on self-hosted runners (#144). +- The content pre-scan now blocks the replay when a PR touches `.github/workflows/*`, `.githooks/*`, or `scripts/*`, requiring the maintainer to re-run with `acknowledge_critical_files: true` after reviewing the diff, instead of only logging a warning (#144). ### Changed - Consolidated duplicate internal Ollama-review and serialized-property write helpers.