Centralized security scanning for every repository in the organization, powered by Semgrep (SAST) and Trivy (vulnerability + secret + misconfiguration scanning).
Scans trigger automatically when a PR is opened/updated or a new repo is created in the org. Results appear as:
- Status checks directly on the PR (pass/fail per scanner)
- A comment in the PR with a findings summary table
- Code scanning alerts in each repo's Security tab (via SARIF)
GitHub Org event │ (pull_request opened/sync'd, or repository created) ▼ Org Webhook ──► Cloudflare Worker (relay) │ │ repository_dispatch ▼ security-scan repo (this repo) │ ┌───────────┴───────────┐ ▼ ▼ Semgrep SAST Trivy scan │ │ └───────────┬───────────┘ ▼ Post to target PR: ✅/❌ Commit status checks 💬 PR comment with summary table 📊 SARIF → Security tab The relay is a Cloudflare Worker that validates and forwards org webhook events.
cd webhook-relay npm install -g wrangler wrangler login # Set secrets wrangler secret put GITHUB_WEBHOOK_SECRET # any random string — you'll use this in Step 2 wrangler secret put GITHUB_TOKEN # PAT with repo + security_events + read:org # Set your org name in wrangler.toml, then deploy wrangler deployThe deploy output will give you a URL like https://security-scan-relay.YOUR_ACCOUNT.workers.dev.
No Cloudflare account? You can use any small serverless platform (AWS Lambda + API Gateway, Railway, Render, etc.) — the
worker.jslogic is standard JavaScript. Or use smee.io for testing.
- Go to GitHub → Your Org → Settings → Webhooks → Add webhook
- Set:
- Payload URL: your Cloudflare Worker URL from Step 1
- Content type:
application/json - Secret: the same random string you used for
GITHUB_WEBHOOK_SECRET - Events: select individual events → check:
Pull requestsRepositories
- Save.
Go to this repo → Settings → Secrets and variables → Actions:
| Secret | Description |
|---|---|
ORG_SCAN_TOKEN | GitHub PAT with repo, security_events, and read:org scopes |
SEMGREP_APP_TOKEN | (Optional) Semgrep Cloud token for the managed dashboard |
| Variable | Description |
|---|---|
ORG_NAME | Your GitHub organization name (e.g. my-org) |
In each repo (or at the org level via a ruleset):
- Settings → Branches → Branch protection rules → main
- Enable Require status checks to pass before merging
- Add
security/semgrepandsecurity/trivyas required checks
This blocks merging PRs that have critical findings.
🔴 Security Scan Results — Action required | Scanner | Critical | High | Medium | Total | Status | |----------------------|:--------:|:----:|:------:|:-----:|--------------| | 🔴 Semgrep (SAST) | 2 | 4 | 7 | 13 | Issues found | | 🟢 Trivy (vulns+sec) | 0 | 0 | 0 | 0 | Clean ✅ | > View full findings in the Security tab. Scan run: #42 · Commit: `a1b2c3d` The comment is updated in place on subsequent pushes — no comment spam.
The workflow currently runs these Semgrep rulesets (edit org-security-scan.yml to change):
p/owasp-top-ten— OWASP Top 10p/secrets— Hardcoded credentialsp/javascript,p/python,p/golang— Language-specific rulesp/docker,p/terraform— Infrastructure rules
Edit the severity input in the Trivy job to adjust which findings are reported:
severity: CRITICAL,HIGH,MEDIUM # defaultSee configs/trivy/trivy.yaml and configs/trivy/trivy-secret.yaml.
Trigger from Actions → Org-Wide Security Scan → Run workflow. Enter owner/repo and optionally a SHA or PR number.
.github/workflows/ org-security-scan.yml # Main workflow (event-driven) reusable-semgrep.yml # Optional: call from individual repos reusable-trivy.yml # Optional: call from individual repos configs/ semgrep/semgrep.yml # Semgrep rule configuration trivy/ trivy.yaml # Trivy scan configuration trivy-secret.yaml # Custom secret detection rules webhook-relay/ worker.js # Cloudflare Worker relay wrangler.toml # Cloudflare deployment config