Skip to content

Commit 1cc7559

Browse files
authored
Harden CI/CD supply chain security across all workflows (#1328)
## Summary Addresses critical, high, and medium findings from a comprehensive CI/CD supply chain security audit. ### CRITICAL - **`claude-code-review.yml`**: Fixed privilege escalation risk — was checking out PR head branch ref (mutable) with `pull-requests: write` + `issues: write` + `id-token: write`. Now checks out by commit SHA. Moved all `github.event.*` expressions from inline `run:` steps to `env:` blocks. ### HIGH - **`dco-check.yml`**: Fixed script injection — `github.event.pull_request.head.repo.full_name`, `.base.ref`, `.base.sha`, `.head.sha` were injected directly into shell `run:` steps. An attacker could craft a fork repo name with shell metacharacters. All now passed via `env:` blocks. ### MEDIUM - **All 22 workflows**: Added explicit `permissions:` blocks (15 were missing, inheriting overly-broad repo defaults) - **`updateVersion.yml`**: Moved `workflow_dispatch` inputs from inline `${{ }}` to `env:` blocks - **`slt.yml`**: Token now passed via `env:` block instead of inline in `run:` command - **`releaseFreeze.yml`**: Moved PR number to `env:` block, added `permissions: contents: read` - **`checkNextChangelog.yml`**: Moved PR number to `env:` block - **Added `CODEOWNERS`**: Requires review for `.github/workflows/` changes - **Added `dependabot.yml`**: Automated monitoring for GitHub Actions and Maven dependency updates ### Permissions Summary | Permission | Workflows | |---|---| | `contents: read` | 18 workflows (tests, CI, checks) | | `contents: write` | 3 workflows (release, release-thin, updateVersion) | | `issues: write, pull-requests: write` | 1 workflow (closeStale) | | Scoped per-job | 2 workflows (claude, claude-code-review) | NO_CHANGELOG=true ## Test plan - [ ] Verify `dco-check` triggers correctly on a PR from a fork - [ ] Verify `claude-code-review` still functions with SHA-based checkout - [ ] Verify `releaseFreeze` check passes on PRs - [ ] Verify `checkNextChangelog` check passes on PRs - [ ] Verify `closeStale` can still label/close issues and PRs - [ ] Verify release workflows can still create GitHub releases (`contents: write`) This pull request was AI-assisted by Isaac. --------- Signed-off-by: Gopal Lal <[email protected]>
1 parent 4f934ed commit 1cc7559

22 files changed

Lines changed: 150 additions & 47 deletions

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Require review for changes to GitHub Actions workflows and CI configuration
2+
.github/workflows/ @databricks/eng-oss-sql-driver
3+
.github/CODEOWNERS @databricks/eng-oss-sql-driver

.github/dependabot.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: 2
2+
updates:
3+
# Keep GitHub Actions up to date
4+
- package-ecosystem: "github-actions"
5+
directory: "/"
6+
schedule:
7+
interval: "weekly"
8+
labels:
9+
- "dependencies"
10+
- "github-actions"
11+
12+
# Keep Maven dependencies up to date
13+
- package-ecosystem: "maven"
14+
directory: "/"
15+
schedule:
16+
interval: "weekly"
17+
labels:
18+
- "dependencies"
19+
- "maven"
20+
open-pull-requests-limit: 10

.github/workflows/bugCatcher.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
# Runs at 00:00 on every Monday
66
- cron: '0 0 * * 1'
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
build-and-test:
1013
name: Build and Run Integration Tests

.github/workflows/checkNextChangelog.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@ jobs:
2424
id: changed-files
2525
env:
2626
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27+
PR_NUMBER: ${{ github.event.pull_request.number }}
2728
run: |
2829
# Use the GitHub API to fetch changed files
29-
files=$(gh pr view ${{ github.event.pull_request.number }} --json files -q '.files[].path')
30-
30+
files=$(gh pr view "$PR_NUMBER" --json files -q '.files[].path')
31+
3132
# Sanitize to avoid code injection
3233
sanitized_files=$(echo "$files" | sed 's/[^a-zA-Z0-9._/-]/_/g')
33-
34+
3435
# Store the sanitized list of files in a temporary file to avoid env variable issues
3536
echo "$sanitized_files" > modified_files.txt
3637
echo "$sanitized_files"
@@ -39,13 +40,14 @@ jobs:
3940
id: pr-message
4041
env:
4142
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
PR_NUMBER: ${{ github.event.pull_request.number }}
4244
run: |
4345
# Use the GitHub API to fetch the PR message
44-
pr_message=$(gh pr view ${{ github.event.pull_request.number }} --json body -q '.body')
45-
46+
pr_message=$(gh pr view "$PR_NUMBER" --json body -q '.body')
47+
4648
# Sanitize the PR message to avoid code injection, keeping the equal sign
4749
sanitized_pr_message=$(echo "$pr_message" | sed 's/[^a-zA-Z0-9._/-=]/_/g')
48-
50+
4951
# Store the sanitized PR message
5052
echo "$sanitized_pr_message" > pr_message.txt
5153
echo "$sanitized_pr_message"

.github/workflows/claude-code-review.yml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,31 @@ jobs:
2323

2424
steps:
2525
- name: React to comment
26-
run: |
27-
gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
28-
-f content=eyes
2926
env:
3027
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
REPO: ${{ github.repository }}
29+
COMMENT_ID: ${{ github.event.comment.id }}
30+
run: |
31+
gh api "repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \
32+
-f content=eyes
3133
32-
- name: Get PR head ref
34+
- name: Get PR head SHA
3335
id: pr
34-
run: |
35-
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})
36-
echo "ref=$(echo "$PR_DATA" | jq -r .head.ref)" >> "$GITHUB_OUTPUT"
37-
echo "number=$(echo "$PR_DATA" | jq -r .number)" >> "$GITHUB_OUTPUT"
3836
env:
3937
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
REPO: ${{ github.repository }}
39+
PR_NUMBER: ${{ github.event.issue.number }}
40+
run: |
41+
PR_DATA=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}")
42+
echo "sha=$(echo "$PR_DATA" | jq -r .head.sha)" >> "$GITHUB_OUTPUT"
43+
echo "number=$(echo "$PR_DATA" | jq -r .number)" >> "$GITHUB_OUTPUT"
4044
4145
- name: Checkout repository
4246
uses: actions/checkout@v4
4347
with:
44-
ref: ${{ steps.pr.outputs.ref }}
48+
# Use the commit SHA instead of the branch ref to prevent
49+
# the branch from being moved between the API call and checkout
50+
ref: ${{ steps.pr.outputs.sha }}
4551
fetch-depth: 0
4652

