Skip to content

Commit 4d76382

Browse files
authored
chore: harden GitHub Actions supply chain security (#1365)
## Summary A supply chain security audit of the dbt-databricks CI/CD pipeline identified **8 findings** across GitHub Actions workflows. This PR addresses the critical and medium severity issues: - **86% of GitHub Action references** used mutable tags (`@v4`, `@v5`) instead of immutable commit SHAs — an attacker who compromises an upstream action repo can silently change what code runs in CI, including jobs with access to Databricks secrets - **`pypa/hatch@install`** had zero version pinning (a branch ref, not even a tag) and ran in 6 jobs - **`conventional-commits-parser`** npm package was installed without any version constraint - **Dependabot config** was in the wrong directory (`.github/ISSUE_TEMPLATE/dependabot.yml`) and was completely inactive - **No lock file** — `uv.lock` was in `.gitignore`, making CI builds non-deterministic ## Changes ### 1. Pin all GitHub Actions to immutable commit SHAs - Replaced all 32 mutable tag references across `main.yml`, `integration.yml`, `ci-pr-linting.yml`, and `coverage.yml` with full 40-character commit SHAs - All SHAs verified via `gh api` to predate March 18, 2026 - `stale.yml` was already SHA-pinned — no change needed - Trailing comments preserve the original tag for readability (e.g., `actions/checkout@34e11487... # v4`) **SHA Reference:** | Action | SHA | Commit Date | |--------|-----|-------------| | `actions/checkout` | `34e11487...` | 2025-11-13 | | `actions/setup-python` | `a26af69b...` | 2025-04-24 | | `actions/setup-node` | `49933ea5...` | 2025-04-02 | | `actions/upload-artifact` | `ea165f8d...` | 2025-03-19 | | `astral-sh/setup-uv` | `38f3f104...` | 2024-11-30 | | `pypa/hatch` | `257e27e5...` | 2024-05-23 | | `py-cov-action/python-coverage-comment-action` | `7188638f...` | 2026-01-06 | ### 2. Pin conventional-commits-parser to v6.3.0 - npm versions are immutable (can't be republished with different content), so exact version pinning is sufficient - v6.3.0 published 2026-03-01 (before March 18 cutoff) - Note: Dependabot cannot auto-update this pin since it's an inline workflow install, not a `package.json` dependency — updates must be manual ### 3. Fix Dependabot config location and add github-actions ecosystem - Moved from `.github/ISSUE_TEMPLATE/dependabot.yml` (wrong — GitHub never reads this) to `.github/dependabot.yml` - Added `github-actions` ecosystem with weekly schedule so SHA-pinned actions get automatic update PRs - Kept `pip` ecosystem with daily schedule - No `npm` ecosystem — the npm dependency is an inline workflow install that Dependabot cannot track ### 4. Commit uv.lock and enforce frozen installs in CI - Removed `uv.lock` from `.gitignore` and committed the generated lock file (82 resolved packages) - Added `UV_FROZEN: "1"` environment variable to all 6 CI jobs (3 in `main.yml`, 3 in `integration.yml`) - When `UV_FROZEN=1` is set, uv refuses to install if the lock file doesn't match `pyproject.toml`, failing CI loudly instead of silently re-resolving - **Developer impact:** when changing dependencies in `pyproject.toml`, you must also run `uv lock` and commit the updated `uv.lock` ### 5. Enhance CODEOWNERS - Added `@jprakash-db` as a code owner - Added explicit `/.github/workflows/` rule to make CI security review governance explicit ## Files Modified | File | Change | |------|--------| | `.github/workflows/main.yml` | 14 action refs SHA-pinned, `UV_FROZEN: "1"` added to 3 jobs | | `.github/workflows/integration.yml` | 15 action refs SHA-pinned, `UV_FROZEN: "1"` added to 3 jobs | | `.github/workflows/ci-pr-linting.yml` | 2 action refs SHA-pinned, npm pinned to `@6.3.0` | | `.github/workflows/coverage.yml` | 1 action ref SHA-pinned | | `.github/ISSUE_TEMPLATE/dependabot.yml` | Deleted | | `.github/dependabot.yml` | Created (correct location) | | `.gitignore` | Removed `uv.lock` line | | `uv.lock` | Created (82 resolved packages) | | `.github/CODEOWNERS` | Added `@jprakash-db`, added workflow rule | ## Local Validation Results - **actionlint:** no new errors (only pre-existing warnings about `linux-ubuntu-latest` custom runner label) - **grep audit:** 0 unpinned action refs remaining - **SHA resolution:** all 7 SHAs verified via `gh api` - **All SHAs predate March 18, 2026** - **npm pin:** `conventional-commits-parser@6.3.0` installs and parses correctly - **Frozen mode:** env resolution, code-quality, and 719 unit tests all pass with `UV_FROZEN=1` - **Drift test:** frozen mode correctly fails when lock file is stale ## Test plan - [ ] CI workflows pass with SHA-pinned actions - [ ] `Check PR title format` workflow validates PR titles correctly with pinned npm package - [ ] Unit tests pass with `UV_FROZEN=1` and committed `uv.lock` - [ ] After merge: verify Dependabot activates (Security > Dependabot tab) for pip and github-actions ecosystems - [ ] After merge: verify correct reviewers are auto-requested on workflow file changes ## Audit Findings Addressed | # | Finding | Severity | Status | |---|---------|----------|--------| | 1 | `pypa/hatch@install` — no version constraint | HIGH | Fixed | | 2 | 19/22 GitHub Actions pinned to tags, not SHAs | MEDIUM | Fixed | | 3 | `npm install conventional-commits-parser` unpinned | MEDIUM | Fixed | | 4 | No `uv.lock` committed — non-deterministic CI | MEDIUM | Fixed | | 5 | Dependabot config in wrong directory | MEDIUM | Fixed | | 6 | 8/11 dev dependencies unpinned | LOW | Deferred (uv.lock covers this) | | 7 | Build verification tools unpinned | LOW | Deferred (uv.lock covers this) | | 8 | `contents: write` on unit test job | INFO | Acceptable — already scoped | --- JIRA: [PECOBLR-2368](https://databricks.atlassian.net/browse/PECOBLR-2368) This pull request was AI-assisted by Isaac. [PECOBLR-2368]: https://databricks.atlassian.net/browse/PECOBLR-2368?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent f7fb7eb commit 4d76382

9 files changed

Lines changed: 1943 additions & 43 deletions

File tree

.github/CODEOWNERS

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22
# the repo. Unless a later match takes precedence, these
33
# users will be requested for review when someone opens a
44
# pull request.
5-
* @sd-db @tejassp-db @benc-db
5+
* @sd-db @tejassp-db @benc-db @jprakash-db
6+
7+
# Explicit rule for CI/CD workflow changes
8+
/.github/workflows/ @sd-db @tejassp-db @benc-db @jprakash-db

.github/ISSUE_TEMPLATE/dependabot.yml

Lines changed: 0 additions & 8 deletions
This file was deleted.

.github/dependabot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 2
2+
updates:
3+
# Python dependencies
4+
- package-ecosystem: "pip"
5+
directory: "/"
6+
schedule:
7+
interval: "daily"
8+
rebase-strategy: "disabled"
9+
10+
# GitHub Actions — auto-update SHA pins
11+
- package-ecosystem: "github-actions"
12+
directory: "/"
13+
schedule:
14+
interval: "weekly"
15+
rebase-strategy: "disabled"

.github/workflows/ci-pr-linting.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ jobs:
1515
pr-title:
1616
runs-on: linux-ubuntu-latest
1717
steps:
18-
- uses: actions/checkout@v4
18+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
1919

2020
- name: Setup node
21-
uses: actions/setup-node@v4
21+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
2222
with:
2323
node-version: 20
2424
- name: Install conventional commit parser
2525
shell: bash
26-
run: npm install --global conventional-commits-parser
26+
run: npm install --global conventional-commits-parser@6.3.0
2727

2828
- name: Validate PR title
2929
id: pr-format

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
# DO NOT run actions/checkout here, for security reasons
2828
# For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
2929
- name: Post comment
30-
uses: py-cov-action/python-coverage-comment-action@v3
30+
uses: py-cov-action/python-coverage-comment-action@7188638f871f721a365d644f505d1ff3df20d683 # v3
3131
with:
3232
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3333
GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }}

