@aldegad/safedeps 2.1.1 → 2.4.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 +273 -463
- package/README.ko.md +76 -12
- package/README.md +107 -38
- package/ROADMAP.md +123 -84
- package/SECURITY.md +45 -0
- package/SKILL.md +86 -143
- package/bin/safedeps +419 -52
- package/lib/gates/audit.sh +36 -0
- package/lib/gates/doctor.sh +212 -0
- package/lib/gates/hooks.sh +131 -0
- package/lib/gates/repo-profile.sh +60 -0
- package/lib/gates/scan.sh +94 -0
- package/lib/gates/templates/gitleaks.private.toml.tmpl +45 -0
- package/lib/gates/templates/gitleaks.toml.tmpl +43 -0
- package/lib/gates/templates/pre-commit.tmpl +49 -0
- package/lib/ledger/ledger.sh +94 -16
- package/lib/npm/closure.sh +115 -0
- package/lib/providers/providers.sh +248 -26
- package/package.json +2 -1
- package/scripts/install/install-safedeps-hooks.mjs +65 -23
- package/scripts/release-gates.sh +252 -0
- package/scripts/safedeps-post-verify.sh +185 -15
- package/scripts/safedeps-pre-guard.sh +309 -39
- package/scripts/test/e2e.sh +228 -4
- package/scripts/test/fixture-provider.mjs +21 -0
- package/scripts/test/smoke.sh +212 -10
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **모든 install 을 미확정 블록으로 본다 — `safedeps` 는 안전한 것만 승인하고, 그렇지 않으면 reorg 한다.**
|
|
4
4
|
>
|
|
5
|
-
> OSV / CISA KEV / GitHub Advisory 로 의존성 spec 을 사전 승인하고, Claude Code 와 Codex CLI 의 hook 에서
|
|
5
|
+
> OSV / CISA KEV / GitHub Advisory 로 의존성 spec 을 사전 승인하고, Claude Code 와 Codex CLI 의 hook 에서 실제 설치 closure 를 강제하며, 승인과 어긋난 install 은 자동으로 마지막 안전 snapshot 으로 롤백한다.
|
|
6
6
|
|
|
7
7
|
*Detailed reference → [README.md](./README.md) (영문, SSoT)*
|
|
8
8
|
|
|
@@ -12,28 +12,52 @@
|
|
|
12
12
|
|
|
13
13
|
블록체인에서 **reorg (재편성)** 은 미확정 블록 시퀀스를 무효화하고 마지막 확정된 안전 상태로 체인을 되돌린다. `safedeps` 는 같은 원리를 `node_modules` 에 적용한다 — 모든 install 은 일련의 공급망 보안 검사를 통과하기 전까지는 **미확정 블록 후보** 로 취급된다. 의심스러우면 도구가 **reorg** 를 수행한다 — lock 파일, `package.json`, `node_modules` 를 마지막 확정된 안전 snapshot 으로 되돌린다.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
빠른 advisory 피드백, 관측 가능한 rollback, silent fallback 없음. command guard 는 best-effort UX 이고, 설치 결과 effect 가 backstop 이다.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 두 lane
|
|
20
|
+
|
|
21
|
+
`safedeps` 는 두 보안 lane 을 소유한다 (전체 설계: [`ARCHITECTURE.md`](./ARCHITECTURE.md) §1):
|
|
22
|
+
|
|
23
|
+
- **install-time** (이 README 의 초점) — advisory check + approved-spec ledger + 빠른 PreToolUse guard + PostToolUse effect enforcement + post-install reorg. 패키지 단위, install 명령과 실제 lockfile effect 주변.
|
|
24
|
+
- **release-time** — `safedeps gates run`, `safedeps scan secrets [--repo|--worktree|--staged]`, `safedeps audit npm`, `safedeps hooks install|check`. repo 트리 secret scan, 의존성 audit, repo-local git hook 설치/검사 (push/release 전). repo-specific policy(gitleaks config, privacy 경로)는 대상 repo 에 남고 safedeps 는 실행 owner. *(옛 `security-release-gates` 흡수.)*
|
|
25
|
+
|
|
26
|
+
release-time lane 의 secret 누출 쪽은 **repo 별이고 opt-in** 이다. `safedeps doctor` 가 그 repo-entry 점검이다 — repo 의 `.gitleaks` policy, `.githooks/pre-commit`, 활성 `core.hooksPath`, scanner 가용성을 진단하고(전역 install-time gate 상태도 같이 보고), `safedeps doctor --fix` 가 시작 policy 를 scaffold(`safedeps hooks init`)하고 활성화(`safedeps hooks install`)한다. scaffold 는 비파괴적이라 repo 가 소유한 기존 `.gitleaks.toml` 은 덮어쓰지 않으며, pre-commit hook 은 `safedeps scan secrets --staged` 에 위임하고 fail-closed 다. [secret 누출 lane (repo 별)](#secret-누출-lane-repo-별) 참고.
|
|
16
27
|
|
|
17
28
|
---
|
|
18
29
|
|
|
19
30
|
## 어떻게 동작
|
|
20
31
|
|
|
32
|
+
`safedeps` 는 모든 install 을 두 동작으로 감싼다:
|
|
33
|
+
|
|
34
|
+
- **설치 전** — `safedeps check` 가 패키지를 OSV(canonical) + CISA KEV + GitHub Advisory 로 검사하고 로컬 ledger 에 승인을 기록한다. npm 은 패키지의 전체 의존성 closure 를 풀어 딸린 모든 transitive 패키지까지 검사한다.
|
|
35
|
+
- **설치 후** — PostToolUse hook 이 실제 `package-lock.json` 에 뭐가 깔렸는지 다시 읽고, ledger 에 없거나 advisory DB 가 새로 취약하다고 답하는 패키지를 reorg(롤백)한다.
|
|
36
|
+
|
|
37
|
+
설치 전 command hook(PreToolUse)은 빠른 advisory 넛지다 — 명백한 미승인 install 과 위험한 명령 형태를 막아 에이전트에게 즉시 피드백을 준다. 하지만 npm 의 진짜 권위는 설치 후 effect gate 다: 명령이 *어떻게 생겼는지* 가 아니라 *실제로 뭐가 깔렸는지* 를 보기 때문에, wrapper 나 난독화된 install 명령으로도 패키지를 통과시킬 수 없다.
|
|
38
|
+
|
|
39
|
+
**스크립트 안전 (무실행 설치).** Claude Code 에서는 PreToolUse hook 이 npm install 에 `--ignore-scripts` 를 붙여 rewrite 한다 — 그래서 설치가 **무실행(inert)** 으로 돈다(패키지는 디스크에 앉되 lifecycle script 는 아직 안 돎). 그다음 effect gate 가 closure 를 검증하고, 통과했을 때만 PostToolUse 가 `npm rebuild` 로 이제서야 검증된 스크립트를 실행한다. 게이트가 거부한 패키지는 *스크립트가 한 번도 돌기 전에* reorg 된다. (Claude Code 의 hook `updatedInput` 기능을 쓴다. Codex CLI 는 이 기능이 없어 Codex 에서는 install 이 정상 실행되고 effect gate 는 detect-and-rollback 이다 — 악성 install script 가 롤백 전에 1회 실행될 수 있다.)
|
|
40
|
+
|
|
41
|
+
이 effect-primary 모델은 현재 npm 한정이다. `pip`, `cargo`, `go`, `gem`, `maven`, `nuget` 은 closure resolver 가 붙기 전까지 v2.1 command-gate + reorg 모델을 유지한다.
|
|
42
|
+
|
|
21
43
|
```
|
|
22
44
|
PreToolUse PostToolUse
|
|
23
45
|
(safedeps-pre-guard.sh) (safedeps-post-verify.sh)
|
|
24
46
|
| |
|
|
25
|
-
install cmd ──> [ Advisory / ledger
|
|
47
|
+
install cmd ──> [ Advisory / ledger UX ] ──> [ Execute ] ──> [ npm effect gate ]
|
|
26
48
|
| | | |
|
|
27
|
-
|
|
28
|
-
block
|
|
49
|
+
명백한 miss/ lock / manifest 깨끗? 의심?
|
|
50
|
+
risk block snapshot | |
|
|
29
51
|
Confirm REORG
|
|
30
52
|
```
|
|
31
53
|
|
|
32
54
|
### 3 단계 구성
|
|
33
55
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
**① Advisory check (`safedeps check`)** — `safedeps check <ecosystem> <pkg>@<range>` 가 OSV(canonical) + CISA KEV(hard-risk overlay) + GHSA(enrichment) 를 조회한다. npm 은 temp dir 에서 `npm install --package-lock-only --ignore-scripts` 로 스크립트 실행 없는 lockfile 을 만들어 전체 closure 를 뽑고 OSV `/v1/querybatch` 로 묶어 검사한다. 깨끗하면 direct spec 과 `transitive_specs` 를 `~/.safedeps/approved-specs/<hash>.json` 에 30일 TTL 로 기록한다.
|
|
57
|
+
|
|
58
|
+
**② 빠른 command guard (`safedeps-pre-guard.sh`, PreToolUse)** — install 명령의 `pkg@version` 토큰을 ledger 와 대조하고 lockfile/manifest 를 snapshot 한다. 미승인이면 차단하고, 다음에 실행할 `safedeps check ...` 명령을 차단 사유에 박아준다 (PATH 에 있으면 `safedeps`, 없으면 절대경로). 에이전트는 그 메시지로 check → 재시도하면 된다. 이 단계는 빠른 advisory/UX 일 뿐 최종 권위는 아니다.
|
|
59
|
+
|
|
60
|
+
**③ Effect gate + reorg (`safedeps-post-verify.sh`, PostToolUse)** — 설치 후 실제 `package-lock.json` closure 전체를 ledger 의 direct entry + `transitive_specs` 와 대조하고 OSV batch 로 재확인한다. **npm 에서는 이 effect gate 가 primary 권위다.** 미승인·취약·provider fail-closed, 또는 의심스러운 install script·native binary 가 있으면 마지막 confirmed snapshot 으로 자동 reorg 한다.
|
|
37
61
|
|
|
38
62
|
---
|
|
39
63
|
|
|
@@ -50,6 +74,46 @@
|
|
|
50
74
|
|
|
51
75
|
---
|
|
52
76
|
|
|
77
|
+
## secret 누출 lane (repo 별)
|
|
78
|
+
|
|
79
|
+
install-time gate 는 전역이지만, secret 이나 진짜 `.env` 가 커밋되는 걸 막는 건 **repo 별이고 opt-in** 이다 — 탐지 policy 가 safedeps 가 아니라 각 repo 에 있기 때문이다. `safedeps doctor` 가 그 빈틈을 메우는 진입점이다.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 이 repo 의 자세 진단 (read-only). secret lane 에 gap 이 있으면 non-zero 로 끝난다.
|
|
83
|
+
$ safedeps doctor
|
|
84
|
+
safedeps doctor — repo security posture
|
|
85
|
+
repo: /path/to/repo
|
|
86
|
+
profile: public
|
|
87
|
+
|
|
88
|
+
Secret-leak lane (per-repo)
|
|
89
|
+
✓ git worktree
|
|
90
|
+
✗ gitleaks config (.gitleaks.toml) → safedeps hooks init --root "/path/to/repo"
|
|
91
|
+
✗ .githooks/pre-commit (present) → safedeps hooks init --root "/path/to/repo"
|
|
92
|
+
✗ git hooks active (core.hooksPath=<unset>) → safedeps hooks install --root "/path/to/repo"
|
|
93
|
+
✓ secret scanner available (gitleaks)
|
|
94
|
+
|
|
95
|
+
Dependency-install gate (global, all repos)
|
|
96
|
+
✓ dependency-install gate installed (~/.claude/skills/safedeps)
|
|
97
|
+
|
|
98
|
+
3 gap(s) in the secret-leak lane.
|
|
99
|
+
Fix all at once: safedeps doctor --fix --root "/path/to/repo"
|
|
100
|
+
|
|
101
|
+
# 시작 policy scaffold + hook 활성화 (비파괴적).
|
|
102
|
+
$ safedeps doctor --fix
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
lane 구성 요소:
|
|
106
|
+
|
|
107
|
+
- **`safedeps hooks init`** 가 시작용 `.gitleaks.toml`(private repo 면 `.gitleaks.private.toml`)과 `.githooks/pre-commit` 을 scaffold 한다. 기존 파일은 덮지 않고 유지 — policy 는 repo 가 소유한다.
|
|
108
|
+
- **`safedeps hooks install`** 이 repo-local hook 을 활성화한다(`core.hooksPath = .githooks`).
|
|
109
|
+
- **pre-commit hook 은 `safedeps scan secrets --staged` 에 위임**하고 **fail-closed** 다: scanner(로컬 `gitleaks` 또는 Docker)가 못 돌면 silent skip 이 아니라 커밋을 막는다. 의도된 우회는 사람이 소유하는 `git commit --no-verify` 뿐이다.
|
|
110
|
+
|
|
111
|
+
scaffold 된 `.gitleaks.toml` 은 **네가 손보는 시작점**이다: gitleaks 기본 ruleset 을 extend 하고, 값이 할당된 `.env` 커밋을 잡는 rule 을 더하며(`.env.example`/`.sample`/`.template` 변형은 allowlist), fixture 용 repo-owned `[allowlist]` 블록을 남겨둔다. safedeps 는 *실행* — `safedeps scan secrets` 로 gitleaks 구동 — 만 소유하고 policy 내용은 소유하지 않는다.
|
|
112
|
+
|
|
113
|
+
`safedeps doctor --json` 은 `{ command, repo, profile, gaps, ok, checks[] }` 를 돌려준다; `gaps`/`ok` 는 repo 별 secret 누출 lane 만 반영한다.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
53
117
|
## 설치
|
|
54
118
|
|
|
55
119
|
### 1) GitHub clone (skill 본체 설치, canonical)
|
|
@@ -119,16 +183,16 @@ node scripts/install/install-safedeps-recheck-agent.mjs install --hour 9 --minut
|
|
|
119
183
|
전형적인 흐름:
|
|
120
184
|
|
|
121
185
|
1. 에이전트가 `npm install foo@1.2.3` 같은 명령을 작성한다.
|
|
122
|
-
2. PreToolUse hook 이
|
|
186
|
+
2. PreToolUse hook 이 빠른 advisory ledger check 를 수행한다. direct spec 이 없거나 만료됐거나 명백히 위험하면 install 을 **차단**하고, 다음에 실행할 정확한 `safedeps check npm foo@1.2.3` 명령을 reason 에 박아 에이전트에게 돌려준다.
|
|
123
187
|
3. 에이전트가 그 안내를 받아 `safedeps check` 를 호출 → OSV / CISA KEV / GitHub Advisory 조회 → 안전하면 **허용목록 (ledger) 에 박는다**. KEV 매치면 hard-block (override 불가), CVE 에 patch 가 있으면 안전 버전으로 자동 narrow.
|
|
124
188
|
4. 다시 install 시도 → 이번엔 ledger 매치되어 **통과**.
|
|
125
|
-
5. install 후 PostToolUse hook 이 lockfile / install script / native binary 를
|
|
189
|
+
5. install 후 PostToolUse hook 이 npm primary authority 로 실제 lockfile closure 를 direct ledger / `transitive_specs` / OSV batch 와 대조하고, install script / native binary 를 검증한다. 어긋나면 마지막 안전 snapshot 으로 **자동 reorg**.
|
|
126
190
|
|
|
127
|
-
이 흐름으로
|
|
191
|
+
이 흐름으로 일반적인 package-manager install 명령은 실행 전에 빠른 advisory 피드백을 받고, npm install 은 실행 후 effect-time closure enforcement 를 받는다. 다만 command hook 은 best-effort 명령 휴리스틱이지 sandbox 가 아니다. 비정상 wrapper/interpreter 경로나 같은 사용자 권한으로 로컬 safedeps 상태를 조작하는 공격은 ledger 서명/강화 전까지 trust boundary 밖이다. npm effect-primary 모델은 아직 `pip`, `cargo`, `go`, `gem`, `maven`, `nuget` 에 적용되지 않으며, 이 ecosystem 들은 v2.1 command-gate + reorg 모델을 유지한다. SaaS 의존 없이 로컬 + 공개 DB (OSV / KEV / GHSA) 만 쓴다.
|
|
128
192
|
|
|
129
193
|
---
|
|
130
194
|
|
|
131
|
-
## Legacy
|
|
195
|
+
## Legacy / Migration: v1 `npm-reorg-guard`
|
|
132
196
|
|
|
133
197
|
v1 시절 이름이 `npm-reorg-guard` 였고 state 디렉토리가 `~/.npm-reorg-guard/` 였다. v2 로 rename 되면서 state 도 `~/.safedeps/` 로 옮겨야 하는데, 그 1회 이전을 자동화한 명령이 있다:
|
|
134
198
|
|
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
> **Treat every install as an unconfirmed block — `safedeps` approves the safe ones, reorgs the rest.**
|
|
4
4
|
>
|
|
5
|
-
> Pre-approve dependency installs against OSV / CISA KEV / GitHub Advisory, enforce
|
|
5
|
+
> Pre-approve dependency installs against OSV / CISA KEV / GitHub Advisory, enforce the installed closure from Claude Code and Codex CLI hooks, and auto-rollback any install that diverges from the approved closure. *(한국어 README → [README.ko.md](./README.ko.md))*
|
|
6
6
|
|
|
7
7
|
## Why "reorg"?
|
|
8
8
|
|
|
9
9
|
In blockchain networks, a **reorganization (reorg)** invalidates a sequence of blocks and reverts the chain to a previously confirmed safe state. `safedeps` applies the same principle to your `node_modules`: every install is treated as an unconfirmed block candidate until it passes a battery of supply-chain security checks. If anything looks wrong, the tool performs a **reorg** -- rolling back lock files, `package.json`, and `node_modules` to the last confirmed safe snapshot.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Fast advisory feedback, observable rollback, and no hidden fallback. The command guard is best-effort UX; the installed effect is the backstop.
|
|
12
12
|
|
|
13
13
|
## Distribution Model
|
|
14
14
|
|
|
@@ -19,19 +19,37 @@ Safedeps has two distribution surfaces:
|
|
|
19
19
|
|
|
20
20
|
Use the GitHub release when you want the full skill/hook source tree as the canonical artifact. Use npm when you mainly want a versioned global CLI.
|
|
21
21
|
|
|
22
|
+
## Two Lanes
|
|
23
|
+
|
|
24
|
+
`safedeps` owns two security lanes (full design in [`ARCHITECTURE.md`](./ARCHITECTURE.md) §1):
|
|
25
|
+
|
|
26
|
+
- **Install-time** (the focus of this README) — advisory check + approved-spec ledger + fast PreToolUse guard + PostToolUse effect enforcement + post-install reorg. Per-package, around the install command and its actual lockfile effect.
|
|
27
|
+
- **Release-time** — `safedeps gates run`, `safedeps scan secrets [--repo|--worktree|--staged]`, `safedeps audit npm`, `safedeps hooks install|check`. Repo-tree secret scan, dependency audit, and repo-local git hook install/check before push/release. Repo-specific policy (gitleaks config, privacy paths) stays in the target repo; safedeps owns execution. *(Absorbed the former `security-release-gates`.)*
|
|
28
|
+
|
|
29
|
+
The secret-leak side of the release-time lane is **per-repo and opt-in**. `safedeps doctor` is its repo-entry check: it diagnoses the repo's `.gitleaks` policy, `.githooks/pre-commit`, the active `core.hooksPath`, and scanner availability (and reports the global install-time gate too), then `safedeps doctor --fix` scaffolds a starter policy (`safedeps hooks init`) and activates it (`safedeps hooks install`). The scaffold is non-destructive — an existing repo-owned `.gitleaks.toml` is never overwritten — and the pre-commit hook delegates to `safedeps scan secrets --staged`, fail-closed. See [Secret-Leak Lane (per-repo)](#secret-leak-lane-per-repo).
|
|
30
|
+
|
|
22
31
|
## How It Works
|
|
23
32
|
|
|
24
|
-
`safedeps`
|
|
33
|
+
`safedeps` works in two moves around every install:
|
|
34
|
+
|
|
35
|
+
- **Before** — `safedeps check` clears a package against OSV (canonical), CISA KEV, and GitHub Advisory, then records the approval in a local ledger. For npm it resolves the package's full dependency closure and checks every transitive package too.
|
|
36
|
+
- **After** — the PostToolUse hook re-reads what actually landed in `package-lock.json` and reorgs (rolls back) anything that isn't in the ledger or that the advisory databases now flag.
|
|
37
|
+
|
|
38
|
+
The pre-install command hook (PreToolUse) is a fast advisory nudge — it blocks obvious unapproved installs and risky command forms so the agent gets immediate feedback. But for npm the real authority is the post-install effect gate: it judges what was *actually installed*, not what the command looked like, so a wrapped or obfuscated install command can't slip a package past it.
|
|
39
|
+
|
|
40
|
+
**Script safety (inert install).** On Claude Code, the PreToolUse hook rewrites an npm install to add `--ignore-scripts`, so the install runs **inert** — packages land on disk but no lifecycle script runs yet. The effect gate then verifies the closure; only if it passes does the PostToolUse hook run `npm rebuild` to execute the now-verified scripts. A package the gate rejects is reorged before any of its scripts run. (This uses the Claude Code hook `updatedInput` capability. Codex CLI does not expose it, so on Codex the install runs normally and the effect gate is detect-and-rollback — a malicious install script can run once before the rollback.)
|
|
41
|
+
|
|
42
|
+
This effect-primary model is npm-only for now. `pip`, `cargo`, `go`, `gem`, `maven`, and `nuget` stay on the v2.1 command-gate + reorg model until their closure resolvers land.
|
|
25
43
|
|
|
26
44
|
```
|
|
27
45
|
PreToolUse PostToolUse
|
|
28
46
|
(safedeps-pre-guard.sh) (safedeps-post-verify.sh)
|
|
29
47
|
| |
|
|
30
|
-
install cmd ──> [ Advisory/ledger
|
|
48
|
+
install cmd ──> [ Advisory/ledger UX ] ──> [ Execute ] ──> [ npm effect gate ]
|
|
31
49
|
| | | |
|
|
32
|
-
Block
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
Block obvious Snapshot Clean? Suspicious?
|
|
51
|
+
misses/risk lock/manifest files, | |
|
|
52
|
+
package listings Confirm REORG
|
|
35
53
|
| |
|
|
36
54
|
| v v
|
|
37
55
|
+--- parent_snapshot_id ──> confirmed
|
|
@@ -40,7 +58,7 @@ Use the GitHub release when you want the full skill/hook source tree as the cano
|
|
|
40
58
|
confirmed snapshot
|
|
41
59
|
```
|
|
42
60
|
|
|
43
|
-
### Phase 1: Advisory
|
|
61
|
+
### Phase 1: Advisory Check (`safedeps check`)
|
|
44
62
|
|
|
45
63
|
Before an agent installs a dependency, it should run:
|
|
46
64
|
|
|
@@ -48,40 +66,44 @@ Before an agent installs a dependency, it should run:
|
|
|
48
66
|
safedeps check <ecosystem> <pkg>@<version|range> --json
|
|
49
67
|
```
|
|
50
68
|
|
|
51
|
-
That command queries OSV (canonical), CISA KEV (hard-risk overlay), and GitHub Advisory (enrichment). Clean or safely narrowed specs are written to `~/.safedeps/approved-specs
|
|
69
|
+
That command queries OSV (canonical), CISA KEV (hard-risk overlay), and GitHub Advisory (enrichment). For npm, it first creates a script-free temp lockfile with `npm install --package-lock-only --ignore-scripts`, extracts the full dependency closure, and queries OSV `/v1/querybatch`. Clean or safely narrowed specs are written to `~/.safedeps/approved-specs/`; npm entries also record `transitive_specs`.
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
### Phase 2: Fast Command Guard + Snapshots (PreToolUse)
|
|
72
|
+
|
|
73
|
+
When Claude Code or Codex CLI is about to run `npm install`, `pip install`, `cargo add`, `go get`, `gem install`, or similar commands, the guard hook provides a fast advisory/UX layer:
|
|
54
74
|
|
|
55
75
|
1. **Snapshots** the current `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, and `package.json` into `~/.safedeps/snapshots/`.
|
|
56
76
|
2. **Records metadata** including a `parent_snapshot_id` linking to the previous confirmed snapshot (forming a chain, just like blocks).
|
|
57
77
|
3. **Captures pre-install state** of `node_modules` (package listings and binary listings) for diff-based detection later.
|
|
58
|
-
4. **
|
|
78
|
+
4. **Fast-checks the approved-spec ledger** for explicit `pkg@version` install commands.
|
|
59
79
|
5. **Runs pre-flight checks** and **blocks** the command entirely if it detects:
|
|
60
80
|
- Typosquatting package names (`lod_sh`, `reacct`, `axois`, etc.)
|
|
61
81
|
- Non-standard `--registry` URLs (anything outside `registry.npmjs.org` and `registry.yarnpkg.com`)
|
|
62
82
|
- Piped remote execution patterns (`curl ... | bash`)
|
|
63
83
|
- Explicit disabling of install script safety (`npm config set ignore-scripts false`)
|
|
64
84
|
|
|
65
|
-
If the ledger gate or a pre-flight check fails, the command is **blocked before execution** -- nothing is installed.
|
|
85
|
+
If the ledger gate or a pre-flight check fails, the command is **blocked before execution** -- nothing is installed. This command guard is intentionally best-effort; it improves the agent loop and catches direct misses, while npm authority lives in the post-install effect gate.
|
|
86
|
+
|
|
87
|
+
### Phase 3: Post-install Effect Enforcement (`safedeps-post-verify.sh` -- PostToolUse)
|
|
66
88
|
|
|
67
|
-
|
|
89
|
+
After the install command completes, the verify hook analyzes what changed. For npm, this is the primary enforcement surface: it reads the actual `package-lock.json` closure, verifies every package against approved direct entries and their `transitive_specs`, and re-checks the closure with OSV batch.
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
1. **npm effect gate** -- Reorgs if any lockfile package is unapproved, KEV-blocked, vulnerable, or cannot be verified fail-closed.
|
|
70
92
|
|
|
71
|
-
|
|
93
|
+
2. **Install script analysis** -- Scans newly added packages for `preinstall`, `install`, and `postinstall` scripts containing:
|
|
72
94
|
- Network access (`curl`, `wget`, `fetch`, `http`, `socket`, `dns`)
|
|
73
95
|
- Dynamic code execution (`eval`, `exec`, `spawn`, `child_process`, `Function()`)
|
|
74
96
|
- Sensitive path access (`~/.ssh`, `.env`, `.aws`, `credentials`)
|
|
75
97
|
- Obfuscated content (`base64`, `atob`, `Buffer.from`, hex/unicode escapes)
|
|
76
98
|
|
|
77
|
-
|
|
99
|
+
3. **Lock file diff analysis** -- Compares the snapshotted lock file content against the post-install version:
|
|
78
100
|
- Resolved URLs pointing to non-standard registries
|
|
79
101
|
- Insecure protocols (`http://`, `git://`) in resolved URLs
|
|
80
102
|
- Unusually large dependency additions (>50 new resolved entries, indicating potential dependency confusion)
|
|
81
103
|
|
|
82
|
-
|
|
104
|
+
4. **Binary inspection** -- Checks `node_modules/.bin/` for newly added native binaries (ELF, Mach-O, shared objects) that should not appear in a JavaScript project.
|
|
83
105
|
|
|
84
|
-
###
|
|
106
|
+
### Confirm or Reorg
|
|
85
107
|
|
|
86
108
|
- **All checks pass** -- The snapshot is marked as **confirmed** in `~/.safedeps/confirmed`. This becomes the new safe baseline.
|
|
87
109
|
- **Any check fails** -- A **reorg** is triggered:
|
|
@@ -96,7 +118,7 @@ After the install command completes, the verify hook analyzes what changed:
|
|
|
96
118
|
| Blockchain Concept | Safedeps Equivalent |
|
|
97
119
|
|---|---|
|
|
98
120
|
| **Block candidate** | Snapshot taken before `npm install` |
|
|
99
|
-
| **Block validation** | Post-install
|
|
121
|
+
| **Block validation** | Post-install effect checks (npm closure, scripts, lock diff, binaries) |
|
|
100
122
|
| **Finality / confirmation** | Snapshot ID written to `~/.safedeps/confirmed` |
|
|
101
123
|
| **Chain reorganization** | Rollback to last confirmed snapshot + `node_modules` rebuild |
|
|
102
124
|
| **Parent hash linking** | `parent_snapshot_id` in each snapshot's `_meta.json` |
|
|
@@ -106,18 +128,58 @@ After the install command completes, the verify hook analyzes what changed:
|
|
|
106
128
|
|
|
107
129
|
| Category | What it catches | Phase | Action |
|
|
108
130
|
|---|---|---|---|
|
|
109
|
-
| Typosquatting | Known misspelling patterns of popular packages |
|
|
110
|
-
| Pipe execution | `curl \| bash`, `wget \| sh` |
|
|
111
|
-
| Registry hijack | `--registry` pointing to unofficial sources |
|
|
112
|
-
| Script safety bypass | `npm config set ignore-scripts false` |
|
|
113
|
-
| Command indirection | `eval "npm install ..."`, subshell expansion, variable indirection |
|
|
114
|
-
| npx/dlx execution | `npx`, `pnpm dlx`, `yarn dlx` package execution |
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
131
|
+
| Typosquatting | Known misspelling patterns of popular packages | PreToolUse advisory guard | **Block** |
|
|
132
|
+
| Pipe execution | `curl \| bash`, `wget \| sh` | PreToolUse advisory guard | **Block** |
|
|
133
|
+
| Registry hijack | `--registry` pointing to unofficial sources | PreToolUse advisory guard | **Block** |
|
|
134
|
+
| Script safety bypass | `npm config set ignore-scripts false` | PreToolUse advisory guard | **Block** |
|
|
135
|
+
| Command indirection | `eval "npm install ..."`, subshell expansion, variable indirection | PreToolUse advisory guard | **Guard** |
|
|
136
|
+
| npx/dlx execution | `npx`, `pnpm dlx`, `yarn dlx` package execution | PreToolUse advisory guard | **Guard** |
|
|
137
|
+
| Unapproved transitive dependency | npm `package-lock.json` package missing from direct ledger or `transitive_specs` | PostToolUse npm primary effect gate | **Reorg** |
|
|
138
|
+
| Vulnerable closure package | npm direct/transitive package with OSV/KEV hit | PostToolUse npm primary effect gate | **Reorg** |
|
|
139
|
+
| Malicious install scripts | Network calls, `eval`/`exec`, sensitive path access in hooks | PostToolUse effect verify | **Reorg** |
|
|
140
|
+
| Obfuscated code | Base64, hex encoding, `Buffer.from` in install scripts | PostToolUse effect verify | **Reorg** |
|
|
141
|
+
| Lock file tampering | Resolved URLs from non-standard registries | PostToolUse effect verify | **Reorg** |
|
|
142
|
+
| Insecure protocols | `http://` or `git://` resolved URLs | PostToolUse effect verify | **Reorg** |
|
|
143
|
+
| Dependency confusion | >50 new dependencies in a single install | PostToolUse effect verify | **Reorg** |
|
|
144
|
+
| Native binaries | Compiled executables in `node_modules/.bin/` | PostToolUse effect verify | **Reorg** |
|
|
145
|
+
|
|
146
|
+
## Secret-Leak Lane (per-repo)
|
|
147
|
+
|
|
148
|
+
The install-time gate is global, but stopping a secret or a real `.env` from being committed is **per-repo** and stays opt-in — its detection policy lives in each repo, not in safedeps. `safedeps doctor` is the entry point that closes that gap.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Diagnose this repo's posture (read-only). Exits non-zero if the secret lane has gaps.
|
|
152
|
+
$ safedeps doctor
|
|
153
|
+
safedeps doctor — repo security posture
|
|
154
|
+
repo: /path/to/repo
|
|
155
|
+
profile: public
|
|
156
|
+
|
|
157
|
+
Secret-leak lane (per-repo)
|
|
158
|
+
✓ git worktree
|
|
159
|
+
✗ gitleaks config (.gitleaks.toml) → safedeps hooks init --root "/path/to/repo"
|
|
160
|
+
✗ .githooks/pre-commit (present) → safedeps hooks init --root "/path/to/repo"
|
|
161
|
+
✗ git hooks active (core.hooksPath=<unset>) → safedeps hooks install --root "/path/to/repo"
|
|
162
|
+
✓ secret scanner available (gitleaks)
|
|
163
|
+
|
|
164
|
+
Dependency-install gate (global, all repos)
|
|
165
|
+
✓ dependency-install gate installed (~/.claude/skills/safedeps)
|
|
166
|
+
|
|
167
|
+
3 gap(s) in the secret-leak lane.
|
|
168
|
+
Fix all at once: safedeps doctor --fix --root "/path/to/repo"
|
|
169
|
+
|
|
170
|
+
# Scaffold the starter policy + activate the hooks (non-destructive).
|
|
171
|
+
$ safedeps doctor --fix
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
What the lane is made of:
|
|
175
|
+
|
|
176
|
+
- **`safedeps hooks init`** scaffolds a starter `.gitleaks.toml` (or `.gitleaks.private.toml` for a private repo) and a `.githooks/pre-commit`. Existing files are kept, never overwritten — the repo owns the policy.
|
|
177
|
+
- **`safedeps hooks install`** activates the repo-local hooks (`core.hooksPath = .githooks`).
|
|
178
|
+
- The **pre-commit hook delegates to `safedeps scan secrets --staged`** and is **fail-closed**: if the scanner (local `gitleaks` or Docker) cannot run, it blocks the commit instead of skipping silently. The only intentional bypass is `git commit --no-verify`, which the human owns.
|
|
179
|
+
|
|
180
|
+
The scaffolded `.gitleaks.toml` is a **starter you tune**: it extends gitleaks' default ruleset, adds a rule for a committed `.env` with an assigned secret (the `.env.example`/`.sample`/`.template` variants are allowlisted), and leaves a repo-owned `[allowlist]` block for your fixtures. safedeps owns *execution* — running gitleaks via `safedeps scan secrets` — not the policy content.
|
|
181
|
+
|
|
182
|
+
`safedeps doctor --json` returns `{ command, repo, profile, gaps, ok, checks[] }`; `gaps`/`ok` reflect the per-repo secret-leak lane only.
|
|
121
183
|
|
|
122
184
|
## Installation
|
|
123
185
|
|
|
@@ -151,7 +213,7 @@ cd safedeps
|
|
|
151
213
|
node scripts/install/install-safedeps-hooks.mjs
|
|
152
214
|
```
|
|
153
215
|
|
|
154
|
-
The installer is idempotent. It symlinks the skill into `~/.claude/skills/safedeps` and `~/.codex/skills/safedeps` when those roots exist, patches the matching hook config, and can also place `safedeps` on PATH through `~/.local/bin`.
|
|
216
|
+
The installer is idempotent. It symlinks the skill into `~/.claude/skills/safedeps` and `~/.codex/skills/safedeps` when those roots exist, patches the matching hook config, and — with `--link-bin` — can also place `safedeps` on PATH through `~/.local/bin`. That PATH link is optional: the hooks name an absolute fallback path in their block messages, so the gate is self-contained and works with zero PATH setup.
|
|
155
217
|
|
|
156
218
|
**3. Manual hook registration, if needed:**
|
|
157
219
|
|
|
@@ -285,9 +347,11 @@ safedeps/
|
|
|
285
347
|
lib/
|
|
286
348
|
providers/ # OSV / CISA KEV / GHSA adapters
|
|
287
349
|
ledger/ # approved-spec ledger
|
|
350
|
+
npm/ # lockfile closure resolver
|
|
351
|
+
gates/ # repo-tree lane: scan / audit / hooks / doctor + templates/
|
|
288
352
|
scripts/
|
|
289
|
-
safedeps-pre-guard.sh # PreToolUse hook --
|
|
290
|
-
safedeps-post-verify.sh # PostToolUse hook --
|
|
353
|
+
safedeps-pre-guard.sh # PreToolUse hook -- advisory ledger UX + snapshots
|
|
354
|
+
safedeps-post-verify.sh # PostToolUse hook -- npm primary effect verification + reorg
|
|
291
355
|
install/install-safedeps-hooks.mjs
|
|
292
356
|
install/install-safedeps-recheck-agent.mjs
|
|
293
357
|
install/migrate-safedeps-state.mjs
|
|
@@ -305,14 +369,19 @@ safedeps/
|
|
|
305
369
|
Typical flow:
|
|
306
370
|
|
|
307
371
|
1. The agent writes `npm install foo@1.2.3` (or any of the other supported install verbs).
|
|
308
|
-
2. The PreToolUse hook
|
|
372
|
+
2. The PreToolUse hook does a fast advisory ledger check. If the direct spec is missing, expired, or obviously risky, it **blocks** the install and returns the exact `safedeps check npm foo@1.2.3` command the agent should run next, in the block reason.
|
|
309
373
|
3. The agent runs `safedeps check`. The CLI queries OSV / CISA KEV / GitHub Advisory and, if safe, **adds the spec to the ledger**. KEV matches are hard-block (no override). CVEs with an available patch are auto-narrowed to the fixed version.
|
|
310
374
|
4. The agent retries the install. The ledger entry now matches, so the install **proceeds**.
|
|
311
|
-
5. After the install, the PostToolUse hook
|
|
375
|
+
5. After the install, the PostToolUse hook is the npm primary authority: it verifies the actual lockfile closure against direct ledger entries, `transitive_specs`, and OSV batch, then checks install scripts and native binaries and **auto-reorgs** to the last confirmed snapshot if anything diverged.
|
|
376
|
+
|
|
377
|
+
Every install command gets fast advisory feedback before it runs; every npm install gets closure-level enforcement after it runs. The suspicious package a human would catch at PR review is already caught at install time — and there is no SaaS dependency, only the local CLI plus public databases (OSV / KEV / GHSA).
|
|
378
|
+
|
|
379
|
+
Two honest boundaries:
|
|
312
380
|
|
|
313
|
-
|
|
381
|
+
- **The command hook is a heuristic, not a sandbox.** Unusual wrappers, shell interpreters, or same-user tampering with local `~/.safedeps` state sit outside its trust boundary. The npm effect gate is the backstop — it catches what the command hook misses, because it inspects the installed result rather than the command text.
|
|
382
|
+
- **Effect-primary enforcement is npm-only today.** `pip`, `cargo`, `go`, `gem`, `maven`, and `nuget` stay on the v2.1 command-gate + reorg model until their closure resolvers land.
|
|
314
383
|
|
|
315
|
-
## Legacy
|
|
384
|
+
## Legacy / Migration: v1 `npm-reorg-guard`
|
|
316
385
|
|
|
317
386
|
The v1 product was named `npm-reorg-guard` and used `~/.npm-reorg-guard/` as the state directory. v2 moves state to `~/.safedeps/`. A one-shot migration is provided:
|
|
318
387
|
|