@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,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
const portFile = process.argv[2];
|
|
6
|
+
const stateFile = process.argv[3];
|
|
7
|
+
|
|
8
|
+
if (!portFile || !stateFile) {
|
|
9
|
+
console.error('usage: fixture-provider.mjs <port-file> <state-file>');
|
|
10
|
+
process.exit(2);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readJson(req) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
let body = '';
|
|
16
|
+
req.setEncoding('utf8');
|
|
17
|
+
req.on('data', (chunk) => {
|
|
18
|
+
body += chunk;
|
|
19
|
+
});
|
|
20
|
+
req.on('end', () => {
|
|
21
|
+
try {
|
|
22
|
+
resolve(JSON.parse(body || '{}'));
|
|
23
|
+
} catch {
|
|
24
|
+
resolve({});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function state() {
|
|
31
|
+
return JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function osvVuln(id, fixed) {
|
|
35
|
+
const events = [{ introduced: '0' }];
|
|
36
|
+
if (fixed) events.push({ fixed });
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
aliases: [id],
|
|
40
|
+
affected: [
|
|
41
|
+
{
|
|
42
|
+
package: { ecosystem: 'npm', name: 'fixture' },
|
|
43
|
+
ranges: [{ type: 'SEMVER', events }]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function osvResponse(packageName, version) {
|
|
50
|
+
const key = `${packageName}@${version}`;
|
|
51
|
+
const current = state();
|
|
52
|
+
if (current.vulnerable?.includes(key)) {
|
|
53
|
+
return { vulns: [osvVuln('CVE-2026-1000', null)] };
|
|
54
|
+
}
|
|
55
|
+
if (packageName === 'fixture-vuln' && version === '1.0.0') {
|
|
56
|
+
return { vulns: [osvVuln('CVE-2026-1001', '1.0.1')] };
|
|
57
|
+
}
|
|
58
|
+
if (packageName === 'fixture-unpatched') {
|
|
59
|
+
return { vulns: [osvVuln('CVE-2026-1002', null)] };
|
|
60
|
+
}
|
|
61
|
+
if (packageName === 'fixture-kev') {
|
|
62
|
+
return { vulns: [osvVuln('CVE-2026-9999', null)] };
|
|
63
|
+
}
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const server = http.createServer(async (req, res) => {
|
|
68
|
+
if (req.method === 'POST' && req.url === '/osv/v1/query') {
|
|
69
|
+
const body = await readJson(req);
|
|
70
|
+
const packageName = body.package?.name || '';
|
|
71
|
+
const version = body.version || '';
|
|
72
|
+
res.setHeader('content-type', 'application/json');
|
|
73
|
+
res.end(JSON.stringify(osvResponse(packageName, version)));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (req.method === 'GET' && req.url === '/kev.json') {
|
|
78
|
+
res.setHeader('content-type', 'application/json');
|
|
79
|
+
res.end(JSON.stringify({
|
|
80
|
+
vulnerabilities: [
|
|
81
|
+
{
|
|
82
|
+
cveID: 'CVE-2026-9999',
|
|
83
|
+
vendorProject: 'fixture',
|
|
84
|
+
product: 'fixture-kev',
|
|
85
|
+
vulnerabilityName: 'Fixture KEV'
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (req.method === 'GET' && req.url.startsWith('/advisories')) {
|
|
93
|
+
res.setHeader('content-type', 'application/json');
|
|
94
|
+
res.end('[]');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
res.statusCode = 404;
|
|
99
|
+
res.end('not found');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
server.listen(0, '127.0.0.1', () => {
|
|
103
|
+
fs.writeFileSync(portFile, String(server.address().port));
|
|
104
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
|
|
5
|
+
cd "${ROOT_DIR}"
|
|
6
|
+
|
|
7
|
+
pass() {
|
|
8
|
+
printf 'ok - %s\n' "$1"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
fail() {
|
|
12
|
+
printf 'not ok - %s\n' "$1" >&2
|
|
13
|
+
exit 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
tmp_root=$(mktemp -d "${TMPDIR:-/tmp}/safedeps-smoke.XXXXXX")
|
|
17
|
+
cleanup() {
|
|
18
|
+
rm -rf "${tmp_root}"
|
|
19
|
+
}
|
|
20
|
+
trap cleanup EXIT
|
|
21
|
+
|
|
22
|
+
bash -n bin/safedeps
|
|
23
|
+
bash -n lib/providers/providers.sh
|
|
24
|
+
bash -n lib/ledger/ledger.sh
|
|
25
|
+
bash -n scripts/safedeps-pre-guard.sh
|
|
26
|
+
bash -n scripts/safedeps-post-verify.sh
|
|
27
|
+
bash -n scripts/safedeps-recheck-alert.sh
|
|
28
|
+
pass "bash syntax"
|
|
29
|
+
|
|
30
|
+
node --check scripts/install/install-safedeps-hooks.mjs >/dev/null
|
|
31
|
+
node --check scripts/install/install-safedeps-recheck-agent.mjs >/dev/null
|
|
32
|
+
node --check scripts/install/migrate-safedeps-state.mjs >/dev/null
|
|
33
|
+
node --check scripts/test/fixture-provider.mjs >/dev/null
|
|
34
|
+
node scripts/install/install-safedeps-recheck-agent.mjs --help >/dev/null
|
|
35
|
+
pass "node syntax"
|
|
36
|
+
|
|
37
|
+
version_json=$(HOME="${tmp_root}/home-version" SAFEDEPS_HOME="${tmp_root}/safe-version" ./bin/safedeps --json version)
|
|
38
|
+
[[ "$(jq -r '.version' <<< "${version_json}")" == "2.1.0" ]] || fail "version json is 2.1.0"
|
|
39
|
+
pass "cli version"
|
|
40
|
+
|
|
41
|
+
ledger_json=$(HOME="${tmp_root}/home-ledger" SAFEDEPS_HOME="${tmp_root}/safe-ledger" ./bin/safedeps --json ledger)
|
|
42
|
+
[[ "$(jq -r '.count' <<< "${ledger_json}")" == "0" ]] || fail "isolated ledger starts empty"
|
|
43
|
+
pass "isolated ledger"
|
|
44
|
+
|
|
45
|
+
provider_tmp="${tmp_root}/missing/provider/tmp"
|
|
46
|
+
provider_created=$(
|
|
47
|
+
TMPDIR="${provider_tmp}" \
|
|
48
|
+
SAFEDEPS_HOME="${tmp_root}/safe-provider" \
|
|
49
|
+
bash -c 'source lib/providers/providers.sh; d=$(safedeps_provider_mktemp_dir); test -d "$d"; printf "%s" "$d"'
|
|
50
|
+
)
|
|
51
|
+
[[ "${provider_created}" == "${provider_tmp%/}/safedeps-providers."* ]] || fail "provider tmp helper uses requested TMPDIR"
|
|
52
|
+
pass "provider temp dir"
|
|
53
|
+
|
|
54
|
+
project_dir="${tmp_root}/project"
|
|
55
|
+
mkdir -p "${project_dir}"
|
|
56
|
+
printf '{"dependencies":{}}\n' > "${project_dir}/package.json"
|
|
57
|
+
|
|
58
|
+
deny_json=$(
|
|
59
|
+
HOME="${tmp_root}/home-hook" SAFEDEPS_HOME="${tmp_root}/safe-hook" \
|
|
60
|
+
scripts/safedeps-pre-guard.sh <<EOF
|
|
61
|
+
{"tool_name":"Bash","tool_input":{"command":"npm install left-pad@1.3.0"},"cwd":"${project_dir}"}
|
|
62
|
+
EOF
|
|
63
|
+
)
|
|
64
|
+
[[ "$(jq -r '.hookSpecificOutput.permissionDecision' <<< "${deny_json}")" == "deny" ]] || fail "hook denies unapproved install"
|
|
65
|
+
pass "hook denies unapproved install"
|
|
66
|
+
|
|
67
|
+
mkdir -p "${tmp_root}/safe-hook-allow"
|
|
68
|
+
SAFEDEPS_HOME="${tmp_root}/safe-hook-allow" lib/ledger/ledger.sh approve npm left-pad 1.3.0 1.3.0 smoke >/dev/null
|
|
69
|
+
allow_output=$(
|
|
70
|
+
HOME="${tmp_root}/home-hook-allow" SAFEDEPS_HOME="${tmp_root}/safe-hook-allow" \
|
|
71
|
+
scripts/safedeps-pre-guard.sh <<EOF
|
|
72
|
+
{"tool_name":"Bash","tool_input":{"command":"npm install left-pad@1.3.0"},"cwd":"${project_dir}"}
|
|
73
|
+
EOF
|
|
74
|
+
)
|
|
75
|
+
[[ -z "${allow_output}" ]] || fail "hook allows approved install"
|
|
76
|
+
pass "hook allows approved install"
|
|
77
|
+
|
|
78
|
+
fixture_json="${tmp_root}/recheck-fixture.json"
|
|
79
|
+
printf '%s\n' '{"command":"re-check","checked":2,"still_clean":1,"newly_vulnerable":[],"kev_hit":[],"revoked":[]}' > "${fixture_json}"
|
|
80
|
+
SAFEDEPS_NOTIFY=0 \
|
|
81
|
+
HOME="${tmp_root}/home-recheck" \
|
|
82
|
+
SAFEDEPS_HOME="${tmp_root}/safe-recheck" \
|
|
83
|
+
SAFEDEPS_RECHECK_FIXTURE_JSON="${fixture_json}" \
|
|
84
|
+
scripts/safedeps-recheck-alert.sh
|
|
85
|
+
grep -q '"checked":2' "${tmp_root}/safe-recheck/recheck.log" || fail "re-check wrapper writes log"
|
|
86
|
+
grep -q '"provider_skipped":1' "${tmp_root}/safe-recheck/recheck-alerts.jsonl" || fail "re-check wrapper alerts on skipped provider checks"
|
|
87
|
+
pass "re-check alert wrapper"
|
|
88
|
+
|
|
89
|
+
printf 'smoke passed\n'
|