.github/workflows/integration.yml

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ jobs:
5252
DBT_DATABRICKS_UC_INITIAL_CATALOG: peco
5353
DBT_DATABRICKS_LOCATION_ROOT: ${{ secrets.TEST_PECO_EXTERNAL_LOCATION }}test
5454
TEST_PECO_UC_CLUSTER_ID: ${{ secrets.TEST_PECO_UC_CLUSTER_ID }}
55+
UV_FROZEN: "1"
5556
steps:
5657
- name: Check out repository
57-
uses: actions/checkout@v4
58+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
5859
with:
5960
# For pull_request: checkout the PR head commit
6061
# For workflow_dispatch with pr_number: checkout that PR's head
@@ -66,7 +67,7 @@ jobs:
6667

6768
- name: Set up python
6869
id: setup-python
69-
uses: actions/setup-python@v5
70+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
7071
with:
7172
python-version: "3.10"
7273

@@ -75,18 +76,18 @@ jobs:
7576
shell: sh
7677

7778
- name: Install uv
78-
uses: astral-sh/setup-uv@v4
79+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
7980

8081
- name: Install Hatch
8182
id: install-dependencies
82-
uses: pypa/hatch@install
83+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # install
8384

8485
- name: Run UC Cluster Functional Tests
8586
run: DBT_TEST_USER=notnecessaryformosttests@example.com DBT_DATABRICKS_LOCATION_ROOT=$DBT_DATABRICKS_LOCATION_ROOT DBT_DATABRICKS_HOST_NAME=$DBT_DATABRICKS_HOST_NAME DBT_DATABRICKS_UC_CLUSTER_HTTP_PATH=$DBT_DATABRICKS_UC_CLUSTER_HTTP_PATH DBT_DATABRICKS_CLIENT_ID=$DBT_DATABRICKS_CLIENT_ID DBT_DATABRICKS_CLIENT_SECRET=$DBT_DATABRICKS_CLIENT_SECRET hatch -v run uc-cluster-e2e
8687

