@aldegad/safedeps 2.5.1 → 2.6.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/README.ko.md CHANGED
@@ -22,6 +22,8 @@ cd "$(npm root -g)/@aldegad/safedeps" && node scripts/install/install-safedeps-h
22
22
 
23
23
  > `safedeps` 는 CLI 명령어이고, npm 패키지는 **`@aldegad/safedeps`** 다 — npm 의 unscoped `safedeps` 는 무관한 남의 패키지. 전체 skill 소스 트리를 원하면 [설치](#설치) 참고.
24
24
 
25
+ ![safedeps 가 취약한 install 을 보류하고, 패치 버전은 통과시킨다](assets/demo.gif)
26
+
25
27
  *Detailed reference → [README.md](./README.md) (영문, SSoT)*
26
28
 
27
29
  ---
package/README.md CHANGED
@@ -22,7 +22,7 @@ cd "$(npm root -g)/@aldegad/safedeps" && node scripts/install/install-safedeps-h
22
22
 
23
23
  > `safedeps` is the CLI command; the npm package is **`@aldegad/safedeps`** — the unscoped `safedeps` on npm is an unrelated package. Prefer the full skill source tree? See [Installation](#installation).
24
24
 
25
- <!-- TODO(demo): add a 15-20s asciinema/VHS recording of safedeps catching a malicious install live (inert -> reorg). Highest-leverage conversion asset per launch review. -->
25
+ ![safedeps withholds a vulnerable install, then clears the patched version](assets/demo.gif)
26
26
 
27
27
  ## Distribution Model
28
28
 
package/bin/safedeps CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  set -euo pipefail
9
9
 
10
- SAFEDEPS_VERSION="2.5.1"
10
+ SAFEDEPS_VERSION="2.6.0"
11
11
 
12
12
  # ---- repo / lib bootstrap ----------------------------------------------------
13
13
 
@@ -340,7 +340,7 @@ sf_cmd_check_npm_full_closure() {
340
340
  evidence_file=$(sf_mktemp_evidence)
341
341
  transitive_file=$(sf_closure_temp_file)
342
342
 
343
- sf_spinner_start "npm closure 해석 중 (${pkg}@${version})"
343
+ sf_spinner_start "resolving npm closure (${pkg}@${version})"
344
344
  if ! sf_npm_closure_for_spec "${pkg}" "${version}" "${closure_file}"; then
345
345
  sf_spinner_stop
346
346
  rm -f "${closure_file}" "${batch_file}" "${evidence_file}" "${transitive_file}"
@@ -352,10 +352,10 @@ sf_cmd_check_npm_full_closure() {
352
352
  fi
353
353
  sf_spinner_stop
354
354
 
355
- sf_spinner_start "closure 취약점 batch 조회 중 (OSV / KEV)"
355
+ sf_spinner_start "batch-checking closure for advisories (OSV / KEV)"
356
356
  if ! sf_npm_batch_check_closure "${closure_file}" "${batch_file}"; then
357
357
  sf_spinner_stop
358
- sf_err "OSV batch 응답 없음 — fail-closed (closure cache miss + 라이브 실패)"
358
+ sf_err "no OSV batch response — fail-closed (closure cache miss + live query failed)"
359
359
  sf_advisory_log "check fail-closed npm-closure package=${pkg} version=${version}"
360
360
  rm -f "${closure_file}" "${batch_file}" "${evidence_file}" "${transitive_file}"
361
361
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
@@ -377,7 +377,7 @@ sf_cmd_check_npm_full_closure() {
377
377
  if [[ "${kev_count}" -gt 0 ]]; then
378
378
  local kev_summary
379
379
  kev_summary=$(jq -r '[.[] | select(.status == "hard_block") | "\(.package)@\(.version)" + (if .direct then " (direct)" else " (transitive)" end)] | join(", ")' "${batch_file}")
380
- sf_err "KEV 매칭 closure 감지 설치 차단: ${kev_summary}"
380
+ sf_err "KEV-matched package in closure — install blocked: ${kev_summary}"
381
381
  sf_advisory_log "check block(KEV closure) package=${pkg} version=${version} affected=${kev_summary}"
382
382
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
383
383
  sf_npm_emit_closure_block_json "kev_hard_block" "${ecosystem}" "${pkg}" "${range}" "${version}" "${batch_file}"
@@ -402,7 +402,7 @@ sf_cmd_check_npm_full_closure() {
402
402
  fi
403
403
 
404
404
  for patched_version in "${patched_versions[@]+${patched_versions[@]}}"; do
405
- sf_warn "${pkg}@${version} direct 취약후보 ${patched_version} closure 재조회 중."
405
+ sf_warn "${pkg}@${version} direct vulnerablere-checking closure for candidate ${patched_version}."
406
406
  if ! sf_npm_closure_for_spec "${pkg}" "${patched_version}" "${closure_file}"; then
407
407
  continue
408
408
  fi
@@ -422,7 +422,7 @@ sf_cmd_check_npm_full_closure() {
422
422
  local hash; hash=$(jq -r '.hash' <<< "${spec_json}")
423
423
  local expires_at; expires_at=$(jq -r '.expires_at' <<< "${spec_json}")
424
424
  local transitive_count; transitive_count=$(jq 'length' "${transitive_file}")
425
- sf_ok "${pkg}@${patched_version} full closure 승인 (transitive ${transitive_count}, until ${expires_at})"
425
+ sf_ok "${pkg}@${patched_version} full closure cleared (transitive ${transitive_count}, until ${expires_at})"
426
426
  sf_info "ledger: ${hash}"
427
427
  sf_advisory_log "check approve(patched closure) package=${pkg} version=${patched_version} hash=${hash} transitive=${transitive_count} prev_version=${version}"
428
428
  rm -f "${direct_evidence}" "${closure_file}" "${batch_file}" "${evidence_file}" "${transitive_file}"
@@ -443,7 +443,7 @@ sf_cmd_check_npm_full_closure() {
443
443
 
444
444
  local affected_summary
445
445
  affected_summary=$(jq -r '[.[] | select(.status == "vulnerable") | "\(.package)@\(.version)" + (if .direct then " (direct)" else " (transitive)" end)] | join(", ")' "${batch_file}")
446
- sf_warn "closure 취약 패키지 감지 승인 보류: ${affected_summary}"
446
+ sf_warn "vulnerable package in closureapproval withheld: ${affected_summary}"
447
447
  sf_advisory_log "check block(vulnerable closure) package=${pkg} version=${version} affected=${affected_summary}"
448
448
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
449
449
  sf_npm_emit_closure_block_json "${block_result}" "${ecosystem}" "${pkg}" "${range}" "${version}" "${batch_file}"
@@ -459,7 +459,7 @@ sf_cmd_check_npm_full_closure() {
459
459
  local hash; hash=$(jq -r '.hash' <<< "${spec_json}")
460
460
  local expires_at; expires_at=$(jq -r '.expires_at' <<< "${spec_json}")
461
461
  local transitive_count; transitive_count=$(jq 'length' "${transitive_file}")
462
- sf_ok "${pkg}@${version} full closure 승인 (transitive ${transitive_count}, until ${expires_at})"
462
+ sf_ok "${pkg}@${version} full closure cleared (transitive ${transitive_count}, until ${expires_at})"
463
463
  sf_info "ledger: ${hash}"
464
464
  sf_advisory_log "check approve(clean closure) package=${pkg} version=${version} hash=${hash} transitive=${transitive_count}"
465
465
  rm -f "${closure_file}" "${batch_file}" "${evidence_file}" "${transitive_file}"
@@ -506,10 +506,10 @@ cmd_check() {
506
506
  range=$(sf_parse_pkg_spec "${pkg_spec}" | sed -n '2p')
507
507
 
508
508
  local version
509
- sf_spinner_start "버전 해석 (${pkg}@${range})"
509
+ sf_spinner_start "resolving version (${pkg}@${range})"
510
510
  if ! version=$(sf_resolve_version "${ecosystem}" "${pkg}" "${range}"); then
511
511
  sf_spinner_stop
512
- sf_err "버전 해석 실패: ${pkg}@${range}"
512
+ sf_err "version resolution failed: ${pkg}@${range}"
513
513
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
514
514
  jq -nc --arg ecosystem "${ecosystem}" --arg package "${pkg}" --arg range "${range}" \
515
515
  '{command:"check", ecosystem:$ecosystem, package:$package, input_range:$range, result:"error", error:"version_resolution_failed"}'
@@ -526,7 +526,7 @@ cmd_check() {
526
526
  hash=$(jq -r '.hash' <<< "${ledger_check}")
527
527
  approved_at=$(jq -r '.spec.approved_at // "n/a"' <<< "${ledger_check}")
528
528
  expires_at=$(jq -r '.spec.expires_at // "n/a"' <<< "${ledger_check}")
529
- sf_ok "${pkg}@${version} 이미 승인됨 (until ${expires_at})"
529
+ sf_ok "${pkg}@${version} already approved (until ${expires_at})"
530
530
  sf_info "ledger: ${hash}"
531
531
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
532
532
  jq -nc \
@@ -551,10 +551,10 @@ cmd_check() {
551
551
 
552
552
  # Provider query (canonical truth = OSV; KEV overlay; GHSA enrichment)
553
553
  local provider_json
554
- sf_spinner_start "취약점 조회 (OSV / KEV / GHSA)"
554
+ sf_spinner_start "checking advisories (OSV / KEV / GHSA)"
555
555
  if ! provider_json=$(safedeps_providers_query "${ecosystem}" "${pkg}" "${version}"); then
556
556
  sf_spinner_stop
557
- sf_err "OSV primary 응답 없음 — fail-closed (cache miss + 라이브 실패)"
557
+ sf_err "no OSV primary response — fail-closed (cache miss + live query failed)"
558
558
  sf_advisory_log "check fail-closed ecosystem=${ecosystem} package=${pkg} version=${version}"
559
559
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
560
560
  jq -nc \
@@ -577,11 +577,11 @@ cmd_check() {
577
577
 
578
578
  case "${status}" in
579
579
  hard_block)
580
- sf_err "KEV 매칭 — ${pkg}@${version} 실제 야생에서 exploit 확인됨. 설치 차단."
580
+ sf_err "KEV match — ${pkg}@${version} is exploited in the wild. install blocked."
581
581
  local kev_cves
582
582
  kev_cves=$(jq -r '[.kev.matches[]?.cveID] | unique | join(", ")' <<< "${provider_json}")
583
- [[ -n "${kev_cves}" ]] && sf_info "관련 CVE: ${kev_cves}"
584
- sf_warn "대체 모듈을 검토하세요. spec ledger 승인되지 않습니다."
583
+ [[ -n "${kev_cves}" ]] && sf_info "related CVEs: ${kev_cves}"
584
+ sf_warn "consider an alternative package; this spec is not approved in the ledger."
585
585
  sf_advisory_log "check block(KEV) ecosystem=${ecosystem} package=${pkg} version=${version} cves=${kev_cves}"
586
586
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
587
587
  jq -c \
@@ -610,11 +610,11 @@ cmd_check() {
610
610
 
611
611
  for patched_version in "${patched_versions[@]}"; do
612
612
  last_patched="${patched_version}"
613
- sf_warn "${pkg}@${version} ${vuln_count} CVE — 후보 ${patched_version} 재조회 중."
614
- sf_spinner_start "${patched_version} 재조회 중"
613
+ sf_warn "${pkg}@${version} has ${vuln_count} CVE(s)re-checking candidate ${patched_version}."
614
+ sf_spinner_start "re-checking ${patched_version}"
615
615
  if ! narrow_json=$(safedeps_providers_query "${ecosystem}" "${pkg}" "${patched_version}"); then
616
616
  sf_spinner_stop
617
- sf_err "${pkg}@${patched_version} 재조회 실패 — fail-closed"
617
+ sf_err "${pkg}@${patched_version} re-check failed — fail-closed"
618
618
  rm -f "${tmp_evidence}"
619
619
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
620
620
  jq -nc \
@@ -630,7 +630,7 @@ cmd_check() {
630
630
  narrow_status=$(jq -r '.status' <<< "${narrow_json}")
631
631
  last_status="${narrow_status}"
632
632
  if [[ "${narrow_status}" != "clean" ]]; then
633
- sf_warn "패치 후보 ${patched_version} 깨끗하지 않음 (status=${narrow_status}); 다음 후보를 확인합니다."
633
+ sf_warn "patch candidate ${patched_version} is not clean either (status=${narrow_status}); trying the next candidate."
634
634
  continue
635
635
  fi
636
636
 
@@ -644,7 +644,7 @@ cmd_check() {
644
644
  rm -f "${narrow_evidence}" "${tmp_evidence}"
645
645
  local hash; hash=$(jq -r '.hash' <<< "${spec_json}")
646
646
  local expires_at; expires_at=$(jq -r '.expires_at' <<< "${spec_json}")
647
- sf_ok "${pkg}@${patched_version} 승인 (until ${expires_at})"
647
+ sf_ok "${pkg}@${patched_version} approved (until ${expires_at})"
648
648
  sf_info "ledger: ${hash}"
649
649
  sf_advisory_log "check approve(patched) ecosystem=${ecosystem} package=${pkg} version=${patched_version} hash=${hash} prev_version=${version}"
650
650
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
@@ -658,7 +658,7 @@ cmd_check() {
658
658
  return 0
659
659
  done
660
660
 
661
- sf_err "패치 후보를 모두 재조회했지만 clean 후보가 없음 (last=${last_patched}, status=${last_status})"
661
+ sf_err "re-checked all patch candidates but none are clean (last=${last_patched}, status=${last_status})"
662
662
  rm -f "${tmp_evidence}"
663
663
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
664
664
  jq -nc \
@@ -669,7 +669,7 @@ cmd_check() {
669
669
  fi
670
670
  return 2
671
671
  else
672
- sf_warn "${pkg}@${version} ${vuln_count} CVE — 사용 가능한 patch 없음. 승인 보류."
672
+ sf_warn "${pkg}@${version} has ${vuln_count} CVE(s)no patch available. approval withheld."
673
673
  sf_advisory_log "check warn(no-patch) ecosystem=${ecosystem} package=${pkg} version=${version} vulns=${vuln_count}"
674
674
  rm -f "${tmp_evidence}"
675
675
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
@@ -688,7 +688,7 @@ cmd_check() {
688
688
  rm -f "${tmp_evidence}"
689
689
  local hash; hash=$(jq -r '.hash' <<< "${spec_json}")
690
690
  local expires_at; expires_at=$(jq -r '.expires_at' <<< "${spec_json}")
691
- sf_ok "${pkg}@${version} 승인 (until ${expires_at})"
691
+ sf_ok "${pkg}@${version} approved (until ${expires_at})"
692
692
  sf_info "ledger: ${hash}"
693
693
  sf_advisory_log "check approve(clean) ecosystem=${ecosystem} package=${pkg} version=${version} hash=${hash}"
694
694
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
@@ -702,7 +702,7 @@ cmd_check() {
702
702
  ;;
703
703
 
704
704
  *)
705
- sf_err "예상치 못한 provider status: ${status}"
705
+ sf_err "unexpected provider status: ${status}"
706
706
  rm -f "${tmp_evidence}"
707
707
  return 4
708
708
  ;;
@@ -757,7 +757,7 @@ cmd_ledger() {
757
757
  fi
758
758
 
759
759
  if [[ ${#entries[@]} -eq 0 ]]; then
760
- sf_info "approved-specs 비어있음 (${SAFEDEPS_LEDGER_DIR})"
760
+ sf_info "approved-specs is empty (${SAFEDEPS_LEDGER_DIR})"
761
761
  return 0
762
762
  fi
763
763
 
@@ -830,7 +830,7 @@ cmd_revoke() {
830
830
  local target_file=""
831
831
  if [[ "${arg1}" == sha256:* ]]; then
832
832
  target_file=$(safedeps_ledger_path_for_hash "${arg1}")
833
- [[ -f "${target_file}" ]] || { sf_err "ledger entry 없음: ${arg1}"; return 1; }
833
+ [[ -f "${target_file}" ]] || { sf_err "no ledger entry: ${arg1}"; return 1; }
834
834
  else
835
835
  # one or two args. Two = ecosystem + pkg@version. One = pkg@version (scan).
836
836
  if [[ -n "${arg2}" ]]; then
@@ -839,7 +839,7 @@ cmd_revoke() {
839
839
  version=$(sf_parse_pkg_spec "${arg2}" | sed -n '2p')
840
840
  [[ -n "${version}" ]] || { sf_eprintf "safedeps: revoke needs pkg@version, got '${arg2}'"; return 4; }
841
841
  target_file=$(safedeps_ledger_path "${arg1}" "${pkg}" "${version}")
842
- [[ -f "${target_file}" ]] || { sf_err "ledger entry 없음: ${arg1} ${pkg}@${version}"; return 1; }
842
+ [[ -f "${target_file}" ]] || { sf_err "no ledger entry: ${arg1} ${pkg}@${version}"; return 1; }
843
843
  else
844
844
  local pkg version
845
845
  pkg=$(sf_parse_pkg_spec "${arg1}" | sed -n '1p')
@@ -855,9 +855,9 @@ cmd_revoke() {
855
855
  fi
856
856
  done < <(find "${SAFEDEPS_LEDGER_DIR}" -maxdepth 1 -name '*.json' -type f -print0 2>/dev/null)
857
857
  case "${#matches[@]}" in
858
- 0) sf_err "ledger entry 없음: ${pkg}@${version}"; return 1 ;;
858
+ 0) sf_err "no ledger entry: ${pkg}@${version}"; return 1 ;;
859
859
  1) target_file="${matches[0]}" ;;
860
- *) sf_err "${pkg}@${version} 여러 ecosystem 에서 매칭됨 ecosystem 명시하세요"; return 4 ;;
860
+ *) sf_err "${pkg}@${version} matched in multiple ecosystemsspecify the ecosystem"; return 4 ;;
861
861
  esac
862
862
  fi
863
863
  fi
@@ -870,7 +870,7 @@ cmd_revoke() {
870
870
  local revoked_json
871
871
  revoked_json=$(safedeps_ledger_revoke "${ecosystem}" "${pkg}" "${version}" "${reason}")
872
872
  sf_advisory_log "revoke ecosystem=${ecosystem} package=${pkg} version=${version} reason=${reason}"
873
- sf_ok "취소: ${ecosystem} ${pkg}@${version}"
873
+ sf_ok "revoked: ${ecosystem} ${pkg}@${version}"
874
874
  sf_info "reason: ${reason}"
875
875
 
876
876
  if [[ "${SAFEDEPS_JSON_MODE}" -eq 1 ]]; then
@@ -909,17 +909,17 @@ cmd_recheck() {
909
909
  hash=$(jq -r '.hash // ""' "${f}")
910
910
  [[ -n "${revoked_at}" ]] && continue
911
911
  if [[ -f "${SAFEDEPS_ADVISORY_LOG}" ]] && ! sf_ledger_has_approval_provenance "${hash}" "${ecosystem}" "${pkg}" "${version}"; then
912
- sf_warn " provenance 없음 위조 의심 flag (revoke 안 함)"
912
+ sf_warn " no provenance — flagged as suspected forgery (not revoked)"
913
913
  suspected_forgery_arr=$(jq -c \
914
914
  --arg ecosystem "${ecosystem}" --arg package "${pkg}" --arg version "${version}" --arg hash "${hash}" \
915
915
  '. + [{ecosystem:$ecosystem, package:$package, version:$version, hash:$hash, reason:"missing_advisory_log_approval"}]' <<< "${suspected_forgery_arr}")
916
916
  fi
917
917
  checked=$(( checked + 1 ))
918
918
 
919
- sf_info "재검증 ${ecosystem} ${pkg}@${version}"
919
+ sf_info "re-checking ${ecosystem} ${pkg}@${version}"
920
920
  local pj
921
921
  if ! pj=$(safedeps_providers_query "${ecosystem}" "${pkg}" "${version}" 2>/dev/null); then
922
- sf_warn " provider 응답 없음 skip"
922
+ sf_warn " no provider responseskipped"
923
923
  continue
924
924
  fi
925
925
  local s; s=$(jq -r '.status' <<< "${pj}")
@@ -932,12 +932,12 @@ cmd_recheck() {
932
932
  safedeps_ledger_revoke "${ecosystem}" "${pkg}" "${version}" "${reason}" >/dev/null
933
933
  sf_advisory_log "re-check revoke ecosystem=${ecosystem} package=${pkg} version=${version} status=${s}"
934
934
  if [[ "${s}" == "hard_block" ]]; then
935
- sf_err " KEV 매칭 revoke"
935
+ sf_err " KEV match -> revoked"
936
936
  kev_hit_arr=$(jq -c \
937
937
  --arg ecosystem "${ecosystem}" --arg package "${pkg}" --arg version "${version}" \
938
938
  '. + [{ecosystem:$ecosystem, package:$package, version:$version, status:"hard_block"}]' <<< "${kev_hit_arr}")
939
939
  else
940
- sf_warn " CVE 매치 revoke"
940
+ sf_warn " new CVE match -> revoked"
941
941
  newly_vuln_arr=$(jq -c \
942
942
  --arg ecosystem "${ecosystem}" --arg package "${pkg}" --arg version "${version}" \
943
943
  '. + [{ecosystem:$ecosystem, package:$package, version:$version, status:"vulnerable"}]' <<< "${newly_vuln_arr}")
@@ -961,15 +961,15 @@ cmd_recheck() {
961
961
  return 0
962
962
  fi
963
963
 
964
- sf_info "검증 완료: ${checked} 개 중 ${still_clean} clean"
964
+ sf_info "re-check complete: ${still_clean}/${checked} still clean"
965
965
  local nv kv
966
966
  nv=$(jq -r 'length' <<< "${newly_vuln_arr}")
967
967
  kv=$(jq -r 'length' <<< "${kev_hit_arr}")
968
968
  local fg
969
969
  fg=$(jq -r 'length' <<< "${suspected_forgery_arr}")
970
- [[ "${nv}" -gt 0 ]] && sf_warn " CVE 매치로 ${nv} revoke"
971
- [[ "${kv}" -gt 0 ]] && sf_err "KEV 매치로 ${kv} revoke"
972
- [[ "${fg}" -gt 0 ]] && sf_warn "approval provenance 없는 ledger entry ${fg} flag"
970
+ [[ "${nv}" -gt 0 ]] && sf_warn "revoked ${nv} for new CVE match"
971
+ [[ "${kv}" -gt 0 ]] && sf_err "revoked ${kv} for KEV match"
972
+ [[ "${fg}" -gt 0 ]] && sf_warn "flagged ${fg} ledger entries with no approval provenance"
973
973
  }
974
974
 
975
975
  # ---- migrate -----------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aldegad/safedeps",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "Dependency install safety gate with OSV-backed advisory checks, approved-spec ledger enforcement, and reorg rollback hooks",
5
5
  "main": "bin/safedeps",
6
6
  "bin": {
@@ -781,12 +781,12 @@ LOG_EOF
781
781
  --arg log_path "${GUARD_DIR}/reorg.log" \
782
782
  '{
783
783
  systemMessage: (
784
- "safedeps: 의심스러운 패키지 변경 감지, 마지막으로 confirmed 안전 스냅샷으로 롤백했습니다.\n\n" +
785
- "감지된 문제:\n" + $reasons + "\n\n" +
786
- "롤백 기준 스냅샷: " + $rollback_snapshot + "\n" +
787
- "롤백된 파일: " + $rolled_back +
788
- (if $warnings == "" then "" else "\n\n추가 경고:\n" + $warnings end) +
789
- "\n\n상세 로그: " + $log_path
784
+ "safedeps: suspicious dependency change detected rolled back to the last confirmed safe snapshot.\n\n" +
785
+ "Detected problems:\n" + $reasons + "\n\n" +
786
+ "Rollback snapshot: " + $rollback_snapshot + "\n" +
787
+ "Rolled-back files: " + $rolled_back +
788
+ (if $warnings == "" then "" else "\n\nAdditional warnings:\n" + $warnings end) +
789
+ "\n\nDetails log: " + $log_path
790
790
  )
791
791
  }'
792
792
  exit 0
@@ -230,7 +230,7 @@ revert_post=$(
230
230
  {"tool_name":"Bash","tool_input":{"command":"npm install fixture-parent@1.0.0"},"cwd":"${revert_project}"}
231
231
  EOF
232
232
  )
233
- grep -q '의심스러운 패키지 변경 감지' <<< "${revert_post}" || fail "reorg fires on a tampered lockfile"
233
+ grep -q 'suspicious dependency change detected' <<< "${revert_post}" || fail "reorg fires on a tampered lockfile"
234
234
  cmp -s "${revert_project}/package-lock.json" "${tmp_root}/revert-safe-lock.json" || fail "reorg restores the exact safe lockfile content on disk"
235
235
  pass "reorg reverts a tampered lockfile to safe content on disk"
236
236
 
@@ -267,7 +267,7 @@ missing_post=$(
267
267
  {"tool_name":"Bash","tool_input":{"command":"npm install fixture-parent@1.0.0"},"cwd":"${missing_project}"}
268
268
  EOF
269
269
  )
270
- grep -q '의심스러운 패키지 변경 감지' <<< "${missing_post}" || fail "post hook reorgs unapproved transitive package"
270
+ grep -q 'suspicious dependency change detected' <<< "${missing_post}" || fail "post hook reorgs unapproved transitive package"
271
271
  grep -q 'fixture-child@1.0.0' <<< "${missing_post}" || fail "post hook names unapproved transitive package"
272
272
  # Not just the message — the unapproved transitive must be gone from the on-disk
273
273
  # lockfile. (Reorg removes the tampered lockfile; a no-network reinstall may recreate
@@ -262,7 +262,7 @@ tamper_post=$(
262
262
  jq -nc --arg cwd "${project_dir}" '{tool_name:"Bash",tool_input:{command:"npm install ledger-tamper@1.0.0"},cwd:$cwd}' |
263
263
  HOME="${tamper_home}" SAFEDEPS_HOME="${tamper_safe}" scripts/safedeps-post-verify.sh
264
264
  )
265
- grep -q '의심스러운 패키지 변경 감지' <<< "${tamper_post}" || fail "post hook reorgs safedeps ledger tamper script"
265
+ grep -q 'suspicious dependency change detected' <<< "${tamper_post}" || fail "post hook reorgs safedeps ledger tamper script"
266
266
  pass "post hook reorgs safedeps ledger tamper script"
267
267
 
268
268
  fixture_json="${tmp_root}/recheck-fixture.json"