@aldegad/safedeps 2.1.0
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/ARCHITECTURE.md +595 -0
- package/LICENSE +190 -0
- package/README.md +311 -0
- package/ROADMAP.md +131 -0
- package/SKILL.md +200 -0
- package/agents/openai.yaml +4 -0
- package/bin/safedeps +842 -0
- package/lib/ledger/ledger.sh +346 -0
- package/lib/providers/providers.sh +479 -0
- package/package.json +41 -0
- package/scripts/install/install-safedeps-hooks.mjs +209 -0
- package/scripts/install/install-safedeps-recheck-agent.mjs +203 -0
- package/scripts/install/migrate-safedeps-state.mjs +91 -0
- package/scripts/safedeps-post-verify.sh +584 -0
- package/scripts/safedeps-pre-guard.sh +427 -0
- package/scripts/safedeps-recheck-alert.sh +115 -0
- package/scripts/test/e2e.sh +107 -0
- package/scripts/test/fixture-provider.mjs +104 -0
- package/scripts/test/smoke.sh +89 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Safedeps provider adapters.
|
|
3
|
+
# OSV is the canonical advisory truth; KEV/GHSA only add observable signals.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SAFEDEPS_HOME="${SAFEDEPS_HOME:-${HOME}/.safedeps}"
|
|
8
|
+
SAFEDEPS_CACHE_DIR="${SAFEDEPS_CACHE_DIR:-${SAFEDEPS_HOME}/cache}"
|
|
9
|
+
SAFEDEPS_ADVISORY_LOG="${SAFEDEPS_ADVISORY_LOG:-${SAFEDEPS_HOME}/advisory.log}"
|
|
10
|
+
SAFEDEPS_PROVIDER_CACHE_TTL_SECONDS="${SAFEDEPS_PROVIDER_CACHE_TTL_SECONDS:-86400}"
|
|
11
|
+
|
|
12
|
+
SAFEDEPS_OSV_API_URL="${SAFEDEPS_OSV_API_URL:-https://api.osv.dev/v1/query}"
|
|
13
|
+
SAFEDEPS_KEV_CATALOG_URL="${SAFEDEPS_KEV_CATALOG_URL:-https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json}"
|
|
14
|
+
SAFEDEPS_GHSA_API_URL="${SAFEDEPS_GHSA_API_URL:-https://api.github.com/advisories}"
|
|
15
|
+
|
|
16
|
+
safedeps_providers_init() {
|
|
17
|
+
umask 077
|
|
18
|
+
mkdir -p \
|
|
19
|
+
"${SAFEDEPS_CACHE_DIR}/osv" \
|
|
20
|
+
"${SAFEDEPS_CACHE_DIR}/kev" \
|
|
21
|
+
"${SAFEDEPS_CACHE_DIR}/ghsa" \
|
|
22
|
+
"$(dirname "${SAFEDEPS_ADVISORY_LOG}")"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
safedeps_provider_log() {
|
|
26
|
+
local level="$1"
|
|
27
|
+
local message="$2"
|
|
28
|
+
|
|
29
|
+
safedeps_providers_init
|
|
30
|
+
printf '[%s] %s %s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "${level}" "${message}" >> "${SAFEDEPS_ADVISORY_LOG}"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
safedeps_require_json_tools() {
|
|
34
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
35
|
+
printf 'safedeps providers: jq is required\n' >&2
|
|
36
|
+
return 1
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
safedeps_require_http_client() {
|
|
41
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
42
|
+
printf 'safedeps providers: curl is required for provider queries\n' >&2
|
|
43
|
+
return 1
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
safedeps_provider_mktemp_dir() {
|
|
48
|
+
local tmp_root="${TMPDIR:-/tmp}"
|
|
49
|
+
|
|
50
|
+
mkdir -p "${tmp_root}" || return 1
|
|
51
|
+
mktemp -d "${tmp_root%/}/safedeps-providers.XXXXXX"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
safedeps_now_iso() {
|
|
55
|
+
date -u +"%Y-%m-%dT%H:%M:%SZ"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
safedeps_hash_text() {
|
|
59
|
+
local input="$1"
|
|
60
|
+
|
|
61
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
62
|
+
printf '%s' "${input}" | shasum -a 256 | cut -d' ' -f1
|
|
63
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
64
|
+
printf '%s' "${input}" | sha256sum | cut -d' ' -f1
|
|
65
|
+
else
|
|
66
|
+
printf 'safedeps providers: shasum or sha256sum is required\n' >&2
|
|
67
|
+
return 1
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
safedeps_file_mtime() {
|
|
72
|
+
local path="$1"
|
|
73
|
+
|
|
74
|
+
stat -f %m "${path}" 2>/dev/null || stat -c %Y "${path}" 2>/dev/null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
safedeps_cache_is_fresh() {
|
|
78
|
+
local path="$1"
|
|
79
|
+
local ttl="${2:-${SAFEDEPS_PROVIDER_CACHE_TTL_SECONDS}}"
|
|
80
|
+
local now
|
|
81
|
+
local mtime
|
|
82
|
+
|
|
83
|
+
[[ -f "${path}" ]] || return 1
|
|
84
|
+
now=$(date +%s)
|
|
85
|
+
mtime=$(safedeps_file_mtime "${path}") || return 1
|
|
86
|
+
[[ $(( now - mtime )) -le "${ttl}" ]]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
safedeps_cache_key() {
|
|
90
|
+
local namespace="$1"
|
|
91
|
+
local ecosystem="$2"
|
|
92
|
+
local package_name="$3"
|
|
93
|
+
local version="$4"
|
|
94
|
+
|
|
95
|
+
safedeps_hash_text "${namespace}
|
|
96
|
+
${ecosystem}
|
|
97
|
+
${package_name}
|
|
98
|
+
${version}"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
safedeps_json_uri_escape() {
|
|
102
|
+
jq -nr --arg value "$1" '$value | @uri'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
safedeps_osv_ecosystem() {
|
|
106
|
+
case "$1" in
|
|
107
|
+
npm|NPM) printf 'npm' ;;
|
|
108
|
+
pypi|PyPI|pip) printf 'PyPI' ;;
|
|
109
|
+
crates.io|cargo|rust) printf 'crates.io' ;;
|
|
110
|
+
go|golang|Go) printf 'Go' ;;
|
|
111
|
+
rubygems|gem|ruby|RubyGems) printf 'RubyGems' ;;
|
|
112
|
+
maven|Maven) printf 'Maven' ;;
|
|
113
|
+
nuget|NuGet) printf 'NuGet' ;;
|
|
114
|
+
*) printf '%s' "$1" ;;
|
|
115
|
+
esac
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
safedeps_ghsa_ecosystem() {
|
|
119
|
+
case "$1" in
|
|
120
|
+
npm|NPM) printf 'npm' ;;
|
|
121
|
+
pypi|PyPI|pip) printf 'pip' ;;
|
|
122
|
+
crates.io|cargo|rust) printf 'rust' ;;
|
|
123
|
+
go|golang|Go) printf 'go' ;;
|
|
124
|
+
rubygems|gem|ruby|RubyGems) printf 'rubygems' ;;
|
|
125
|
+
maven|Maven) printf 'maven' ;;
|
|
126
|
+
nuget|NuGet) printf 'nuget' ;;
|
|
127
|
+
*) printf '%s' "$1" ;;
|
|
128
|
+
esac
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
safedeps_osv_query() {
|
|
132
|
+
local ecosystem="$1"
|
|
133
|
+
local package_name="$2"
|
|
134
|
+
local version="$3"
|
|
135
|
+
local osv_ecosystem
|
|
136
|
+
local cache_key
|
|
137
|
+
local cache_path
|
|
138
|
+
local payload
|
|
139
|
+
local response_file
|
|
140
|
+
local http_status
|
|
141
|
+
|
|
142
|
+
safedeps_require_json_tools || return 1
|
|
143
|
+
safedeps_providers_init
|
|
144
|
+
|
|
145
|
+
osv_ecosystem=$(safedeps_osv_ecosystem "${ecosystem}")
|
|
146
|
+
cache_key=$(safedeps_cache_key "osv" "${osv_ecosystem}" "${package_name}" "${version}")
|
|
147
|
+
cache_path="${SAFEDEPS_CACHE_DIR}/osv/${cache_key}.json"
|
|
148
|
+
|
|
149
|
+
if safedeps_cache_is_fresh "${cache_path}"; then
|
|
150
|
+
safedeps_provider_log "INFO" "OSV cache hit ecosystem=${osv_ecosystem} package=${package_name} version=${version}"
|
|
151
|
+
cat "${cache_path}"
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
safedeps_require_http_client || {
|
|
156
|
+
if [[ -f "${cache_path}" ]]; then
|
|
157
|
+
safedeps_provider_log "ERROR" "OSV unavailable; stale cache refused ecosystem=${osv_ecosystem} package=${package_name} version=${version}"
|
|
158
|
+
else
|
|
159
|
+
safedeps_provider_log "ERROR" "OSV unavailable; cache miss ecosystem=${osv_ecosystem} package=${package_name} version=${version}"
|
|
160
|
+
fi
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
payload=$(jq -cn \
|
|
165
|
+
--arg ecosystem "${osv_ecosystem}" \
|
|
166
|
+
--arg package "${package_name}" \
|
|
167
|
+
--arg version "${version}" \
|
|
168
|
+
'{version: $version, package: {name: $package, ecosystem: $ecosystem}}')
|
|
169
|
+
|
|
170
|
+
response_file="${cache_path}.$$"
|
|
171
|
+
http_status=$(curl -fsS \
|
|
172
|
+
--max-time 15 \
|
|
173
|
+
-H 'Content-Type: application/json' \
|
|
174
|
+
-o "${response_file}" \
|
|
175
|
+
-w '%{http_code}' \
|
|
176
|
+
-d "${payload}" \
|
|
177
|
+
"${SAFEDEPS_OSV_API_URL}" 2>/dev/null || true)
|
|
178
|
+
|
|
179
|
+
if [[ "${http_status}" == "200" ]] && jq -e 'type == "object"' "${response_file}" >/dev/null 2>&1; then
|
|
180
|
+
mv "${response_file}" "${cache_path}"
|
|
181
|
+
safedeps_provider_log "INFO" "OSV live query ok ecosystem=${osv_ecosystem} package=${package_name} version=${version}"
|
|
182
|
+
cat "${cache_path}"
|
|
183
|
+
return 0
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
rm -f "${response_file}"
|
|
187
|
+
if [[ -f "${cache_path}" ]]; then
|
|
188
|
+
safedeps_provider_log "ERROR" "OSV live query failed; stale cache refused ecosystem=${osv_ecosystem} package=${package_name} version=${version} status=${http_status:-none}"
|
|
189
|
+
else
|
|
190
|
+
safedeps_provider_log "ERROR" "OSV live query failed; cache miss ecosystem=${osv_ecosystem} package=${package_name} version=${version} status=${http_status:-none}"
|
|
191
|
+
fi
|
|
192
|
+
return 1
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
safedeps_extract_cves_from_osv() {
|
|
196
|
+
local osv_json="$1"
|
|
197
|
+
|
|
198
|
+
jq -r '
|
|
199
|
+
[
|
|
200
|
+
.vulns[]? |
|
|
201
|
+
(.id // empty),
|
|
202
|
+
(.aliases[]? // empty)
|
|
203
|
+
]
|
|
204
|
+
| map(select(test("^CVE-[0-9]{4}-[0-9]+$")))
|
|
205
|
+
| unique
|
|
206
|
+
| .[]
|
|
207
|
+
' "${osv_json}"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
safedeps_kev_catalog_path() {
|
|
211
|
+
printf '%s/kev/known_exploited_vulnerabilities.json' "${SAFEDEPS_CACHE_DIR}"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
safedeps_kev_refresh_catalog() {
|
|
215
|
+
local cache_path
|
|
216
|
+
local response_path
|
|
217
|
+
local http_status
|
|
218
|
+
|
|
219
|
+
safedeps_require_json_tools || return 1
|
|
220
|
+
safedeps_providers_init
|
|
221
|
+
cache_path=$(safedeps_kev_catalog_path)
|
|
222
|
+
|
|
223
|
+
if safedeps_cache_is_fresh "${cache_path}"; then
|
|
224
|
+
printf '%s' "${cache_path}"
|
|
225
|
+
return 0
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
if ! safedeps_require_http_client; then
|
|
229
|
+
[[ -f "${cache_path}" ]] && printf '%s' "${cache_path}" && return 0
|
|
230
|
+
return 1
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
response_path="${cache_path}.$$"
|
|
234
|
+
http_status=$(curl -fsS --max-time 15 -o "${response_path}" -w '%{http_code}' "${SAFEDEPS_KEV_CATALOG_URL}" 2>/dev/null || true)
|
|
235
|
+
|
|
236
|
+
if [[ "${http_status}" == "200" ]] && jq -e '.vulnerabilities | type == "array"' "${response_path}" >/dev/null 2>&1; then
|
|
237
|
+
mv "${response_path}" "${cache_path}"
|
|
238
|
+
safedeps_provider_log "INFO" "CISA KEV catalog refresh ok"
|
|
239
|
+
printf '%s' "${cache_path}"
|
|
240
|
+
return 0
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
rm -f "${response_path}"
|
|
244
|
+
if [[ -f "${cache_path}" ]]; then
|
|
245
|
+
safedeps_provider_log "WARN" "CISA KEV refresh failed; using stale local catalog status=${http_status:-none}"
|
|
246
|
+
printf '%s' "${cache_path}"
|
|
247
|
+
return 0
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
safedeps_provider_log "WARN" "CISA KEV unavailable and no local catalog status=${http_status:-none}"
|
|
251
|
+
return 1
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
safedeps_kev_overlay() {
|
|
255
|
+
local osv_json="$1"
|
|
256
|
+
local queried_at="$2"
|
|
257
|
+
local catalog_path
|
|
258
|
+
local cve_array
|
|
259
|
+
local status="ok"
|
|
260
|
+
local warning=""
|
|
261
|
+
|
|
262
|
+
cve_array=$(safedeps_extract_cves_from_osv "${osv_json}" | jq -R . | jq -s .)
|
|
263
|
+
|
|
264
|
+
if ! catalog_path=$(safedeps_kev_refresh_catalog); then
|
|
265
|
+
jq -cn --arg queried_at "${queried_at}" --argjson cves "${cve_array}" \
|
|
266
|
+
'{queried_at: $queried_at, status: "unavailable", warning: "CISA KEV catalog unavailable", cves_checked: $cves, exploited: false, matches: []}'
|
|
267
|
+
return 0
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
if ! safedeps_cache_is_fresh "${catalog_path}"; then
|
|
271
|
+
status="stale"
|
|
272
|
+
warning="CISA KEV catalog is older than provider TTL"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
jq -cn \
|
|
276
|
+
--arg queried_at "${queried_at}" \
|
|
277
|
+
--arg status "${status}" \
|
|
278
|
+
--arg warning "${warning}" \
|
|
279
|
+
--argjson cves "${cve_array}" \
|
|
280
|
+
--slurpfile catalog "${catalog_path}" '
|
|
281
|
+
($catalog[0].vulnerabilities // []) as $items
|
|
282
|
+
| ($items | map(select(.cveID as $id | $cves | index($id)))) as $matches
|
|
283
|
+
| {
|
|
284
|
+
queried_at: $queried_at,
|
|
285
|
+
status: $status,
|
|
286
|
+
warning: (if $warning == "" then null else $warning end),
|
|
287
|
+
cves_checked: $cves,
|
|
288
|
+
exploited: (($matches | length) > 0),
|
|
289
|
+
matches: $matches
|
|
290
|
+
}'
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
safedeps_ghsa_query() {
|
|
294
|
+
local ecosystem="$1"
|
|
295
|
+
local package_name="$2"
|
|
296
|
+
local queried_at="$3"
|
|
297
|
+
local ghsa_ecosystem
|
|
298
|
+
local encoded_ecosystem
|
|
299
|
+
local encoded_package
|
|
300
|
+
local cache_key
|
|
301
|
+
local cache_path
|
|
302
|
+
local response_file
|
|
303
|
+
local http_status
|
|
304
|
+
|
|
305
|
+
safedeps_require_json_tools || return 1
|
|
306
|
+
safedeps_providers_init
|
|
307
|
+
|
|
308
|
+
ghsa_ecosystem=$(safedeps_ghsa_ecosystem "${ecosystem}")
|
|
309
|
+
cache_key=$(safedeps_cache_key "ghsa" "${ghsa_ecosystem}" "${package_name}" "all")
|
|
310
|
+
cache_path="${SAFEDEPS_CACHE_DIR}/ghsa/${cache_key}.json"
|
|
311
|
+
|
|
312
|
+
if safedeps_cache_is_fresh "${cache_path}"; then
|
|
313
|
+
jq -cn --arg queried_at "${queried_at}" --slurpfile advisories "${cache_path}" \
|
|
314
|
+
'{queried_at: $queried_at, status: "cache_hit", advisories: $advisories[0]}'
|
|
315
|
+
return 0
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
if ! safedeps_require_http_client; then
|
|
319
|
+
safedeps_provider_log "WARN" "GHSA skipped; curl unavailable ecosystem=${ghsa_ecosystem} package=${package_name}"
|
|
320
|
+
jq -cn --arg queried_at "${queried_at}" \
|
|
321
|
+
'{queried_at: $queried_at, status: "skipped", warning: "curl unavailable", advisories: []}'
|
|
322
|
+
return 0
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
encoded_ecosystem=$(safedeps_json_uri_escape "${ghsa_ecosystem}")
|
|
326
|
+
encoded_package=$(safedeps_json_uri_escape "${package_name}")
|
|
327
|
+
response_file="${cache_path}.$$"
|
|
328
|
+
|
|
329
|
+
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
330
|
+
http_status=$(curl -fsS \
|
|
331
|
+
--max-time 15 \
|
|
332
|
+
-H 'Accept: application/vnd.github+json' \
|
|
333
|
+
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
|
334
|
+
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
|
335
|
+
-o "${response_file}" \
|
|
336
|
+
-w '%{http_code}' \
|
|
337
|
+
"${SAFEDEPS_GHSA_API_URL}?ecosystem=${encoded_ecosystem}&affects=${encoded_package}&per_page=100" 2>/dev/null || true)
|
|
338
|
+
else
|
|
339
|
+
http_status=$(curl -fsS \
|
|
340
|
+
--max-time 15 \
|
|
341
|
+
-H 'Accept: application/vnd.github+json' \
|
|
342
|
+
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
|
343
|
+
-o "${response_file}" \
|
|
344
|
+
-w '%{http_code}' \
|
|
345
|
+
"${SAFEDEPS_GHSA_API_URL}?ecosystem=${encoded_ecosystem}&affects=${encoded_package}&per_page=100" 2>/dev/null || true)
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if [[ "${http_status}" == "200" ]] && jq -e 'type == "array"' "${response_file}" >/dev/null 2>&1; then
|
|
349
|
+
mv "${response_file}" "${cache_path}"
|
|
350
|
+
safedeps_provider_log "INFO" "GHSA live query ok ecosystem=${ghsa_ecosystem} package=${package_name}"
|
|
351
|
+
jq -cn --arg queried_at "${queried_at}" --slurpfile advisories "${cache_path}" \
|
|
352
|
+
'{queried_at: $queried_at, status: "live", advisories: $advisories[0]}'
|
|
353
|
+
return 0
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
rm -f "${response_file}"
|
|
357
|
+
safedeps_provider_log "WARN" "GHSA cross-check skipped ecosystem=${ghsa_ecosystem} package=${package_name} status=${http_status:-none}"
|
|
358
|
+
jq -cn --arg queried_at "${queried_at}" --arg status "${http_status:-none}" \
|
|
359
|
+
'{queried_at: $queried_at, status: "skipped", warning: ("GHSA cross-check skipped; HTTP status " + $status), advisories: []}'
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
safedeps_providers_query() {
|
|
363
|
+
local ecosystem="$1"
|
|
364
|
+
local package_name="$2"
|
|
365
|
+
local version="$3"
|
|
366
|
+
local queried_at
|
|
367
|
+
local osv_file
|
|
368
|
+
local temp_dir
|
|
369
|
+
local kev_file
|
|
370
|
+
local ghsa_file
|
|
371
|
+
|
|
372
|
+
safedeps_require_json_tools || return 1
|
|
373
|
+
queried_at=$(safedeps_now_iso)
|
|
374
|
+
if ! temp_dir=$(safedeps_provider_mktemp_dir); then
|
|
375
|
+
safedeps_provider_log "ERROR" "provider temp dir creation failed tmpdir=${TMPDIR:-/tmp}"
|
|
376
|
+
jq -cn \
|
|
377
|
+
--arg ecosystem "${ecosystem}" \
|
|
378
|
+
--arg package "${package_name}" \
|
|
379
|
+
--arg version "${version}" \
|
|
380
|
+
--arg queried_at "${queried_at}" \
|
|
381
|
+
'{
|
|
382
|
+
ecosystem: $ecosystem,
|
|
383
|
+
package: $package,
|
|
384
|
+
version: $version,
|
|
385
|
+
queried_at: $queried_at,
|
|
386
|
+
status: "blocked",
|
|
387
|
+
reason: "provider temp dir creation failed",
|
|
388
|
+
vulnerabilities: [],
|
|
389
|
+
kev: {queried_at: $queried_at, status: "not_queried", exploited: false, matches: []},
|
|
390
|
+
advisories: [],
|
|
391
|
+
provider_status: {
|
|
392
|
+
osv: {status: "failed_closed"},
|
|
393
|
+
kev: {status: "not_queried"},
|
|
394
|
+
ghsa: {status: "not_queried"}
|
|
395
|
+
}
|
|
396
|
+
}'
|
|
397
|
+
return 1
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
osv_file="${temp_dir}/osv.json"
|
|
401
|
+
kev_file="${temp_dir}/kev.json"
|
|
402
|
+
ghsa_file="${temp_dir}/ghsa.json"
|
|
403
|
+
|
|
404
|
+
if ! safedeps_osv_query "${ecosystem}" "${package_name}" "${version}" > "${osv_file}"; then
|
|
405
|
+
jq -cn \
|
|
406
|
+
--arg ecosystem "${ecosystem}" \
|
|
407
|
+
--arg package "${package_name}" \
|
|
408
|
+
--arg version "${version}" \
|
|
409
|
+
--arg queried_at "${queried_at}" \
|
|
410
|
+
'{
|
|
411
|
+
ecosystem: $ecosystem,
|
|
412
|
+
package: $package,
|
|
413
|
+
version: $version,
|
|
414
|
+
queried_at: $queried_at,
|
|
415
|
+
status: "blocked",
|
|
416
|
+
reason: "OSV primary provider unavailable and no fresh cache",
|
|
417
|
+
vulnerabilities: [],
|
|
418
|
+
kev: {queried_at: $queried_at, status: "not_queried", exploited: false, matches: []},
|
|
419
|
+
advisories: [],
|
|
420
|
+
provider_status: {
|
|
421
|
+
osv: {status: "failed_closed"},
|
|
422
|
+
kev: {status: "not_queried"},
|
|
423
|
+
ghsa: {status: "not_queried"}
|
|
424
|
+
}
|
|
425
|
+
}'
|
|
426
|
+
rm -rf "${temp_dir}"
|
|
427
|
+
return 1
|
|
428
|
+
fi
|
|
429
|
+
|
|
430
|
+
safedeps_kev_overlay "${osv_file}" "${queried_at}" > "${kev_file}"
|
|
431
|
+
safedeps_ghsa_query "${ecosystem}" "${package_name}" "${queried_at}" > "${ghsa_file}"
|
|
432
|
+
|
|
433
|
+
jq -cn \
|
|
434
|
+
--arg ecosystem "${ecosystem}" \
|
|
435
|
+
--arg package "${package_name}" \
|
|
436
|
+
--arg version "${version}" \
|
|
437
|
+
--arg queried_at "${queried_at}" \
|
|
438
|
+
--slurpfile osv "${osv_file}" \
|
|
439
|
+
--slurpfile kev "${kev_file}" \
|
|
440
|
+
--slurpfile ghsa "${ghsa_file}" '
|
|
441
|
+
($osv[0].vulns // []) as $vulns
|
|
442
|
+
| ($kev[0]) as $kev_result
|
|
443
|
+
| ($ghsa[0]) as $ghsa_result
|
|
444
|
+
| {
|
|
445
|
+
ecosystem: $ecosystem,
|
|
446
|
+
package: $package,
|
|
447
|
+
version: $version,
|
|
448
|
+
queried_at: $queried_at,
|
|
449
|
+
status: (if $kev_result.exploited then "hard_block" elif ($vulns | length) > 0 then "vulnerable" else "clean" end),
|
|
450
|
+
vulnerabilities: $vulns,
|
|
451
|
+
kev: $kev_result,
|
|
452
|
+
advisories: ($ghsa_result.advisories // []),
|
|
453
|
+
provider_status: {
|
|
454
|
+
osv: {status: "ok", canonical: true},
|
|
455
|
+
kev: {status: ($kev_result.status // "ok"), overlay: true},
|
|
456
|
+
ghsa: {status: ($ghsa_result.status // "ok"), enrichment: true}
|
|
457
|
+
}
|
|
458
|
+
}'
|
|
459
|
+
rm -rf "${temp_dir}"
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
463
|
+
command_name="${1:-}"
|
|
464
|
+
shift || true
|
|
465
|
+
|
|
466
|
+
case "${command_name}" in
|
|
467
|
+
query)
|
|
468
|
+
if [[ "$#" -ne 3 ]]; then
|
|
469
|
+
printf 'usage: %s query <ecosystem> <package> <version>\n' "$0" >&2
|
|
470
|
+
exit 2
|
|
471
|
+
fi
|
|
472
|
+
safedeps_providers_query "$@"
|
|
473
|
+
;;
|
|
474
|
+
*)
|
|
475
|
+
printf 'usage: %s query <ecosystem> <package> <version>\n' "$0" >&2
|
|
476
|
+
exit 2
|
|
477
|
+
;;
|
|
478
|
+
esac
|
|
479
|
+
fi
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aldegad/safedeps",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Dependency install safety gate with OSV-backed advisory checks, approved-spec ledger enforcement, and reorg rollback hooks",
|
|
5
|
+
"main": "bin/safedeps",
|
|
6
|
+
"bin": {
|
|
7
|
+
"safedeps": "bin/safedeps"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"scripts/",
|
|
13
|
+
"agents/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"ARCHITECTURE.md",
|
|
16
|
+
"ROADMAP.md",
|
|
17
|
+
"SKILL.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "bash scripts/test/smoke.sh && bash scripts/test/e2e.sh"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"safedeps",
|
|
25
|
+
"security",
|
|
26
|
+
"supply-chain",
|
|
27
|
+
"advisory",
|
|
28
|
+
"osv",
|
|
29
|
+
"reorg",
|
|
30
|
+
"claude-code",
|
|
31
|
+
"hooks",
|
|
32
|
+
"package-lock",
|
|
33
|
+
"rollback"
|
|
34
|
+
],
|
|
35
|
+
"author": "soohongkim",
|
|
36
|
+
"license": "Apache-2.0",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/aldegad/safedeps.git"
|
|
40
|
+
}
|
|
41
|
+
}
|