@aldegad/safedeps 2.1.1 → 2.2.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 +268 -462
- package/README.ko.md +34 -12
- package/README.md +65 -38
- package/ROADMAP.md +82 -87
- package/SKILL.md +13 -7
- package/bin/safedeps +385 -52
- package/lib/gates/audit.sh +36 -0
- package/lib/gates/hooks.sh +93 -0
- package/lib/gates/repo-profile.sh +60 -0
- package/lib/gates/scan.sh +94 -0
- package/lib/ledger/ledger.sh +94 -16
- package/lib/npm/closure.sh +115 -0
- package/lib/providers/providers.sh +244 -25
- package/package.json +1 -1
- package/scripts/install/install-safedeps-hooks.mjs +62 -23
- package/scripts/release-gates.sh +252 -0
- package/scripts/safedeps-post-verify.sh +167 -10
- package/scripts/safedeps-pre-guard.sh +270 -32
- package/scripts/test/e2e.sh +180 -4
- package/scripts/test/fixture-provider.mjs +21 -0
- package/scripts/test/smoke.sh +135 -10
package/scripts/test/smoke.sh
CHANGED
|
@@ -22,9 +22,15 @@ trap cleanup EXIT
|
|
|
22
22
|
bash -n bin/safedeps
|
|
23
23
|
bash -n lib/providers/providers.sh
|
|
24
24
|
bash -n lib/ledger/ledger.sh
|
|
25
|
+
bash -n lib/npm/closure.sh
|
|
25
26
|
bash -n scripts/safedeps-pre-guard.sh
|
|
26
27
|
bash -n scripts/safedeps-post-verify.sh
|
|
27
28
|
bash -n scripts/safedeps-recheck-alert.sh
|
|
29
|
+
bash -n scripts/release-gates.sh
|
|
30
|
+
bash -n lib/gates/repo-profile.sh
|
|
31
|
+
bash -n lib/gates/scan.sh
|
|
32
|
+
bash -n lib/gates/audit.sh
|
|
33
|
+
bash -n lib/gates/hooks.sh
|
|
28
34
|
pass "bash syntax"
|
|
29
35
|
|
|
30
36
|
node --check scripts/install/install-safedeps-hooks.mjs >/dev/null
|
|
@@ -35,7 +41,8 @@ node scripts/install/install-safedeps-recheck-agent.mjs --help >/dev/null
|
|
|
35
41
|
pass "node syntax"
|
|
36
42
|
|
|
37
43
|
version_json=$(HOME="${tmp_root}/home-version" SAFEDEPS_HOME="${tmp_root}/safe-version" ./bin/safedeps --json version)
|
|
38
|
-
|
|
44
|
+
pkg_version=$(jq -r '.version' package.json)
|
|
45
|
+
[[ "$(jq -r '.version' <<< "${version_json}")" == "${pkg_version}" ]] || fail "cli version matches package.json (${pkg_version})"
|
|
39
46
|
pass "cli version"
|
|
40
47
|
|
|
41
48
|
ledger_json=$(HOME="${tmp_root}/home-ledger" SAFEDEPS_HOME="${tmp_root}/safe-ledger" ./bin/safedeps --json ledger)
|
|
@@ -55,11 +62,28 @@ project_dir="${tmp_root}/project"
|
|
|
55
62
|
mkdir -p "${project_dir}"
|
|
56
63
|
printf '{"dependencies":{}}\n' > "${project_dir}/package.json"
|
|
57
64
|
|
|
65
|
+
run_hook_command() {
|
|
66
|
+
local home_dir="$1"
|
|
67
|
+
local safe_dir="$2"
|
|
68
|
+
local command="$3"
|
|
69
|
+
|
|
70
|
+
jq -nc --arg command "${command}" --arg cwd "${project_dir}" \
|
|
71
|
+
'{tool_name:"Bash",tool_input:{command:$command},cwd:$cwd}' |
|
|
72
|
+
HOME="${home_dir}" SAFEDEPS_HOME="${safe_dir}" scripts/safedeps-pre-guard.sh
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
run_codex_hook_command() {
|
|
76
|
+
local home_dir="$1"
|
|
77
|
+
local safe_dir="$2"
|
|
78
|
+
local command="$3"
|
|
79
|
+
|
|
80
|
+
jq -nc --arg command "${command}" --arg cwd "${project_dir}" \
|
|
81
|
+
'{tool_name:"Bash",tool_input:{command:$command},cwd:$cwd,turn_id:"turn-smoke",model:"codex-test"}' |
|
|
82
|
+
HOME="${home_dir}" SAFEDEPS_HOME="${safe_dir}" scripts/safedeps-pre-guard.sh
|
|
83
|
+
}
|
|
84
|
+
|
|
58
85
|
deny_json=$(
|
|
59
|
-
|
|
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
|
|
86
|
+
run_hook_command "${tmp_root}/home-hook" "${tmp_root}/safe-hook" "npm install left-pad@1.3.0"
|
|
63
87
|
)
|
|
64
88
|
[[ "$(jq -r '.hookSpecificOutput.permissionDecision' <<< "${deny_json}")" == "deny" ]] || fail "hook denies unapproved install"
|
|
65
89
|
pass "hook denies unapproved install"
|
|
@@ -67,13 +91,103 @@ pass "hook denies unapproved install"
|
|
|
67
91
|
mkdir -p "${tmp_root}/safe-hook-allow"
|
|
68
92
|
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
93
|
allow_output=$(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
run_hook_command "${tmp_root}/home-hook-allow" "${tmp_root}/safe-hook-allow" "npm install left-pad@1.3.0"
|
|
95
|
+
)
|
|
96
|
+
[[ "$(jq -r '.hookSpecificOutput.permissionDecision' <<< "${allow_output}")" == "allow" ]] || fail "hook emits Claude allow decision for approved install"
|
|
97
|
+
[[ "$(jq -r '.hookSpecificOutput.updatedInput.command' <<< "${allow_output}")" == "npm install left-pad@1.3.0 --ignore-scripts" ]] || fail "hook injects --ignore-scripts for Claude npm install"
|
|
98
|
+
allow_sid=$(jq -r '.snapshot_id' "${tmp_root}/safe-hook-allow/current_state")
|
|
99
|
+
jq -e '.ignore_scripts_injected == true' "${tmp_root}/safe-hook-allow/snapshots/${allow_sid}_meta.json" >/dev/null || fail "hook records injected meta flag"
|
|
100
|
+
pass "hook injects --ignore-scripts for Claude approved install"
|
|
101
|
+
|
|
102
|
+
mkdir -p "${tmp_root}/safe-hook-codex"
|
|
103
|
+
SAFEDEPS_HOME="${tmp_root}/safe-hook-codex" lib/ledger/ledger.sh approve npm left-pad 1.3.0 1.3.0 smoke >/dev/null
|
|
104
|
+
codex_allow_output=$(
|
|
105
|
+
run_codex_hook_command "${tmp_root}/home-hook-codex" "${tmp_root}/safe-hook-codex" "npm install left-pad@1.3.0"
|
|
106
|
+
)
|
|
107
|
+
[[ -z "${codex_allow_output}" ]] || fail "hook keeps Codex approved install as plain allow"
|
|
108
|
+
codex_sid=$(jq -r '.snapshot_id' "${tmp_root}/safe-hook-codex/current_state")
|
|
109
|
+
jq -e '.ignore_scripts_injected == false' "${tmp_root}/safe-hook-codex/snapshots/${codex_sid}_meta.json" >/dev/null || fail "hook does not record injected meta flag for Codex"
|
|
110
|
+
pass "hook keeps Codex approved install as plain allow"
|
|
111
|
+
|
|
112
|
+
for inert_skip_cmd in "npm view left-pad" "npm run build" "npm --version"; do
|
|
113
|
+
inert_skip_output=$(run_hook_command "${tmp_root}/home-inert-skip" "${tmp_root}/safe-inert-skip" "${inert_skip_cmd}")
|
|
114
|
+
[[ -z "${inert_skip_output}" ]] || fail "hook does not inject non-install command: ${inert_skip_cmd}"
|
|
115
|
+
done
|
|
116
|
+
pass "hook does not inject npm non-install commands"
|
|
117
|
+
|
|
118
|
+
mkdir -p "${tmp_root}/safe-hook-ignore-scripts"
|
|
119
|
+
SAFEDEPS_HOME="${tmp_root}/safe-hook-ignore-scripts" lib/ledger/ledger.sh approve npm left-pad 1.3.0 1.3.0 smoke >/dev/null
|
|
120
|
+
ignore_scripts_output=$(
|
|
121
|
+
run_hook_command "${tmp_root}/home-hook-ignore-scripts" "${tmp_root}/safe-hook-ignore-scripts" "npm install left-pad@1.3.0 --ignore-scripts"
|
|
122
|
+
)
|
|
123
|
+
[[ -z "${ignore_scripts_output}" ]] || fail "hook does not duplicate --ignore-scripts"
|
|
124
|
+
ignore_sid=$(jq -r '.snapshot_id' "${tmp_root}/safe-hook-ignore-scripts/current_state")
|
|
125
|
+
jq -e '.ignore_scripts_injected == false' "${tmp_root}/safe-hook-ignore-scripts/snapshots/${ignore_sid}_meta.json" >/dev/null || fail "hook does not record injected meta flag when flag already exists"
|
|
126
|
+
pass "hook does not duplicate --ignore-scripts"
|
|
127
|
+
|
|
128
|
+
# Regression: `npx <tool> <args>` runs an already-installed binary. Arguments to
|
|
129
|
+
# the tool (e.g. an email) must NOT be misread as a pkg@spec install and denied.
|
|
130
|
+
npx_runner_output=$(
|
|
131
|
+
run_hook_command "${tmp_root}/home-npx-run" "${tmp_root}/safe-npx-run" "npx wrangler secret put ORIGIN_SHARED_SECRET --name pqc-auth-gateway dev1@block-s.io"
|
|
132
|
+
)
|
|
133
|
+
[[ -z "${npx_runner_output}" ]] || fail "hook allows npx tool run with @-bearing args"
|
|
134
|
+
pass "hook allows npx tool run with @-bearing args"
|
|
135
|
+
|
|
136
|
+
# Regression: a genuine install chained with an npx tool run must STILL be gated
|
|
137
|
+
# on the real package — and must not be polluted by the npx arg email.
|
|
138
|
+
mixed_output=$(
|
|
139
|
+
run_hook_command "${tmp_root}/home-mixed" "${tmp_root}/safe-mixed" "npm install evil-pkg@9.9.9 && npx wrangler secret put X dev1@block-s.io"
|
|
140
|
+
)
|
|
141
|
+
[[ "$(jq -r '.hookSpecificOutput.permissionDecision' <<< "${mixed_output}")" == "deny" ]] || fail "hook gates real install chained with npx run"
|
|
142
|
+
reason=$(jq -r '.hookSpecificOutput.permissionDecisionReason' <<< "${mixed_output}")
|
|
143
|
+
grep -q 'evil-pkg@9.9.9' <<< "${reason}" || fail "deny reason names the real package"
|
|
144
|
+
[[ "${reason}" != *"dev1@block-s.io"* ]] || fail "deny reason must not name the email arg"
|
|
145
|
+
pass "hook gates real install chained with npx run (email not polluted)"
|
|
146
|
+
|
|
147
|
+
echo_output=$(
|
|
148
|
+
run_hook_command "${tmp_root}/home-echo" "${tmp_root}/safe-echo" "echo \"npm install evil-pkg@9.9.9\""
|
|
149
|
+
)
|
|
150
|
+
[[ -z "${echo_output}" ]] || fail "hook ignores quoted echo text"
|
|
151
|
+
heredoc_output=$(
|
|
152
|
+
run_hook_command "${tmp_root}/home-heredoc" "${tmp_root}/safe-heredoc" $'cat <<'\''EOF'\''\nnpm install evil-pkg@9.9.9\nEOF'
|
|
153
|
+
)
|
|
154
|
+
[[ -z "${heredoc_output}" ]] || fail "hook ignores heredoc body text"
|
|
155
|
+
pass "hook ignores echo/heredoc text"
|
|
156
|
+
|
|
157
|
+
bypass_cases=(
|
|
158
|
+
"/usr/bin/npm install evil@1.2.3"
|
|
159
|
+
"bash -lc \"npm install evil@1.2.3\""
|
|
160
|
+
"env npm install evil@1.2.3"
|
|
161
|
+
"command npm install evil@1.2.3"
|
|
162
|
+
"npm --prefix sub install evil@1.2.3"
|
|
163
|
+
"pip install requests==2.31.0"
|
|
164
|
+
"gem install rails -v 7.1.0"
|
|
165
|
+
"cargo add serde --vers 1.0.0"
|
|
166
|
+
"dotnet add package X --version 1.0.0"
|
|
167
|
+
)
|
|
168
|
+
for bypass_cmd in "${bypass_cases[@]}"; do
|
|
169
|
+
bypass_output=$(run_hook_command "${tmp_root}/home-bypass" "${tmp_root}/safe-bypass" "${bypass_cmd}")
|
|
170
|
+
[[ "$(jq -r '.hookSpecificOutput.permissionDecision' <<< "${bypass_output}")" == "deny" ]] || fail "hook denies bypass: ${bypass_cmd}"
|
|
171
|
+
done
|
|
172
|
+
pass "hook denies install bypass forms"
|
|
173
|
+
|
|
174
|
+
tamper_safe="${tmp_root}/safe-tamper"
|
|
175
|
+
tamper_home="${tmp_root}/home-tamper"
|
|
176
|
+
SAFEDEPS_HOME="${tamper_safe}" lib/ledger/ledger.sh approve npm ledger-tamper 1.0.0 1.0.0 smoke >/dev/null
|
|
177
|
+
tamper_pre=$(run_hook_command "${tamper_home}" "${tamper_safe}" "npm install ledger-tamper@1.0.0")
|
|
178
|
+
[[ "$(jq -r '.hookSpecificOutput.permissionDecision' <<< "${tamper_pre}")" == "allow" ]] || fail "tamper fixture pre hook allows approved install"
|
|
179
|
+
mkdir -p "${project_dir}/node_modules/ledger-tamper"
|
|
180
|
+
jq '.dependencies["ledger-tamper"]="1.0.0"' "${project_dir}/package.json" > "${project_dir}/package.json.tmp"
|
|
181
|
+
mv "${project_dir}/package.json.tmp" "${project_dir}/package.json"
|
|
182
|
+
cat > "${project_dir}/node_modules/ledger-tamper/package.json" <<'EOF'
|
|
183
|
+
{"name":"ledger-tamper","version":"1.0.0","scripts":{"postinstall":"node -e \"require('fs').writeFileSync(process.env.HOME + '/.safedeps/approved-specs/evil.json', '{}')\""}}
|
|
73
184
|
EOF
|
|
185
|
+
tamper_post=$(
|
|
186
|
+
jq -nc '{tool_name:"Bash",tool_input:{command:"npm install ledger-tamper@1.0.0"}}' |
|
|
187
|
+
HOME="${tamper_home}" SAFEDEPS_HOME="${tamper_safe}" scripts/safedeps-post-verify.sh
|
|
74
188
|
)
|
|
75
|
-
|
|
76
|
-
pass "hook
|
|
189
|
+
grep -q '의심스러운 패키지 변경 감지' <<< "${tamper_post}" || fail "post hook reorgs safedeps ledger tamper script"
|
|
190
|
+
pass "post hook reorgs safedeps ledger tamper script"
|
|
77
191
|
|
|
78
192
|
fixture_json="${tmp_root}/recheck-fixture.json"
|
|
79
193
|
printf '%s\n' '{"command":"re-check","checked":2,"still_clean":1,"newly_vulnerable":[],"kev_hit":[],"revoked":[]}' > "${fixture_json}"
|
|
@@ -86,4 +200,15 @@ grep -q '"checked":2' "${tmp_root}/safe-recheck/recheck.log" || fail "re-check w
|
|
|
86
200
|
grep -q '"provider_skipped":1' "${tmp_root}/safe-recheck/recheck-alerts.jsonl" || fail "re-check wrapper alerts on skipped provider checks"
|
|
87
201
|
pass "re-check alert wrapper"
|
|
88
202
|
|
|
203
|
+
# Release-time lane (absorbed from security-release-gates): commands must be
|
|
204
|
+
# registered and resolve their gate scripts.
|
|
205
|
+
gates_help=$(HOME="${tmp_root}/home-gates" SAFEDEPS_HOME="${tmp_root}/safe-gates" ./bin/safedeps help)
|
|
206
|
+
for gate_cmd in "gates" "scan secrets" "audit" "hooks"; do
|
|
207
|
+
grep -q "${gate_cmd}" <<< "${gates_help}" || fail "release-time command listed in help: ${gate_cmd}"
|
|
208
|
+
done
|
|
209
|
+
for gate_script in scripts/release-gates.sh lib/gates/repo-profile.sh lib/gates/scan.sh lib/gates/audit.sh lib/gates/hooks.sh; do
|
|
210
|
+
[[ -f "${gate_script}" ]] || fail "release-time gate script present: ${gate_script}"
|
|
211
|
+
done
|
|
212
|
+
pass "release-time gate commands registered"
|
|
213
|
+
|
|
89
214
|
printf 'smoke passed\n'
|