4753
- name: Run Claude Code Review

.github/workflows/closeStale.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ on:
33
schedule:
44
# Run at 06:30 GMT everyday
55
- cron: "30 6 * * *"
6+
7+
permissions:
8+
issues: write
9+
pull-requests: write
10+
611
jobs:
712
stale:
813
runs-on:

.github/workflows/concurrencyExecutionTests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ on:
1414
required: false
1515
default: 'databricks/databricks-jdbc'
1616

17+
permissions:
18+
contents: read
19+
1720
jobs:
1821
concurrency-tests:
1922
name: Run Concurrency Execution Tests

.github/workflows/dco-check.yml

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
types: [opened, synchronize, reopened]
66
branches: [ main ]
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
dco-check:
1013
runs-on:
@@ -20,59 +23,65 @@ jobs:
2023
repository: ${{ github.event.pull_request.head.repo.full_name }}
2124

2225
- name: Add upstream remote (for forks)
26+
env:
27+
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
28+
BASE_REPO: ${{ github.repository }}
29+
BASE_REF: ${{ github.event.pull_request.base.ref }}
2330
run: |
2431
# Add the upstream repository as a remote if this is a fork
25-
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
32+
if [ "$HEAD_REPO" != "$BASE_REPO" ]; then
2633
echo "This is a fork, adding upstream remote"
27-
git remote add upstream https://github.com/${{ github.repository }}.git
28-
git fetch upstream ${{ github.event.pull_request.base.ref }}
34+
git remote add upstream "https://github.com/${BASE_REPO}.git"
35+
git fetch upstream "$BASE_REF"
2936
else
3037
echo "This is not a fork, using origin"
3138
fi
3239
3340
- name: Check DCO Sign-off
41+
env:
42+
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
43+
BASE_REPO: ${{ github.repository }}
44+
BASE_REF: ${{ github.event.pull_request.base.ref }}
45+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
46+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
3447
run: |
3548
#!/bin/bash
3649
set -e
37-
38-
# Get the list of commits in this PR
39-
BASE_SHA="${{ github.event.pull_request.base.sha }}"
40-
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
41-
50+
4251
echo "Checking commits from $BASE_SHA to $HEAD_SHA"
43-
52+
4453
# Verify that both commits exist
4554
if ! git cat-file -e "$BASE_SHA" 2>/dev/null; then
4655
echo "Error: Base commit $BASE_SHA not found"
4756
echo "Trying to fetch from upstream..."
48-
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
49-
git fetch upstream ${{ github.event.pull_request.base.ref }}
57+
if [ "$HEAD_REPO" != "$BASE_REPO" ]; then
58+
git fetch upstream "$BASE_REF"
5059
else
51-
git fetch origin ${{ github.event.pull_request.base.ref }}
60+
git fetch origin "$BASE_REF"
5261
fi
5362
fi
54-
63+
5564
if ! git cat-file -e "$HEAD_SHA" 2>/dev/null; then
5665
echo "Error: Head commit $HEAD_SHA not found"
5766
exit 1
5867
fi
59-
68+
6069
# Get commit messages for all commits in the PR
6170
COMMITS=$(git rev-list --no-merges "$BASE_SHA..$HEAD_SHA")
62-
71+
6372
if [ -z "$COMMITS" ]; then
6473
echo "No commits found in this PR"
6574
exit 0
6675
fi
67-
76+
6877
FAILED_COMMITS=()
69-
78+
7079
for commit in $COMMITS; do
7180
echo "Checking commit: $commit"
72-
81+
7382
# Get the commit message
7483
COMMIT_MSG=$(git log --format=%B -n 1 "$commit")
75-
84+
7685
# Check if the commit message contains "Signed-off-by:"
7786
if echo "$COMMIT_MSG" | grep -q "^Signed-off-by: "; then
7887
echo "✅ Commit $commit has DCO sign-off"
@@ -81,7 +90,7 @@ jobs:
8190
FAILED_COMMITS+=("$commit")
8291
fi
8392
done
84-
93+
8594
if [ ${#FAILED_COMMITS[@]} -ne 0 ]; then
8695
echo ""
8796
echo "❌ DCO Check Failed!"

.github/workflows/loggingTesting.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ on:
1111
required: false
1212
default: 'main'
1313

14+
permissions:
15+
contents: read
16+
1417
jobs:
1518
test-logging:
1619
strategy:

.github/workflows/prCheck.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ on:
88
types: [opened, synchronize, reopened]
99
branches: [ main ]
1010

11+
permissions:
12+
contents: read
13+
1114
jobs:
1215
formatting-check:
1316
strategy:

0 commit comments

Comments
 (0)