@biffo/cli 0.246.2 → 0.246.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_skeletons/plugin-template/scripts/js-dependency-audit.sh +94 -11
- package/_skeletons/plugin-template/scripts/py-dependency-audit.sh +97 -22
- package/_skeletons/sibling-template/scripts/js-dependency-audit.sh +94 -11
- package/_skeletons/sibling-template/scripts/py-dependency-audit.sh +97 -22
- package/package.json +1 -1
|
@@ -30,6 +30,23 @@
|
|
|
30
30
|
# the gate was green because it was not looking, and "we checked and it is
|
|
31
31
|
# clean" read identically to "we never checked".
|
|
32
32
|
#
|
|
33
|
+
# ## Discovery, not a fixed path list (#1270)
|
|
34
|
+
#
|
|
35
|
+
# A fixed sweep of "workspace root + `_skeletons/**`" is itself the same shape
|
|
36
|
+
# of gap, one level further down: it reports clean on any pnpm-lock.yaml
|
|
37
|
+
# outside that pair, and an INSTANCE grows trees this repo never has —
|
|
38
|
+
# vendored plugin frontends under `services/*/web/`, a second app under
|
|
39
|
+
# `web-admin/`, or any other nested pnpm project. Found live:
|
|
40
|
+
# `services/ideation/web/pnpm-lock.yaml` sat on two advisory-range packages
|
|
41
|
+
# (one high) while this exact gate stayed green, because it never looked
|
|
42
|
+
# there. Ten lockfiles in that repo; a fixed sweep covered nine.
|
|
43
|
+
#
|
|
44
|
+
# So every pnpm-lock.yaml under the repo is DISCOVERED by walking from the git
|
|
45
|
+
# root, rather than assumed from a hardcoded list — see "Discover the trees"
|
|
46
|
+
# below. Nothing here still means the estate's layouts differ; discovery
|
|
47
|
+
# covers a repo the day it grows a new one, instead of the day someone
|
|
48
|
+
# remembers to add a path.
|
|
49
|
+
#
|
|
33
50
|
# ## This file is distributed verbatim (#743)
|
|
34
51
|
#
|
|
35
52
|
# Both skeletons and every existing sibling/plugin repo hold a BYTE-IDENTICAL
|
|
@@ -39,10 +56,9 @@
|
|
|
39
56
|
# every satellite was born with the original defect and reddened its required JS
|
|
40
57
|
# check on any registry hiccup.
|
|
41
58
|
#
|
|
42
|
-
# It therefore takes its
|
|
43
|
-
# job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
44
|
-
# root workspace here, `apps/frontend` in a sibling
|
|
45
|
-
# sweep below simply finds nothing outside this repo. Keep it layout-agnostic,
|
|
59
|
+
# It therefore takes its WORKSPACE tree from the CURRENT WORKING DIRECTORY —
|
|
60
|
+
# the CI job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
61
|
+
# the root workspace here, `apps/frontend` in a sibling. Keep it layout-agnostic,
|
|
46
62
|
# or the copies stop being interchangeable and `shared-sync.sh --check` starts
|
|
47
63
|
# reporting drift that is really divergence.
|
|
48
64
|
#
|
|
@@ -112,14 +128,80 @@ audit_dir() {
|
|
|
112
128
|
return 0
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
#
|
|
116
|
-
|
|
131
|
+
# ## Discover the trees (#1270)
|
|
132
|
+
#
|
|
133
|
+
# Walk from the git root — not the CWD this script happens to run from — so a
|
|
134
|
+
# vendored tree anywhere in the repo is found regardless of where the CI job's
|
|
135
|
+
# `working-directory` points. Excludes `node_modules` (installed, not source),
|
|
136
|
+
# `.git`, and `.worktrees` (other agents' in-progress checkouts, git-ignored
|
|
137
|
+
# and not part of this run).
|
|
138
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
139
|
+
if [ -z "$REPO_ROOT" ]; then
|
|
140
|
+
echo "::error::js-dependency-audit: not inside a git repository ('git rev-parse --show-toplevel' failed). Discovery cannot walk a tree it cannot find." >&2
|
|
141
|
+
exit 2
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
WORKSPACE_ABS=$(pwd -P)
|
|
145
|
+
|
|
146
|
+
# shellcheck disable=SC2016
|
|
147
|
+
ALL_LOCKS=$(find "$REPO_ROOT" \
|
|
148
|
+
\( -name node_modules -o -name .git -o -name .worktrees \) -prune -o \
|
|
149
|
+
-type f -name pnpm-lock.yaml -print 2>/dev/null | sort)
|
|
150
|
+
|
|
151
|
+
# Fail CLOSED, not open, on an empty discovery (#1270). Zero trees — including
|
|
152
|
+
# no lockfile at the workspace root itself — means discovery is broken (a
|
|
153
|
+
# wrong root, an over-eager prune, a repo layout this script has never seen),
|
|
154
|
+
# not that the repo has nothing to audit. Every repo this script is wired into
|
|
155
|
+
# ships at least one pnpm-lock.yaml, so an empty result here is a configuration
|
|
156
|
+
# error, not a clean pass — the same posture as `verify.sh`'s `ci_has()`
|
|
157
|
+
# (#1218) and `ci-wiring-audit.sh`'s empty-map check: "This audit checked
|
|
158
|
+
# nothing. That is a configuration error, not a pass." Exit 2 (not 1), the
|
|
159
|
+
# estate's "cannot tell" code, distinct from "found a vulnerability" (1) and
|
|
160
|
+
# "clean" (0) — CI treats any non-zero as a failed step either way.
|
|
161
|
+
if [ -z "$ALL_LOCKS" ]; then
|
|
162
|
+
printf '::error::js-dependency-audit: discovered ZERO pnpm-lock.yaml trees under %s.\n' "$REPO_ROOT" >&2
|
|
163
|
+
printf 'This audit checked nothing. That is a configuration error, not a pass — see #1270.\n' >&2
|
|
164
|
+
exit 2
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# Print what was scanned BEFORE auditing, so the list survives even if a later
|
|
168
|
+
# tree crashes the run — a bare green must be a falsifiable claim, not
|
|
169
|
+
# something a reader has to infer or re-enumerate by hand.
|
|
170
|
+
tree_count=$(printf '%s\n' "$ALL_LOCKS" | wc -l | tr -d ' ')
|
|
171
|
+
printf 'js-dependency-audit: discovered %s pnpm-lock.yaml tree(s):\n' "$tree_count"
|
|
172
|
+
for lock in $ALL_LOCKS; do
|
|
173
|
+
dir=$(dirname "$lock")
|
|
174
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
175
|
+
case "$dir_abs" in
|
|
176
|
+
"$REPO_ROOT") rel=. ;;
|
|
177
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
178
|
+
*) rel="$dir_abs" ;;
|
|
179
|
+
esac
|
|
180
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
181
|
+
printf ' - %s (workspace)\n' "$rel"
|
|
182
|
+
else
|
|
183
|
+
printf ' - %s\n' "$rel"
|
|
184
|
+
fi
|
|
185
|
+
done
|
|
117
186
|
|
|
118
|
-
#
|
|
119
|
-
# so
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
187
|
+
# Audit each discovered tree. The workspace's own lockfile is audited WITHOUT
|
|
188
|
+
# --ignore-workspace, so pnpm resolves it normally; every other discovered
|
|
189
|
+
# lockfile is a separate, vendored project and needs the flag, or pnpm walks
|
|
190
|
+
# up, finds the workspace, and silently audits THAT instead — reporting clean
|
|
191
|
+
# for a tree it never looked at, which is this exact defect one level down.
|
|
192
|
+
for lock in $ALL_LOCKS; do
|
|
193
|
+
dir=$(dirname "$lock")
|
|
194
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
195
|
+
case "$dir_abs" in
|
|
196
|
+
"$REPO_ROOT") rel=. ;;
|
|
197
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
198
|
+
*) rel="$dir_abs" ;;
|
|
199
|
+
esac
|
|
200
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
201
|
+
audit_dir "$dir" "pnpm audit (workspace: ${rel})" "" || failed=1
|
|
202
|
+
else
|
|
203
|
+
audit_dir "$dir" "pnpm audit (${rel})" "--ignore-workspace" || failed=1
|
|
204
|
+
fi
|
|
123
205
|
done
|
|
124
206
|
|
|
125
207
|
if [ "$failed" -ne 0 ]; then
|
|
@@ -130,4 +212,5 @@ if [ "$inconclusive" -ne 0 ]; then
|
|
|
130
212
|
echo "${inconclusive} tree(s) could not be audited; see warnings above."
|
|
131
213
|
fi
|
|
132
214
|
|
|
215
|
+
echo "js-dependency-audit: audited ${tree_count} tree(s), 0 blocking findings."
|
|
133
216
|
exit 0
|
|
@@ -30,11 +30,23 @@
|
|
|
30
30
|
# and it is clean" read identically to "we never checked".
|
|
31
31
|
#
|
|
32
32
|
# Unlike `pnpm audit --ignore-workspace`, pip-audit cannot simply be run inside
|
|
33
|
-
# a
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
33
|
+
# a nested directory: it is not an installed environment, and pip-audit's
|
|
34
|
+
# default mode audits an environment. So we export `--frozen` requirements from
|
|
35
|
+
# the nested lockfile (a read of the committed lock, no re-resolution) and
|
|
36
|
+
# audit those with `-r`, from the workspace, which is where pip-audit is
|
|
37
|
+
# actually installed.
|
|
38
|
+
#
|
|
39
|
+
# ## Discovery, not a fixed path list (#1270)
|
|
40
|
+
#
|
|
41
|
+
# A fixed sweep of "workspace + `_skeletons/**`" is the same shape of gap one
|
|
42
|
+
# level further down: it reports clean on any uv.lock outside that pair, and an
|
|
43
|
+
# INSTANCE grows trees this repo never has — vendored plugin services such as
|
|
44
|
+
# `services/idea-scout/uv.lock`. Found live: that lockfile and
|
|
45
|
+
# `services/ideation/uv.lock` both sat on `cryptography@49.0.0` (CVE-2026-69247)
|
|
46
|
+
# while this gate stayed green, because it never looked there.
|
|
47
|
+
#
|
|
48
|
+
# So every uv.lock under the repo is DISCOVERED by walking from the git root,
|
|
49
|
+
# rather than assumed from a hardcoded list — see "Discover the trees" below.
|
|
38
50
|
#
|
|
39
51
|
# ## This file is distributed verbatim (#743)
|
|
40
52
|
#
|
|
@@ -44,11 +56,10 @@
|
|
|
44
56
|
# shipped a raw `uv run pip-audit` — so every satellite was born with the
|
|
45
57
|
# original defect and reddened a required check on any PyPI/OSV hiccup.
|
|
46
58
|
#
|
|
47
|
-
# It therefore takes its
|
|
48
|
-
# job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
49
|
-
# root project here and in a plugin repo, `services/api` in a sibling
|
|
50
|
-
#
|
|
51
|
-
# layout-agnostic, or the copies stop being interchangeable and
|
|
59
|
+
# It therefore takes its WORKSPACE tree from the CURRENT WORKING DIRECTORY —
|
|
60
|
+
# the CI job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
61
|
+
# the root project here and in a plugin repo, `services/api` in a sibling. Keep
|
|
62
|
+
# it layout-agnostic, or the copies stop being interchangeable and
|
|
52
63
|
# `shared-sync.sh --check` starts reporting drift that is really divergence.
|
|
53
64
|
#
|
|
54
65
|
# POSIX sh (the CI step runs `sh scripts/...`, i.e. dash) — no `pipefail`.
|
|
@@ -108,26 +119,89 @@ audit_deps() {
|
|
|
108
119
|
return 0
|
|
109
120
|
}
|
|
110
121
|
|
|
111
|
-
#
|
|
112
|
-
|
|
122
|
+
# ## Discover the trees (#1270)
|
|
123
|
+
#
|
|
124
|
+
# Walk from the git root — not the CWD this script happens to run from — so a
|
|
125
|
+
# vendored tree anywhere in the repo is found regardless of where the CI job's
|
|
126
|
+
# `working-directory` points. Excludes `node_modules`, `.git`, `.worktrees`
|
|
127
|
+
# (other agents' in-progress checkouts) and `.venv` (an installed environment,
|
|
128
|
+
# not a lockfile source).
|
|
129
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
130
|
+
if [ -z "$REPO_ROOT" ]; then
|
|
131
|
+
echo "::error::py-dependency-audit: not inside a git repository ('git rev-parse --show-toplevel' failed). Discovery cannot walk a tree it cannot find." >&2
|
|
132
|
+
exit 2
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
WORKSPACE_ABS=$(pwd -P)
|
|
136
|
+
|
|
137
|
+
# shellcheck disable=SC2016
|
|
138
|
+
ALL_LOCKS=$(find "$REPO_ROOT" \
|
|
139
|
+
\( -name node_modules -o -name .git -o -name .worktrees -o -name .venv \) -prune -o \
|
|
140
|
+
-type f -name uv.lock -print 2>/dev/null | sort)
|
|
141
|
+
|
|
142
|
+
# Fail CLOSED, not open, on an empty discovery (#1270). Zero trees — including
|
|
143
|
+
# no uv.lock at the workspace root itself — means discovery is broken, not
|
|
144
|
+
# that the repo has nothing to audit. Same posture as `verify.sh`'s `ci_has()`
|
|
145
|
+
# (#1218) and `ci-wiring-audit.sh`'s empty-map check: "This audit checked
|
|
146
|
+
# nothing. That is a configuration error, not a pass." Exit 2, the estate's
|
|
147
|
+
# "cannot tell" code, distinct from "found a vulnerability" (1) and "clean"
|
|
148
|
+
# (0) — CI treats any non-zero as a failed step either way.
|
|
149
|
+
if [ -z "$ALL_LOCKS" ]; then
|
|
150
|
+
printf '::error::py-dependency-audit: discovered ZERO uv.lock trees under %s.\n' "$REPO_ROOT" >&2
|
|
151
|
+
printf 'This audit checked nothing. That is a configuration error, not a pass — see #1270.\n' >&2
|
|
152
|
+
exit 2
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Print what was scanned BEFORE auditing, so the list survives even if a later
|
|
156
|
+
# tree crashes the run — a bare green must be a falsifiable claim, not
|
|
157
|
+
# something a reader has to infer or re-enumerate by hand.
|
|
158
|
+
tree_count=$(printf '%s\n' "$ALL_LOCKS" | wc -l | tr -d ' ')
|
|
159
|
+
printf 'py-dependency-audit: discovered %s uv.lock tree(s):\n' "$tree_count"
|
|
160
|
+
for lock in $ALL_LOCKS; do
|
|
161
|
+
dir=$(dirname "$lock")
|
|
162
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
163
|
+
case "$dir_abs" in
|
|
164
|
+
"$REPO_ROOT") rel=. ;;
|
|
165
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
166
|
+
*) rel="$dir_abs" ;;
|
|
167
|
+
esac
|
|
168
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
169
|
+
printf ' - %s (workspace)\n' "$rel"
|
|
170
|
+
else
|
|
171
|
+
printf ' - %s\n' "$rel"
|
|
172
|
+
fi
|
|
173
|
+
done
|
|
174
|
+
|
|
175
|
+
# Audit each discovered tree. The workspace's own lockfile is audited via the
|
|
176
|
+
# INSTALLED environment `uv sync` already produced (unchanged from before —
|
|
177
|
+
# `uv run pip-audit` with no `-r`). Every other discovered lockfile is not an
|
|
178
|
+
# installed environment, so it is exported with `--frozen` (a read of the
|
|
179
|
+
# committed lock, no re-resolution) and audited with `-r`, from the workspace,
|
|
180
|
+
# which is where pip-audit is actually installed.
|
|
181
|
+
for lock in $ALL_LOCKS; do
|
|
182
|
+
dir=$(dirname "$lock")
|
|
183
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
184
|
+
case "$dir_abs" in
|
|
185
|
+
"$REPO_ROOT") rel=. ;;
|
|
186
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
187
|
+
*) rel="$dir_abs" ;;
|
|
188
|
+
esac
|
|
189
|
+
|
|
190
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
191
|
+
audit_deps "pip-audit (workspace: ${rel})" "" || failed=1
|
|
192
|
+
continue
|
|
193
|
+
fi
|
|
113
194
|
|
|
114
|
-
# Every scaffolding tree with its own lockfile. Discovered rather than listed,
|
|
115
|
-
# so a new skeleton is covered the day it lands instead of the day someone
|
|
116
|
-
# remembers to add it here.
|
|
117
|
-
for lock in $(find _skeletons -name uv.lock -not -path '*/node_modules/*' 2>/dev/null | sort); do
|
|
118
|
-
dir="$(dirname "$lock")"
|
|
119
195
|
reqs="$(mktemp)"
|
|
120
196
|
|
|
121
|
-
# `--
|
|
122
|
-
# what gets audited is exactly what a scaffolded sibling would be born with.
|
|
123
|
-
# `--no-emit-project` drops the skeleton package itself (nothing published to
|
|
197
|
+
# `--no-emit-project` drops the tree's own package (nothing published to
|
|
124
198
|
# audit); `--no-dev` keeps the audit to what actually ships.
|
|
125
199
|
if (cd "$dir" && uv export --frozen --no-dev --no-emit-project) >"$reqs" 2>/dev/null && [ -s "$reqs" ]; then
|
|
126
|
-
audit_deps "pip-audit (${
|
|
200
|
+
audit_deps "pip-audit (${rel})" "-r $reqs" || failed=1
|
|
127
201
|
else
|
|
128
202
|
# An export failure is "couldn't run", not "clean" — same discipline as a
|
|
129
203
|
# transient pip-audit error, and it must never read as a pass.
|
|
130
|
-
echo "::warning::pip-audit (${
|
|
204
|
+
echo "::warning::pip-audit (${rel}): could not export requirements from ${lock}; treating as INCONCLUSIVE and not blocking. Advisory scanning was NOT performed for this tree — see #719."
|
|
131
205
|
inconclusive=$((inconclusive + 1))
|
|
132
206
|
fi
|
|
133
207
|
|
|
@@ -142,4 +216,5 @@ if [ "$inconclusive" -ne 0 ]; then
|
|
|
142
216
|
echo "${inconclusive} tree(s) could not be audited; see warnings above."
|
|
143
217
|
fi
|
|
144
218
|
|
|
219
|
+
echo "py-dependency-audit: audited ${tree_count} tree(s), 0 blocking findings."
|
|
145
220
|
exit 0
|
|
@@ -30,6 +30,23 @@
|
|
|
30
30
|
# the gate was green because it was not looking, and "we checked and it is
|
|
31
31
|
# clean" read identically to "we never checked".
|
|
32
32
|
#
|
|
33
|
+
# ## Discovery, not a fixed path list (#1270)
|
|
34
|
+
#
|
|
35
|
+
# A fixed sweep of "workspace root + `_skeletons/**`" is itself the same shape
|
|
36
|
+
# of gap, one level further down: it reports clean on any pnpm-lock.yaml
|
|
37
|
+
# outside that pair, and an INSTANCE grows trees this repo never has —
|
|
38
|
+
# vendored plugin frontends under `services/*/web/`, a second app under
|
|
39
|
+
# `web-admin/`, or any other nested pnpm project. Found live:
|
|
40
|
+
# `services/ideation/web/pnpm-lock.yaml` sat on two advisory-range packages
|
|
41
|
+
# (one high) while this exact gate stayed green, because it never looked
|
|
42
|
+
# there. Ten lockfiles in that repo; a fixed sweep covered nine.
|
|
43
|
+
#
|
|
44
|
+
# So every pnpm-lock.yaml under the repo is DISCOVERED by walking from the git
|
|
45
|
+
# root, rather than assumed from a hardcoded list — see "Discover the trees"
|
|
46
|
+
# below. Nothing here still means the estate's layouts differ; discovery
|
|
47
|
+
# covers a repo the day it grows a new one, instead of the day someone
|
|
48
|
+
# remembers to add a path.
|
|
49
|
+
#
|
|
33
50
|
# ## This file is distributed verbatim (#743)
|
|
34
51
|
#
|
|
35
52
|
# Both skeletons and every existing sibling/plugin repo hold a BYTE-IDENTICAL
|
|
@@ -39,10 +56,9 @@
|
|
|
39
56
|
# every satellite was born with the original defect and reddened its required JS
|
|
40
57
|
# check on any registry hiccup.
|
|
41
58
|
#
|
|
42
|
-
# It therefore takes its
|
|
43
|
-
# job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
44
|
-
# root workspace here, `apps/frontend` in a sibling
|
|
45
|
-
# sweep below simply finds nothing outside this repo. Keep it layout-agnostic,
|
|
59
|
+
# It therefore takes its WORKSPACE tree from the CURRENT WORKING DIRECTORY —
|
|
60
|
+
# the CI job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
61
|
+
# the root workspace here, `apps/frontend` in a sibling. Keep it layout-agnostic,
|
|
46
62
|
# or the copies stop being interchangeable and `shared-sync.sh --check` starts
|
|
47
63
|
# reporting drift that is really divergence.
|
|
48
64
|
#
|
|
@@ -112,14 +128,80 @@ audit_dir() {
|
|
|
112
128
|
return 0
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
#
|
|
116
|
-
|
|
131
|
+
# ## Discover the trees (#1270)
|
|
132
|
+
#
|
|
133
|
+
# Walk from the git root — not the CWD this script happens to run from — so a
|
|
134
|
+
# vendored tree anywhere in the repo is found regardless of where the CI job's
|
|
135
|
+
# `working-directory` points. Excludes `node_modules` (installed, not source),
|
|
136
|
+
# `.git`, and `.worktrees` (other agents' in-progress checkouts, git-ignored
|
|
137
|
+
# and not part of this run).
|
|
138
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
139
|
+
if [ -z "$REPO_ROOT" ]; then
|
|
140
|
+
echo "::error::js-dependency-audit: not inside a git repository ('git rev-parse --show-toplevel' failed). Discovery cannot walk a tree it cannot find." >&2
|
|
141
|
+
exit 2
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
WORKSPACE_ABS=$(pwd -P)
|
|
145
|
+
|
|
146
|
+
# shellcheck disable=SC2016
|
|
147
|
+
ALL_LOCKS=$(find "$REPO_ROOT" \
|
|
148
|
+
\( -name node_modules -o -name .git -o -name .worktrees \) -prune -o \
|
|
149
|
+
-type f -name pnpm-lock.yaml -print 2>/dev/null | sort)
|
|
150
|
+
|
|
151
|
+
# Fail CLOSED, not open, on an empty discovery (#1270). Zero trees — including
|
|
152
|
+
# no lockfile at the workspace root itself — means discovery is broken (a
|
|
153
|
+
# wrong root, an over-eager prune, a repo layout this script has never seen),
|
|
154
|
+
# not that the repo has nothing to audit. Every repo this script is wired into
|
|
155
|
+
# ships at least one pnpm-lock.yaml, so an empty result here is a configuration
|
|
156
|
+
# error, not a clean pass — the same posture as `verify.sh`'s `ci_has()`
|
|
157
|
+
# (#1218) and `ci-wiring-audit.sh`'s empty-map check: "This audit checked
|
|
158
|
+
# nothing. That is a configuration error, not a pass." Exit 2 (not 1), the
|
|
159
|
+
# estate's "cannot tell" code, distinct from "found a vulnerability" (1) and
|
|
160
|
+
# "clean" (0) — CI treats any non-zero as a failed step either way.
|
|
161
|
+
if [ -z "$ALL_LOCKS" ]; then
|
|
162
|
+
printf '::error::js-dependency-audit: discovered ZERO pnpm-lock.yaml trees under %s.\n' "$REPO_ROOT" >&2
|
|
163
|
+
printf 'This audit checked nothing. That is a configuration error, not a pass — see #1270.\n' >&2
|
|
164
|
+
exit 2
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# Print what was scanned BEFORE auditing, so the list survives even if a later
|
|
168
|
+
# tree crashes the run — a bare green must be a falsifiable claim, not
|
|
169
|
+
# something a reader has to infer or re-enumerate by hand.
|
|
170
|
+
tree_count=$(printf '%s\n' "$ALL_LOCKS" | wc -l | tr -d ' ')
|
|
171
|
+
printf 'js-dependency-audit: discovered %s pnpm-lock.yaml tree(s):\n' "$tree_count"
|
|
172
|
+
for lock in $ALL_LOCKS; do
|
|
173
|
+
dir=$(dirname "$lock")
|
|
174
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
175
|
+
case "$dir_abs" in
|
|
176
|
+
"$REPO_ROOT") rel=. ;;
|
|
177
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
178
|
+
*) rel="$dir_abs" ;;
|
|
179
|
+
esac
|
|
180
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
181
|
+
printf ' - %s (workspace)\n' "$rel"
|
|
182
|
+
else
|
|
183
|
+
printf ' - %s\n' "$rel"
|
|
184
|
+
fi
|
|
185
|
+
done
|
|
117
186
|
|
|
118
|
-
#
|
|
119
|
-
# so
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
187
|
+
# Audit each discovered tree. The workspace's own lockfile is audited WITHOUT
|
|
188
|
+
# --ignore-workspace, so pnpm resolves it normally; every other discovered
|
|
189
|
+
# lockfile is a separate, vendored project and needs the flag, or pnpm walks
|
|
190
|
+
# up, finds the workspace, and silently audits THAT instead — reporting clean
|
|
191
|
+
# for a tree it never looked at, which is this exact defect one level down.
|
|
192
|
+
for lock in $ALL_LOCKS; do
|
|
193
|
+
dir=$(dirname "$lock")
|
|
194
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
195
|
+
case "$dir_abs" in
|
|
196
|
+
"$REPO_ROOT") rel=. ;;
|
|
197
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
198
|
+
*) rel="$dir_abs" ;;
|
|
199
|
+
esac
|
|
200
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
201
|
+
audit_dir "$dir" "pnpm audit (workspace: ${rel})" "" || failed=1
|
|
202
|
+
else
|
|
203
|
+
audit_dir "$dir" "pnpm audit (${rel})" "--ignore-workspace" || failed=1
|
|
204
|
+
fi
|
|
123
205
|
done
|
|
124
206
|
|
|
125
207
|
if [ "$failed" -ne 0 ]; then
|
|
@@ -130,4 +212,5 @@ if [ "$inconclusive" -ne 0 ]; then
|
|
|
130
212
|
echo "${inconclusive} tree(s) could not be audited; see warnings above."
|
|
131
213
|
fi
|
|
132
214
|
|
|
215
|
+
echo "js-dependency-audit: audited ${tree_count} tree(s), 0 blocking findings."
|
|
133
216
|
exit 0
|
|
@@ -30,11 +30,23 @@
|
|
|
30
30
|
# and it is clean" read identically to "we never checked".
|
|
31
31
|
#
|
|
32
32
|
# Unlike `pnpm audit --ignore-workspace`, pip-audit cannot simply be run inside
|
|
33
|
-
# a
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
33
|
+
# a nested directory: it is not an installed environment, and pip-audit's
|
|
34
|
+
# default mode audits an environment. So we export `--frozen` requirements from
|
|
35
|
+
# the nested lockfile (a read of the committed lock, no re-resolution) and
|
|
36
|
+
# audit those with `-r`, from the workspace, which is where pip-audit is
|
|
37
|
+
# actually installed.
|
|
38
|
+
#
|
|
39
|
+
# ## Discovery, not a fixed path list (#1270)
|
|
40
|
+
#
|
|
41
|
+
# A fixed sweep of "workspace + `_skeletons/**`" is the same shape of gap one
|
|
42
|
+
# level further down: it reports clean on any uv.lock outside that pair, and an
|
|
43
|
+
# INSTANCE grows trees this repo never has — vendored plugin services such as
|
|
44
|
+
# `services/idea-scout/uv.lock`. Found live: that lockfile and
|
|
45
|
+
# `services/ideation/uv.lock` both sat on `cryptography@49.0.0` (CVE-2026-69247)
|
|
46
|
+
# while this gate stayed green, because it never looked there.
|
|
47
|
+
#
|
|
48
|
+
# So every uv.lock under the repo is DISCOVERED by walking from the git root,
|
|
49
|
+
# rather than assumed from a hardcoded list — see "Discover the trees" below.
|
|
38
50
|
#
|
|
39
51
|
# ## This file is distributed verbatim (#743)
|
|
40
52
|
#
|
|
@@ -44,11 +56,10 @@
|
|
|
44
56
|
# shipped a raw `uv run pip-audit` — so every satellite was born with the
|
|
45
57
|
# original defect and reddened a required check on any PyPI/OSV hiccup.
|
|
46
58
|
#
|
|
47
|
-
# It therefore takes its
|
|
48
|
-
# job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
49
|
-
# root project here and in a plugin repo, `services/api` in a sibling
|
|
50
|
-
#
|
|
51
|
-
# layout-agnostic, or the copies stop being interchangeable and
|
|
59
|
+
# It therefore takes its WORKSPACE tree from the CURRENT WORKING DIRECTORY —
|
|
60
|
+
# the CI job's `working-directory` — rather than assuming a repo layout: `.` is
|
|
61
|
+
# the root project here and in a plugin repo, `services/api` in a sibling. Keep
|
|
62
|
+
# it layout-agnostic, or the copies stop being interchangeable and
|
|
52
63
|
# `shared-sync.sh --check` starts reporting drift that is really divergence.
|
|
53
64
|
#
|
|
54
65
|
# POSIX sh (the CI step runs `sh scripts/...`, i.e. dash) — no `pipefail`.
|
|
@@ -108,26 +119,89 @@ audit_deps() {
|
|
|
108
119
|
return 0
|
|
109
120
|
}
|
|
110
121
|
|
|
111
|
-
#
|
|
112
|
-
|
|
122
|
+
# ## Discover the trees (#1270)
|
|
123
|
+
#
|
|
124
|
+
# Walk from the git root — not the CWD this script happens to run from — so a
|
|
125
|
+
# vendored tree anywhere in the repo is found regardless of where the CI job's
|
|
126
|
+
# `working-directory` points. Excludes `node_modules`, `.git`, `.worktrees`
|
|
127
|
+
# (other agents' in-progress checkouts) and `.venv` (an installed environment,
|
|
128
|
+
# not a lockfile source).
|
|
129
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
130
|
+
if [ -z "$REPO_ROOT" ]; then
|
|
131
|
+
echo "::error::py-dependency-audit: not inside a git repository ('git rev-parse --show-toplevel' failed). Discovery cannot walk a tree it cannot find." >&2
|
|
132
|
+
exit 2
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
WORKSPACE_ABS=$(pwd -P)
|
|
136
|
+
|
|
137
|
+
# shellcheck disable=SC2016
|
|
138
|
+
ALL_LOCKS=$(find "$REPO_ROOT" \
|
|
139
|
+
\( -name node_modules -o -name .git -o -name .worktrees -o -name .venv \) -prune -o \
|
|
140
|
+
-type f -name uv.lock -print 2>/dev/null | sort)
|
|
141
|
+
|
|
142
|
+
# Fail CLOSED, not open, on an empty discovery (#1270). Zero trees — including
|
|
143
|
+
# no uv.lock at the workspace root itself — means discovery is broken, not
|
|
144
|
+
# that the repo has nothing to audit. Same posture as `verify.sh`'s `ci_has()`
|
|
145
|
+
# (#1218) and `ci-wiring-audit.sh`'s empty-map check: "This audit checked
|
|
146
|
+
# nothing. That is a configuration error, not a pass." Exit 2, the estate's
|
|
147
|
+
# "cannot tell" code, distinct from "found a vulnerability" (1) and "clean"
|
|
148
|
+
# (0) — CI treats any non-zero as a failed step either way.
|
|
149
|
+
if [ -z "$ALL_LOCKS" ]; then
|
|
150
|
+
printf '::error::py-dependency-audit: discovered ZERO uv.lock trees under %s.\n' "$REPO_ROOT" >&2
|
|
151
|
+
printf 'This audit checked nothing. That is a configuration error, not a pass — see #1270.\n' >&2
|
|
152
|
+
exit 2
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Print what was scanned BEFORE auditing, so the list survives even if a later
|
|
156
|
+
# tree crashes the run — a bare green must be a falsifiable claim, not
|
|
157
|
+
# something a reader has to infer or re-enumerate by hand.
|
|
158
|
+
tree_count=$(printf '%s\n' "$ALL_LOCKS" | wc -l | tr -d ' ')
|
|
159
|
+
printf 'py-dependency-audit: discovered %s uv.lock tree(s):\n' "$tree_count"
|
|
160
|
+
for lock in $ALL_LOCKS; do
|
|
161
|
+
dir=$(dirname "$lock")
|
|
162
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
163
|
+
case "$dir_abs" in
|
|
164
|
+
"$REPO_ROOT") rel=. ;;
|
|
165
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
166
|
+
*) rel="$dir_abs" ;;
|
|
167
|
+
esac
|
|
168
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
169
|
+
printf ' - %s (workspace)\n' "$rel"
|
|
170
|
+
else
|
|
171
|
+
printf ' - %s\n' "$rel"
|
|
172
|
+
fi
|
|
173
|
+
done
|
|
174
|
+
|
|
175
|
+
# Audit each discovered tree. The workspace's own lockfile is audited via the
|
|
176
|
+
# INSTALLED environment `uv sync` already produced (unchanged from before —
|
|
177
|
+
# `uv run pip-audit` with no `-r`). Every other discovered lockfile is not an
|
|
178
|
+
# installed environment, so it is exported with `--frozen` (a read of the
|
|
179
|
+
# committed lock, no re-resolution) and audited with `-r`, from the workspace,
|
|
180
|
+
# which is where pip-audit is actually installed.
|
|
181
|
+
for lock in $ALL_LOCKS; do
|
|
182
|
+
dir=$(dirname "$lock")
|
|
183
|
+
dir_abs=$(cd "$dir" 2>/dev/null && pwd -P)
|
|
184
|
+
case "$dir_abs" in
|
|
185
|
+
"$REPO_ROOT") rel=. ;;
|
|
186
|
+
"$REPO_ROOT"/*) rel=${dir_abs#"$REPO_ROOT"/} ;;
|
|
187
|
+
*) rel="$dir_abs" ;;
|
|
188
|
+
esac
|
|
189
|
+
|
|
190
|
+
if [ "$dir_abs" = "$WORKSPACE_ABS" ]; then
|
|
191
|
+
audit_deps "pip-audit (workspace: ${rel})" "" || failed=1
|
|
192
|
+
continue
|
|
193
|
+
fi
|
|
113
194
|
|
|
114
|
-
# Every scaffolding tree with its own lockfile. Discovered rather than listed,
|
|
115
|
-
# so a new skeleton is covered the day it lands instead of the day someone
|
|
116
|
-
# remembers to add it here.
|
|
117
|
-
for lock in $(find _skeletons -name uv.lock -not -path '*/node_modules/*' 2>/dev/null | sort); do
|
|
118
|
-
dir="$(dirname "$lock")"
|
|
119
195
|
reqs="$(mktemp)"
|
|
120
196
|
|
|
121
|
-
# `--
|
|
122
|
-
# what gets audited is exactly what a scaffolded sibling would be born with.
|
|
123
|
-
# `--no-emit-project` drops the skeleton package itself (nothing published to
|
|
197
|
+
# `--no-emit-project` drops the tree's own package (nothing published to
|
|
124
198
|
# audit); `--no-dev` keeps the audit to what actually ships.
|
|
125
199
|
if (cd "$dir" && uv export --frozen --no-dev --no-emit-project) >"$reqs" 2>/dev/null && [ -s "$reqs" ]; then
|
|
126
|
-
audit_deps "pip-audit (${
|
|
200
|
+
audit_deps "pip-audit (${rel})" "-r $reqs" || failed=1
|
|
127
201
|
else
|
|
128
202
|
# An export failure is "couldn't run", not "clean" — same discipline as a
|
|
129
203
|
# transient pip-audit error, and it must never read as a pass.
|
|
130
|
-
echo "::warning::pip-audit (${
|
|
204
|
+
echo "::warning::pip-audit (${rel}): could not export requirements from ${lock}; treating as INCONCLUSIVE and not blocking. Advisory scanning was NOT performed for this tree — see #719."
|
|
131
205
|
inconclusive=$((inconclusive + 1))
|
|
132
206
|
fi
|
|
133
207
|
|
|
@@ -142,4 +216,5 @@ if [ "$inconclusive" -ne 0 ]; then
|
|
|
142
216
|
echo "${inconclusive} tree(s) could not be audited; see warnings above."
|
|
143
217
|
fi
|
|
144
218
|
|
|
219
|
+
echo "py-dependency-audit: audited ${tree_count} tree(s), 0 blocking findings."
|
|
145
220
|
exit 0
|