Skip to content

Commit a1996f2

Browse files
committed
Audit the locked dependencies on a schedule
The gitpython advisories published 2026-09-09, but the audit that catches them only runs on pushes and pull requests touching a Python path, so the first red run was 2026-09-17 -- eight days of shipping a known-vulnerable pin with every check green. pip-audit compares the lockfile against databases that publish continuously, so its result is a function of when it runs, not of the commit; it needs a clock, not a trigger. Move it to its own workflow with a daily schedule, keeping the pull request trigger for dependency changes. A failing scheduled run has no PR to turn red, so it opens a tracking issue instead, and closes it when the audit comes back clean. Unit Tests now reports only on the test suite. Mixing the two is why this surfaced as "Unit Tests failed" while all 717 tests passed.
1 parent 9538662 commit a1996f2

3 files changed

Lines changed: 185 additions & 4 deletions

File tree

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
name: Dependency Audit
2+
3+
env:
4+
PYTHON_VERSION: "3.12"
5+
6+
# This check is not a function of the code alone. pip-audit compares the locked
7+
# dependency set against advisory databases that publish continuously, so a
8+
# green run only describes the moment it ran -- the same commit can fail
9+
# tomorrow with nothing changed. That is why this runs on a schedule and not
10+
# only on pull requests: between 2026-09-09 (three GitPython advisories
11+
# published) and 2026-09-17 (the next push that happened to touch a path the
12+
# test workflow watches) the repository was shipping a known-vulnerable pin
13+
# with every check green.
14+
on:
15+
schedule:
16+
- cron: "17 6 * * *"
17+
pull_request:
18+
paths:
19+
- "pyproject.toml"
20+
- "uv.lock"
21+
- ".github/workflows/dependency-audit.yml"
22+
push:
23+
branches: [main]
24+
paths:
25+
- "pyproject.toml"
26+
- "uv.lock"
27+
- ".github/workflows/dependency-audit.yml"
28+
workflow_dispatch:
29+
30+
permissions:
31+
contents: read
32+
33+
concurrency:
34+
group: dependency-audit-${{ github.event.pull_request.number || github.ref }}
35+
cancel-in-progress: true
36+
37+
jobs:
38+
dependency-audit:
39+
runs-on: ubuntu-latest
40+
timeout-minutes: 10
41+
permissions:
42+
contents: read
43+
issues: write
44+
steps:
45+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
46+
with:
47+
fetch-depth: 1
48+
persist-credentials: false
49+
50+
- name: 🐍 setup python
51+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
52+
with:
53+
python-version: ${{ env.PYTHON_VERSION }}
54+
55+
- name: 🛠️ install uv
56+
run: |
57+
python -m pip install --upgrade pip
58+
pip install uv
59+
60+
- name: 🛡️ pip-audit (known CVEs in the locked deps)
61+
id: audit
62+
run: |
63+
uv export --no-hashes --no-emit-project --format requirements-txt > /tmp/req-audit.txt
64+
65+
set +e
66+
uvx pip-audit --strict --progress-spinner off --disable-pip --no-deps \
67+
-r /tmp/req-audit.txt 2>&1 | tee /tmp/audit.log
68+
status=${PIPESTATUS[0]}
69+
set -e
70+
71+
# Written before the exit so the issue step below can quote the table.
72+
{
73+
echo 'report<<AUDIT_REPORT_EOF'
74+
cat /tmp/audit.log
75+
echo 'AUDIT_REPORT_EOF'
76+
} >> "$GITHUB_OUTPUT"
77+
78+
exit "$status"
79+
80+
# A scheduled run has no pull request to turn red, so a failure here would
81+
# otherwise be visible only to someone reading the Actions tab. File it.
82+
- name: 📮 open or update the tracking issue
83+
if: always() && steps.audit.outcome == 'failure' && github.event_name == 'schedule'
84+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
85+
env:
86+
AUDIT_REPORT: ${{ steps.audit.outputs.report }}
87+
with:
88+
script: |
89+
const marker = '<!-- dependency-audit-tracking-issue -->';
90+
const title = 'Dependency audit: known vulnerabilities in the locked dependencies';
91+
const runUrl =
92+
`${context.serverUrl}/${context.repo.owner}/${context.repo.repo}` +
93+
`/actions/runs/${context.runId}`;
94+
const body = [
95+
marker,
96+
'`pip-audit` found known vulnerabilities in the locked dependency set.',
97+
'',
98+
'Bump the affected pins in `pyproject.toml`, run `uv lock`, and open a PR.',
99+
'',
100+
'```',
101+
process.env.AUDIT_REPORT.trim(),
102+
'```',
103+
'',
104+
`Run: ${runUrl}`,
105+
`Last checked: ${new Date().toISOString()}`,
106+
].join('\n');
107+
108+
const existing = await github.paginate(github.rest.issues.listForRepo, {
109+
owner: context.repo.owner,
110+
repo: context.repo.repo,
111+
state: 'open',
112+
per_page: 100,
113+
});
114+
const tracking = existing.find(
115+
(issue) => !issue.pull_request && issue.body && issue.body.includes(marker),
116+
);
117+
118+
if (tracking) {
119+
await github.rest.issues.update({
120+
owner: context.repo.owner,
121+
repo: context.repo.repo,
122+
issue_number: tracking.number,
123+
body,
124+
});
125+
core.notice(`Updated tracking issue #${tracking.number}`);
126+
} else {
127+
const created = await github.rest.issues.create({
128+
owner: context.repo.owner,
129+
repo: context.repo.repo,
130+
title,
131+
body,
132+
labels: ['dependencies'],
133+
});
134+
core.notice(`Opened tracking issue #${created.data.number}`);
135+
}
136+
137+
# Close the loop, so a stale issue does not outlive the problem.
138+
- name: ✅ close the tracking issue once the audit is clean
139+
if: always() && steps.audit.outcome == 'success' && github.event_name == 'schedule'
140+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
141+
with:
142+
script: |
143+
const marker = '<!-- dependency-audit-tracking-issue -->';
144+
const existing = await github.paginate(github.rest.issues.listForRepo, {
145+
owner: context.repo.owner,
146+
repo: context.repo.repo,
147+
state: 'open',
148+
per_page: 100,
149+
});
150+
const tracking = existing.find(
151+
(issue) => !issue.pull_request && issue.body && issue.body.includes(marker),
152+
);
153+
if (!tracking) {
154+
return;
155+
}
156+
await github.rest.issues.createComment({
157+
owner: context.repo.owner,
158+
repo: context.repo.repo,
159+
issue_number: tracking.number,
160+
body: 'The scheduled audit is clean again. Closing.',
161+
});
162+
await github.rest.issues.update({
163+
owner: context.repo.owner,
164+
repo: context.repo.repo,
165+
issue_number: tracking.number,
166+
state: 'closed',
167+
});

