diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d830c16dfa2..9ef2ae461ba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,6 +10,7 @@ - [ ] My PR passes all unit tests on [`make test-unit`](https://docs.litellm.ai/docs/extras/contributing_code) - [ ] My PR's scope is as isolated as possible, it only solves 1 specific problem - [ ] I have requested a Greptile review by commenting `@greptileai` and received a **Confidence Score of at least 4/5** before requesting a maintainer review +- [ ] (LiteLLM Team Only) I have confirmed that the **Critical Entry Point Scan** (from `.github/workflows/security-gate.yml`) is passing and is configured as a required status check in the branch protection rules. ## Delays in PR merge? diff --git a/.github/workflows/security-gate.yml b/.github/workflows/security-gate.yml new file mode 100644 index 00000000000..e3c7deb12ce --- /dev/null +++ b/.github/workflows/security-gate.yml @@ -0,0 +1,75 @@ +name: 🛡️ PR Security Gate v2 + +# NOTE: This workflow MUST be configured as a 'Required Status Check' in the repository's +# branch protection rules for 'main' and 'master' branches to effectively block merges. +# Settings -> Branches -> Branch protection rules -> [Branch] -> Require status checks to pass -> 'Critical Entry Point Scan' + +on: + pull_request: + branches: [ main, master ] + types: [ opened, synchronize, reopened ] + +permissions: + contents: read + pull-requests: read + +jobs: + supply-chain-check: + name: Critical Entry Point Scan + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Detect Malicious .pth Files + uses: actions/github-script@v7.0.1 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pull_number = context.payload.pull_request.number; + + const suspects = []; + let totalFiles = 0; + + for await (const page of github.paginate.iterator(github.rest.pulls.listFiles, { + owner, + repo, + pull_number, + per_page: 100, + })) { + totalFiles += page.data.length; + for (const f of page.data) { + const status = (f.status || '').toLowerCase(); + const newName = (f.filename || '').toLowerCase(); + + if (status === 'removed') { + continue; // ignore deletions + } + + if (status === 'renamed') { + if (newName.endsWith('.pth')) { + suspects.push(f.filename); + } + continue; + } + + if (newName.endsWith('.pth')) { + suspects.push(f.filename); + } + } + } + + if (totalFiles >= 300) { + core.warning( + `PR has ${totalFiles} files. GitHub API limit for listFiles may have been reached (approx. 300-3000 depending on API version) — this security scan may be incomplete. Please manually verify if large numbers of files were added.` + ); + } + + if (suspects.length > 0) { + core.setFailed( + `SECURITY ALERT: .pth file(s) detected in PR diff:\n${suspects.join('\n')}\n` + + `REASON: .pth files allow arbitrary code execution at Python startup.\n` + + `If this is a legitimate test fixture, rename it to .pth.txt` + ); + } else { + core.info('No suspicious entry points found.'); + } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77bc15ff50b..441448f2288 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,7 @@ Here are the core requirements for any PR submitted to LiteLLM: - [ ] **Ensure your PR passes all checks**: - [ ] [Unit Tests](#running-unit-tests) - `make test-unit` - [ ] [Linting / Formatting](#running-linting-and-formatting-checks) - `make lint` + - [ ] **Security Gate** - Ensure the `Critical Entry Point Scan` (`security-gate.yml`) passes on your PR. #### UI PRs