8788
- name: Upload UC Cluster Test Logs
8889
if: always()
89-
uses: actions/upload-artifact@v4
90+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
9091
with:
9192
name: uc-cluster-test-logs
9293
path: logs/
@@ -105,9 +106,10 @@ jobs:
105106
DBT_DATABRICKS_UC_INITIAL_CATALOG: peco
106107
DBT_DATABRICKS_LOCATION_ROOT: ${{ secrets.TEST_PECO_EXTERNAL_LOCATION }}test
107108
TEST_PECO_UC_CLUSTER_ID: ${{ secrets.TEST_PECO_UC_CLUSTER_ID }}
109+
UV_FROZEN: "1"
108110
steps:
109111
- name: Check out repository
110-
uses: actions/checkout@v4
112+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
111113
with:
112114
# For pull_request: checkout the PR head commit
113115
# For workflow_dispatch with pr_number: checkout that PR's head
@@ -119,7 +121,7 @@ jobs:
119121

120122
- name: Set up python
121123
id: setup-python
122-
uses: actions/setup-python@v5
124+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
123125
with:
124126
python-version: "3.10"
125127

@@ -128,18 +130,18 @@ jobs:
128130
shell: sh
129131

130132
- name: Install uv
131-
uses: astral-sh/setup-uv@v4
133+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
132134

133135
- name: Install Hatch
134136
id: install-dependencies
135-
uses: pypa/hatch@install
137+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # install
136138

137139
- name: Run Sql Endpoint Functional Tests
138140
run: DBT_TEST_USER=notnecessaryformosttests@example.com DBT_DATABRICKS_LOCATION_ROOT=$DBT_DATABRICKS_LOCATION_ROOT DBT_DATABRICKS_HOST_NAME=$DBT_DATABRICKS_HOST_NAME DBT_DATABRICKS_UC_CLUSTER_HTTP_PATH=$DBT_DATABRICKS_UC_CLUSTER_HTTP_PATH DBT_DATABRICKS_CLIENT_ID=$DBT_DATABRICKS_CLIENT_ID DBT_DATABRICKS_CLIENT_SECRET=$DBT_DATABRICKS_CLIENT_SECRET hatch -v run sqlw-e2e
139141

140142
- name: Upload SQL Endpoint Test Logs
141143
if: always()
142-
uses: actions/upload-artifact@v4
144+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
143145
with:
144146
name: sql-endpoint-test-logs
145147
path: logs/
@@ -156,9 +158,10 @@ jobs:
156158
DBT_DATABRICKS_CLIENT_SECRET: ${{ secrets.TEST_PECO_SP_SECRET }}
157159
TEST_PECO_CLUSTER_ID: ${{ secrets.TEST_PECO_CLUSTER_ID }}
158160
DBT_DATABRICKS_LOCATION_ROOT: ${{ secrets.TEST_PECO_EXTERNAL_LOCATION }}test
161+
UV_FROZEN: "1"
159162
steps:
160163
- name: Check out repository
161-
uses: actions/checkout@v4
164+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
162165
with:
163166
# For pull_request: checkout the PR head commit
164167
# For workflow_dispatch with pr_number: checkout that PR's head
@@ -170,7 +173,7 @@ jobs:
170173

171174
- name: Set up python
172175
id: setup-python
173-
uses: actions/setup-python@v5
176+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
174177
with:
175178
python-version: "3.10"
176179