.github/workflows/python-tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ jobs:
6262
from socketsecurity.config import CliConfig
6363
print('import smoke OK')
6464
"
65-
- name: 🛡️ pip-audit (known CVEs in the locked deps)
66-
run: |
67-
uv export --no-hashes --no-emit-project --format requirements-txt > /tmp/req-audit.txt
68-
uvx pip-audit --strict --progress-spinner off --disable-pip --no-deps -r /tmp/req-audit.txt
65+
# pip-audit used to run here. It moved to dependency-audit.yml, because
66+
# its result depends on when it runs rather than on the commit, and
67+
# mixing the two meant a third-party advisory publication reported itself
68+
# as "Unit Tests failed" while every test passed.
6969

7070
ruff:
7171
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616
uv updater had been failing on it with `dependency_file_content_not_changed`
1717
while updating every other dependency in the same run.
1818

19+
### Changed: audit the locked dependencies on a schedule
20+
21+
- Moved `pip-audit` out of the Unit Tests workflow and into a new Dependency
22+
Audit workflow that runs daily, on pull requests that touch `pyproject.toml`
23+
or `uv.lock`, and on demand. The audit compares the locked dependencies
24+
against advisory databases that publish continuously, so a green result is
25+
only true for the moment it ran; running it solely on pushes meant a newly
26+
published advisory went unreported until someone happened to touch a Python
27+
file. A failing scheduled run now opens a tracking issue, and closes it once
28+
the audit is clean again.
29+
- Unit Tests now reports only on the test suite. Previously an advisory
30+
published against an unchanged pin surfaced as "Unit Tests failed" while
31+
every test passed.
32+
1933
## 2.9.4
2034

2135
### Changed: bump pinned @coana-tech/cli to 15.10.46

0 commit comments

Comments
 (0)