Skip to content

Commit b68e1a7

Browse files
Merge pull request #112 from upstox/add-secure
Add secure.yml and CODEOWNER to .github/workflows to run secure scans
2 parents 5682a31 + b7b5b2e commit b68e1a7

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

.github/workflows/secure.yml

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
on:
2+
workflow_dispatch: {}
3+
pull_request:
4+
branches:
5+
- main
6+
types: [opened, synchronize, reopened, labeled]
7+
push:
8+
branches:
9+
- main
10+
paths:
11+
- .github/workflows/secure.yml
12+
schedule:
13+
- cron: '12 01 * * *'
14+
15+
name: Security Check Scan
16+
17+
permissions:
18+
contents: read
19+
pull-requests: write
20+
21+
jobs:
22+
guard_whitelist_expiry:
23+
if: ${{ github.event_name == 'pull_request' }}
24+
name: checking eta
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Download complete whitelist
28+
run: |
29+
curl -s -X POST "${{ secrets.WHITELIST_CALLBACK_URL }}" \
30+
-H "Content-Type: application/json" \
31+
-H "X-API-KEY: ${{ secrets.sg_wh_api_key }}" \
32+
-d '{"pr_url":""}' \
33+
-o all.json
34+
35+
- name: Check for expired entries
36+
run: |
37+
TODAY=$(date +%F)
38+
REPO_FULL="https://github.com/${GITHUB_REPOSITORY}"
39+
EXPIRED=$(jq -r --arg t "$TODAY" --arg repo "$REPO_FULL" \
40+
'(.exemptions // []) | map(select(.eta <= $t and (.pr_url | startswith($repo)))) | length' all.json)
41+
42+
if [ "$EXPIRED" -gt 0 ]; then
43+
echo "::error ::🔒 $EXPIRED whitelist exemption(s) for this repository (${GITHUB_REPOSITORY}) are past their ETA."
44+
echo "❌ Please remove or update the expired entries from the whitelist file before merging this PR."
45+
exit 1
46+
fi
47+
48+
sec_checks:
49+
if: ${{ github.event_name == 'pull_request' }}
50+
name: Run required verification
51+
needs: guard_whitelist_expiry
52+
runs-on: ubuntu-latest
53+
steps:
54+
- name: Check whitelist
55+
id: check_whitelist
56+
run: |
57+
curl -s -X POST "${{ secrets.WHITELIST_CALLBACK_URL }}" \
58+
-H "Content-Type: application/json" \
59+
-H "X-API-KEY: ${{ secrets.sg_wh_api_key }}" \
60+
-d '{"pr_url":"'"${{ github.event.pull_request.html_url }}"'"}' \
61+
-o response.json
62+
63+
echo "=== Raw response ==="
64+
cat response.json || echo "response.json is empty"
65+
echo "===================="
66+
67+
PR_URL="${{ github.event.pull_request.html_url }}"
68+
PR_CLEAN=$(echo "$PR_URL" | xargs)
69+
70+
if jq -e --arg pr "$PR_CLEAN" '.exemptions[]? | select(.pr_url == $pr)' response.json > /dev/null 2>&1; then
71+
echo "whitelisted=true" >> $GITHUB_OUTPUT
72+
echo "✅ PR is whitelisted. Skipping scan."
73+
exit 0
74+
else
75+
echo "whitelisted=false" >> $GITHUB_OUTPUT
76+
echo "🔍 PR not whitelisted. Proceeding to scan."
77+
fi
78+
79+
- name: Checkout code
80+
uses: actions/checkout@v3
81+
with:
82+
fetch-depth: 2
83+
84+
- name: Cache pip dependencies
85+
uses: actions/cache@v3
86+
with:
87+
path: ~/.cache/pip
88+
key: ${{ runner.os }}-pip-semgrep
89+
90+
- name: Set up Python
91+
uses: actions/setup-python@v4
92+
with:
93+
python-version: '3.x'
94+
95+
- name: Install security check
96+
run: pip install semgrep
97+
98+
- name: Fix Git and fetch base branch
99+
run: |
100+
git config --global --add safe.directory $GITHUB_WORKSPACE
101+
git fetch origin ${{ github.base_ref }}
102+
103+
- name: Run diff check
104+
if: ${{ steps.check_whitelist.outputs.whitelisted == 'false' }}
105+
run: |
106+
echo "🚀 Running diff scan against origin/${{ github.base_ref }}"
107+
semgrep scan \
108+
--config p/gitleaks \
109+
--config p/secrets \
110+
--baseline-commit origin/${{ github.base_ref }} \
111+
--error
112+
113+
- name: Save JSON results
114+
if: ${{ steps.check_whitelist.outputs.whitelisted == 'false' }}
115+
run: |
116+
semgrep scan \
117+
--config p/gitleaks \
118+
--config p/secrets \
119+
--baseline-commit origin/${{ github.base_ref }} \
120+
--json > semgrep-results.json
121+
122+
- name: Dump results to logs
123+
if: ${{ steps.check_whitelist.outputs.whitelisted == 'false' }}
124+
run: cat semgrep-results.json
125+
126+
notify_blocked:
127+
needs: sec_checks
128+
if: ${{ always() && github.event_name == 'pull_request' && needs.sec_checks.result == 'failure' }}
129+
name: Notify if blocked
130+
runs-on: ubuntu-latest
131+
132+
steps:
133+
- name: Export Slack webhook (no-op)
134+
env:
135+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
136+
run: echo "webhook ready"
137+
138+
- name: Send Slack alert
139+
env:
140+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
141+
run: |
142+
PR_NUMBER=${{ github.event.pull_request.number }}
143+
PR_URL=${{ github.event.pull_request.html_url }}
144+
REPO=${{ github.repository }}
145+
ACTOR=${{ github.actor }}
146+
147+
PAYLOAD=$(printf '{
148+
"text": ":rotating_light: *Security check failed* <%s|PR #%s> in *%s* (triggered by *%s*).\nIf this is a false positive, run this command:\n\n`/github-whitelist-pr %s reason:\\"<reason>\\" eta:\\"YYYY-MM-DD\\" bug:\\"SEC-1234\\" tribe:\\"<t-tribe-name>\\"`"
149+
}' "$PR_URL" "$PR_NUMBER" "$REPO" "$ACTOR" "$PR_URL")
150+
151+
curl -X POST -H 'Content-Type: application/json' \
152+
--data "$PAYLOAD" \
153+
"$SLACK_WEBHOOK_URL"
154+
155+
approve:
156+
if: ${{ github.event_name == 'pull_request' && needs.sec_checks.result == 'success' }}
157+
needs: sec_checks
158+
name: Auto-approve if security check passes
159+
runs-on: ubuntu-latest
160+
steps:
161+
- name: Checkout code
162+
uses: actions/checkout@v3
163+
164+
- name: Approve PR
165+
run: gh pr review --approve "${{ github.event.pull_request.html_url }}"
166+
env:
167+
GITHUB_TOKEN: ${{ secrets.PAT_SECURITYREVIEWUSER }}

0 commit comments

Comments
 (0)