@@ -179,18 +182,18 @@ jobs:
179182
shell: sh
180183

181184
- name: Install uv
182-
uses: astral-sh/setup-uv@v4
185+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
183186

184187
- name: Install Hatch
185188
id: install-dependencies
186-
uses: pypa/hatch@install
189+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # install
187190

188191
- name: Run Cluster Functional Tests
189192
run: DBT_TEST_USER=notnecessaryformosttests@example.com DBT_DATABRICKS_LOCATION_ROOT=$DBT_DATABRICKS_LOCATION_ROOT DBT_DATABRICKS_HOST_NAME=$DBT_DATABRICKS_HOST_NAME DBT_DATABRICKS_HTTP_PATH=$DBT_DATABRICKS_CLUSTER_HTTP_PATH DBT_DATABRICKS_CLIENT_ID=$DBT_DATABRICKS_CLIENT_ID DBT_DATABRICKS_CLIENT_SECRET=$DBT_DATABRICKS_CLIENT_SECRET hatch -v run cluster-e2e
190193

191194
- name: Upload Cluster Test Logs
192195
if: always()
193-
uses: actions/upload-artifact@v4
196+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
194197
with:
195198
name: cluster-test-logs
196199
path: logs/

.github/workflows/main.yml

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,26 @@ jobs:
4747
runs-on: linux-ubuntu-latest
4848
timeout-minutes: 10
4949

50+
env:
51+
UV_FROZEN: "1"
52+
5053
strategy:
5154
fail-fast: false
5255

5356
steps:
5457
- name: Check out the repository
55-
uses: actions/checkout@v4
58+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
5659

5760
- name: Set up Python
58-
uses: actions/setup-python@v5
61+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
5962
with:
6063
python-version: "3.10"
6164

6265
- name: Install uv
63-
uses: astral-sh/setup-uv@v4
66+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
6467

6568
- name: Install Hatch
66-
uses: pypa/hatch@install
69+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # install
6770

6871
- name: Run Code Quality
6972
run: hatch -v run code-quality
@@ -83,38 +86,41 @@ jobs:
8386
# comments (to avoid publishing multiple comments in the same PR)
8487
contents: write
8588

89+
env:
90+
UV_FROZEN: "1"
91+
8692
strategy:
8793
fail-fast: false
8894
matrix:
8995
python-version: ["3.10", "3.11", "3.12", "3.13"]
9096

9197
steps:
9298
- name: Check out the repository
93-
uses: actions/checkout@v4
99+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
94100

95101
- name: Set up Python ${{ matrix.python-version }}
96-
uses: actions/setup-python@v5
102+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
97103
with:
98104
python-version: ${{ matrix.python-version }}
99105

100106
- name: Install uv
101-
uses: astral-sh/setup-uv@v4
107+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
102108

103109
- name: Install Hatch
104-
uses: pypa/hatch@install
110+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # install
105111

106112
- name: Run Unit Tests and Generate Coverage
107113
run: hatch run -v +py=${{ matrix.python-version }} test:unit-with-cov
108114

109115
# Only run coverage comment once (not for all python versions)
110116
- name: Coverage Comment
111117
if: matrix.python-version == '3.12' && github.event_name == 'pull_request'
112-
uses: py-cov-action/python-coverage-comment-action@v3
118+
uses: py-cov-action/python-coverage-comment-action@7188638f871f721a365d644f505d1ff3df20d683 # v3
113119
with:
114120
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
115121

116122
- name: Store Pull Request comment to be posted
117-
uses: actions/upload-artifact@v4
123+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
118124
if: steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true'
119125
with:
120126
name: python-coverage-comment-action
@@ -124,20 +130,23 @@ jobs:
124130
name: Build and Verify Packages
125131
runs-on: linux-ubuntu-latest
126132

133+
env:
134+
UV_FROZEN: "1"
135+
127136
steps:
128137
- name: Check out the repository
129-
uses: actions/checkout@v4
138+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
130139

131140
- name: Set up Python
132-
uses: actions/setup-python@v5
141+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
133142
with:
134143
python-version: "3.10"
135144

136145
- name: Install uv
137-
uses: astral-sh/setup-uv@v4
146+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
138147

139148
- name: Install Hatch
140-
uses: pypa/hatch@install
149+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc # install
141150

142151
- name: Build distributions
143152
run: hatch -v build

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ logs/
2424
CLAUDE.md
2525
.claude/
2626
.cursor
27-
uv.lock
27+
2828
docs/plans/

0 commit comments

Comments
 (0)