@rexymayderio/sentinel 0.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.
Files changed (191) hide show
  1. package/README.md +295 -0
  2. package/dist/acquire/acquirer.d.ts +38 -0
  3. package/dist/acquire/acquirer.d.ts.map +1 -0
  4. package/dist/acquire/acquirer.js +178 -0
  5. package/dist/acquire/acquirer.js.map +1 -0
  6. package/dist/adapters/cli-approval-prompt.d.ts +13 -0
  7. package/dist/adapters/cli-approval-prompt.d.ts.map +1 -0
  8. package/dist/adapters/cli-approval-prompt.js +44 -0
  9. package/dist/adapters/cli-approval-prompt.js.map +1 -0
  10. package/dist/adapters/github-repo-client.d.ts +9 -0
  11. package/dist/adapters/github-repo-client.d.ts.map +1 -0
  12. package/dist/adapters/github-repo-client.js +48 -0
  13. package/dist/adapters/github-repo-client.js.map +1 -0
  14. package/dist/adapters/index.d.ts +5 -0
  15. package/dist/adapters/index.d.ts.map +1 -0
  16. package/dist/adapters/index.js +5 -0
  17. package/dist/adapters/index.js.map +1 -0
  18. package/dist/adapters/node-process-runner.d.ts +8 -0
  19. package/dist/adapters/node-process-runner.d.ts.map +1 -0
  20. package/dist/adapters/node-process-runner.js +21 -0
  21. package/dist/adapters/node-process-runner.js.map +1 -0
  22. package/dist/adapters/npm-registry-client.d.ts +8 -0
  23. package/dist/adapters/npm-registry-client.d.ts.map +1 -0
  24. package/dist/adapters/npm-registry-client.js +66 -0
  25. package/dist/adapters/npm-registry-client.js.map +1 -0
  26. package/dist/analyzers/ai-prompt-analyzer.d.ts +7 -0
  27. package/dist/analyzers/ai-prompt-analyzer.d.ts.map +1 -0
  28. package/dist/analyzers/ai-prompt-analyzer.js +88 -0
  29. package/dist/analyzers/ai-prompt-analyzer.js.map +1 -0
  30. package/dist/analyzers/analyzer.d.ts +14 -0
  31. package/dist/analyzers/analyzer.d.ts.map +1 -0
  32. package/dist/analyzers/analyzer.js +11 -0
  33. package/dist/analyzers/analyzer.js.map +1 -0
  34. package/dist/analyzers/dependency-analyzer.d.ts +10 -0
  35. package/dist/analyzers/dependency-analyzer.d.ts.map +1 -0
  36. package/dist/analyzers/dependency-analyzer.js +79 -0
  37. package/dist/analyzers/dependency-analyzer.js.map +1 -0
  38. package/dist/analyzers/index.d.ts +13 -0
  39. package/dist/analyzers/index.d.ts.map +1 -0
  40. package/dist/analyzers/index.js +30 -0
  41. package/dist/analyzers/index.js.map +1 -0
  42. package/dist/analyzers/install-script-analyzer.d.ts +7 -0
  43. package/dist/analyzers/install-script-analyzer.d.ts.map +1 -0
  44. package/dist/analyzers/install-script-analyzer.js +64 -0
  45. package/dist/analyzers/install-script-analyzer.js.map +1 -0
  46. package/dist/analyzers/match-evidence.d.ts +6 -0
  47. package/dist/analyzers/match-evidence.d.ts.map +1 -0
  48. package/dist/analyzers/match-evidence.js +15 -0
  49. package/dist/analyzers/match-evidence.js.map +1 -0
  50. package/dist/analyzers/metadata-analyzer.d.ts +7 -0
  51. package/dist/analyzers/metadata-analyzer.d.ts.map +1 -0
  52. package/dist/analyzers/metadata-analyzer.js +105 -0
  53. package/dist/analyzers/metadata-analyzer.js.map +1 -0
  54. package/dist/analyzers/network-analyzer.d.ts +7 -0
  55. package/dist/analyzers/network-analyzer.d.ts.map +1 -0
  56. package/dist/analyzers/network-analyzer.js +47 -0
  57. package/dist/analyzers/network-analyzer.js.map +1 -0
  58. package/dist/analyzers/rules/index.d.ts +19 -0
  59. package/dist/analyzers/rules/index.d.ts.map +1 -0
  60. package/dist/analyzers/rules/index.js +70 -0
  61. package/dist/analyzers/rules/index.js.map +1 -0
  62. package/dist/analyzers/secret-analyzer.d.ts +7 -0
  63. package/dist/analyzers/secret-analyzer.d.ts.map +1 -0
  64. package/dist/analyzers/secret-analyzer.js +33 -0
  65. package/dist/analyzers/secret-analyzer.js.map +1 -0
  66. package/dist/analyzers/source-analyzer.d.ts +7 -0
  67. package/dist/analyzers/source-analyzer.d.ts.map +1 -0
  68. package/dist/analyzers/source-analyzer.js +73 -0
  69. package/dist/analyzers/source-analyzer.js.map +1 -0
  70. package/dist/analyzers/static-code-analyzer.d.ts +7 -0
  71. package/dist/analyzers/static-code-analyzer.d.ts.map +1 -0
  72. package/dist/analyzers/static-code-analyzer.js +67 -0
  73. package/dist/analyzers/static-code-analyzer.js.map +1 -0
  74. package/dist/analyzers/test-path.d.ts +2 -0
  75. package/dist/analyzers/test-path.d.ts.map +1 -0
  76. package/dist/analyzers/test-path.js +32 -0
  77. package/dist/analyzers/test-path.js.map +1 -0
  78. package/dist/cli/index.d.ts +3 -0
  79. package/dist/cli/index.d.ts.map +1 -0
  80. package/dist/cli/index.js +176 -0
  81. package/dist/cli/index.js.map +1 -0
  82. package/dist/cli/spinner.d.ts +5 -0
  83. package/dist/cli/spinner.d.ts.map +1 -0
  84. package/dist/cli/spinner.js +39 -0
  85. package/dist/cli/spinner.js.map +1 -0
  86. package/dist/core/index.d.ts +3 -0
  87. package/dist/core/index.d.ts.map +1 -0
  88. package/dist/core/index.js +3 -0
  89. package/dist/core/index.js.map +1 -0
  90. package/dist/core/permissions.d.ts +4 -0
  91. package/dist/core/permissions.d.ts.map +1 -0
  92. package/dist/core/permissions.js +28 -0
  93. package/dist/core/permissions.js.map +1 -0
  94. package/dist/core/sentinel.d.ts +32 -0
  95. package/dist/core/sentinel.d.ts.map +1 -0
  96. package/dist/core/sentinel.js +164 -0
  97. package/dist/core/sentinel.js.map +1 -0
  98. package/dist/domain/artifact.d.ts +34 -0
  99. package/dist/domain/artifact.d.ts.map +1 -0
  100. package/dist/domain/artifact.js +2 -0
  101. package/dist/domain/artifact.js.map +1 -0
  102. package/dist/domain/finding.d.ts +22 -0
  103. package/dist/domain/finding.d.ts.map +1 -0
  104. package/dist/domain/finding.js +30 -0
  105. package/dist/domain/finding.js.map +1 -0
  106. package/dist/domain/index.d.ts +7 -0
  107. package/dist/domain/index.d.ts.map +1 -0
  108. package/dist/domain/index.js +7 -0
  109. package/dist/domain/index.js.map +1 -0
  110. package/dist/domain/permission.d.ts +8 -0
  111. package/dist/domain/permission.d.ts.map +1 -0
  112. package/dist/domain/permission.js +21 -0
  113. package/dist/domain/permission.js.map +1 -0
  114. package/dist/domain/report.d.ts +35 -0
  115. package/dist/domain/report.d.ts.map +1 -0
  116. package/dist/domain/report.js +2 -0
  117. package/dist/domain/report.js.map +1 -0
  118. package/dist/domain/risk.d.ts +14 -0
  119. package/dist/domain/risk.d.ts.map +1 -0
  120. package/dist/domain/risk.js +15 -0
  121. package/dist/domain/risk.js.map +1 -0
  122. package/dist/domain/target.d.ts +12 -0
  123. package/dist/domain/target.d.ts.map +1 -0
  124. package/dist/domain/target.js +43 -0
  125. package/dist/domain/target.js.map +1 -0
  126. package/dist/engine/data-assessment.d.ts +10 -0
  127. package/dist/engine/data-assessment.d.ts.map +1 -0
  128. package/dist/engine/data-assessment.js +39 -0
  129. package/dist/engine/data-assessment.js.map +1 -0
  130. package/dist/engine/default-policy.d.ts +16 -0
  131. package/dist/engine/default-policy.d.ts.map +1 -0
  132. package/dist/engine/default-policy.js +15 -0
  133. package/dist/engine/default-policy.js.map +1 -0
  134. package/dist/engine/index.d.ts +4 -0
  135. package/dist/engine/index.d.ts.map +1 -0
  136. package/dist/engine/index.js +4 -0
  137. package/dist/engine/index.js.map +1 -0
  138. package/dist/engine/policy-engine.d.ts +13 -0
  139. package/dist/engine/policy-engine.d.ts.map +1 -0
  140. package/dist/engine/policy-engine.js +78 -0
  141. package/dist/engine/policy-engine.js.map +1 -0
  142. package/dist/engine/risk-calculator.d.ts +15 -0
  143. package/dist/engine/risk-calculator.d.ts.map +1 -0
  144. package/dist/engine/risk-calculator.js +57 -0
  145. package/dist/engine/risk-calculator.js.map +1 -0
  146. package/dist/factory.d.ts +14 -0
  147. package/dist/factory.d.ts.map +1 -0
  148. package/dist/factory.js +25 -0
  149. package/dist/factory.js.map +1 -0
  150. package/dist/index.d.ts +4 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +4 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/mcp/server.d.ts +3 -0
  155. package/dist/mcp/server.d.ts.map +1 -0
  156. package/dist/mcp/server.js +151 -0
  157. package/dist/mcp/server.js.map +1 -0
  158. package/dist/ports/approval-prompt.d.ts +5 -0
  159. package/dist/ports/approval-prompt.d.ts.map +1 -0
  160. package/dist/ports/approval-prompt.js +2 -0
  161. package/dist/ports/approval-prompt.js.map +1 -0
  162. package/dist/ports/clock.d.ts +5 -0
  163. package/dist/ports/clock.d.ts.map +1 -0
  164. package/dist/ports/clock.js +4 -0
  165. package/dist/ports/clock.js.map +1 -0
  166. package/dist/ports/index.d.ts +6 -0
  167. package/dist/ports/index.d.ts.map +1 -0
  168. package/dist/ports/index.js +6 -0
  169. package/dist/ports/index.js.map +1 -0
  170. package/dist/ports/process-runner.d.ts +12 -0
  171. package/dist/ports/process-runner.d.ts.map +1 -0
  172. package/dist/ports/process-runner.js +2 -0
  173. package/dist/ports/process-runner.js.map +1 -0
  174. package/dist/ports/registry-client.d.ts +14 -0
  175. package/dist/ports/registry-client.d.ts.map +1 -0
  176. package/dist/ports/registry-client.js +2 -0
  177. package/dist/ports/registry-client.js.map +1 -0
  178. package/dist/ports/repo-client.d.ts +18 -0
  179. package/dist/ports/repo-client.d.ts.map +1 -0
  180. package/dist/ports/repo-client.js +2 -0
  181. package/dist/ports/repo-client.js.map +1 -0
  182. package/dist/report/index.d.ts +2 -0
  183. package/dist/report/index.d.ts.map +1 -0
  184. package/dist/report/index.js +2 -0
  185. package/dist/report/index.js.map +1 -0
  186. package/dist/report/report-generator.d.ts +29 -0
  187. package/dist/report/report-generator.d.ts.map +1 -0
  188. package/dist/report/report-generator.js +167 -0
  189. package/dist/report/report-generator.js.map +1 -0
  190. package/package.json +50 -0
  191. package/skills/sentinel/SKILL.md +525 -0
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # Sentinel
2
+
3
+ > Universal Security Verification Framework for AI Agent Installations
4
+
5
+ Sentinel sits between an AI agent (or a human) and any installer. Nothing installs until the target has been **acquired (download-only, never executed)**, **analyzed**, **risk-scored**, **policy-checked**, and **explicitly approved**.
6
+
7
+ ```
8
+ AI / User -> Sentinel -> Acquire -> Analyze -> Score -> Policy -> Approve -> Install
9
+ ```
10
+
11
+ The installer never runs directly. Acquirers only download and read files; install scripts are never executed during analysis.
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install
19
+ npm run build
20
+ ```
21
+
22
+ Or install globally / run via npx after publishing:
23
+
24
+ ```bash
25
+ npm install -g @rexymayderio/sentinel
26
+ sentinel verify npm express
27
+
28
+ # MCP server (use -p so npx runs the sentinel-mcp bin, not the CLI)
29
+ npx -y -p @rexymayderio/sentinel sentinel-mcp
30
+ ```
31
+
32
+ Requires Node.js >= 20.
33
+
34
+ ---
35
+
36
+ ## Usage
37
+
38
+ ### CLI
39
+
40
+ ```bash
41
+ # Verify only (never installs) - the manual verification path
42
+ sentinel verify npm express
43
+ sentinel verify github owner/repo
44
+ sentinel verify skill ./my-skill
45
+ sentinel verify local ./some-directory
46
+
47
+ # Verify, then install behind the approval gate
48
+ sentinel install npm express
49
+ sentinel install skill ./my-skill --yes # auto-approve (still cannot override a hard BLOCK)
50
+
51
+ # Output formats
52
+ sentinel verify npm express --json
53
+ sentinel verify npm express --markdown
54
+
55
+ # Custom policy file
56
+ sentinel verify npm express --policy ./policy.json
57
+
58
+ # Score test/fixture findings at full weight (default: down-weighted)
59
+ sentinel verify local ./my-project --score-tests
60
+
61
+ # CLI help
62
+ sentinel help
63
+ sentinel verify --help
64
+ ```
65
+
66
+ During local development you can run without building:
67
+
68
+ ```bash
69
+ npm run dev -- verify skill ./my-skill
70
+ ```
71
+
72
+ ### MCP server
73
+
74
+ ```bash
75
+ npm run mcp
76
+ ```
77
+
78
+ Example MCP client config:
79
+
80
+ ```json
81
+ {
82
+ "mcpServers": {
83
+ "sentinel": {
84
+ "command": "npx",
85
+ "args": ["-y", "-p", "@rexymayderio/sentinel", "sentinel-mcp"]
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ > The `-p @rexymayderio/sentinel` (package) flag is required: it tells `npx` to run the `sentinel-mcp` binary from the package. Without `-p`, `npx @rexymayderio/sentinel sentinel-mcp` would run the CLI with `sentinel-mcp` as an (invalid) argument.
92
+
93
+ ### CLI vs MCP
94
+
95
+ Both ship in the **same `@rexymayderio/sentinel` package** - installing it provides two binaries:
96
+
97
+ | Binary | Purpose | Run |
98
+ |--------|---------|-----|
99
+ | `sentinel` | Command-line verifier/installer | `npx -y -p @rexymayderio/sentinel sentinel verify npm express` |
100
+ | `sentinel-mcp` | MCP server over stdio for AI agents | `npx -y -p @rexymayderio/sentinel sentinel-mcp` |
101
+
102
+ There is no separate "CLI-only" or "MCP-only" download - the two binaries are lightweight symlinks over a shared engine, so one install covers both. Use whichever entry point you need.
103
+
104
+ MCP tools: `verify_package`, `verify_repository`, `verify_skill`, `verify_mcp`, `verify_extension`, `verify_docker`, `calculate_risk`, `generate_report`, `scan_directory`, `scan_archive`, `approve_install`, `install`.
105
+
106
+ ### Agent Skill
107
+
108
+ Install the Sentinel agent skill so your AI assistant intercepts install requests, verifies via the MCP above, explains risks, and only installs after approval.
109
+
110
+ Copy or symlink [skills/sentinel/SKILL.md](skills/sentinel/SKILL.md) into your agent's skills directory (e.g. `~/.cursor/skills/sentinel/SKILL.md` for Cursor).
111
+
112
+ The skill never performs analysis itself — it routes to the Sentinel MCP tools and interprets the JSON report. Manual `/verify-*` commands and automatic install interception are documented in the skill file.
113
+
114
+ ---
115
+
116
+ ## Supported targets
117
+
118
+ | Target | Status | Notes |
119
+ |--------|--------|-------|
120
+ | `npm` | Implemented | Registry metadata + tarball extraction |
121
+ | `github` | Implemented | Repo metadata + tarball download |
122
+ | `skill` | Implemented | Local AI skill folder / `SKILL.md` |
123
+ | `local` | Implemented | Any local directory |
124
+ | `pip`, `uv`, `cargo`, `go`, `docker`, `vscode`, `mcp`, `git`, `zip` | Not yet implemented | Acquisition fails -> `REQUIRE_APPROVAL` (manual review) |
125
+
126
+ ---
127
+
128
+ ## How risk is scored
129
+
130
+ Each finding has a severity. The Risk Calculator sums severity weights, subtracts positive-signal credit, and clamps the result to `0-100`.
131
+
132
+ | Severity | Weight |
133
+ |----------|--------|
134
+ | `CRITICAL` | +50 |
135
+ | `HIGH` | +20 |
136
+ | `MEDIUM` | +10 |
137
+ | `LOW` | +2 |
138
+ | `INFO` | 0 |
139
+
140
+ Positive signals (verified publisher, long history, etc.) each subtract `5`, capped at `-30` total.
141
+
142
+ **Test/fixture code** is detected by path (`test/`, `tests/`, `__tests__/`, `fixtures/`, `*.test.*`, `*.spec.*`, etc.). Findings in those files are still reported (tagged `[test]` in terminal output, `isTest: true` in JSON) but contribute only **10%** of their severity weight by default. They also do not drive permission detection. Use `--score-tests` or set `scoreTestCodeFully: true` in policy to score them at full weight.
143
+
144
+ The final score maps to a **risk level**:
145
+
146
+ | Score | Risk level |
147
+ |-------|------------|
148
+ | `0-19` | `LOW` |
149
+ | `20-49` | `MEDIUM` |
150
+ | `50-79` | `HIGH` |
151
+ | `80-100` | `CRITICAL` |
152
+
153
+ **Confidence** reflects how much evidence was actually gathered, not how many problems were found. A clean, fully-described package is high confidence even with zero findings. It is computed from:
154
+
155
+ - package metadata present (`+35`)
156
+ - at least one file scanned (`+25`), plus `+2` per file up to `+20`
157
+ - `+2` per finding signal up to `+20`
158
+
159
+ clamped to `0-100`. When acquisition fails entirely, confidence is `0`. Confidence (and whether any files/metadata were gathered) feeds the data-sufficiency check that can escalate a target to `REQUIRE_APPROVAL`.
160
+
161
+ ---
162
+
163
+ ## Policy decisions
164
+
165
+ The Policy Engine (`src/engine/policy-engine.ts`) reduces the risk score, findings, permissions, and data assessment into exactly one of the five `POLICY_DECISIONS`. They are evaluated in a fixed priority order and the **first** matching rule wins.
166
+
167
+ Evaluation order:
168
+
169
+ 1. Corporate blacklist -> `BLOCK`
170
+ 2. Corporate whitelist -> `AUTO_APPROVE`
171
+ 3. Risk score >= block threshold -> `BLOCK`
172
+ 4. Insufficient data -> `REQUIRE_APPROVAL`
173
+ 5. Verified / trusted publisher -> `AUTO_APPROVE`
174
+ 6. Install script / shell / network / warn-threshold checks -> `WARN` or `REQUIRE_APPROVAL`
175
+ 7. Nothing flagged -> `APPROVE`
176
+
177
+ ### `AUTO_APPROVE`
178
+
179
+ - **Meaning:** Sentinel is confident the target is safe; no human interaction needed.
180
+ - **Triggered when:** the package name matches the `corporateWhitelist`, OR `autoApproveTrusted` is on and the target has a verified-publisher signal or matches `trustedPublishers`.
181
+ - **Risk shown:** actual level (typically `LOW`).
182
+ - **Recommended action:** `SAFE TO INSTALL`.
183
+ - **Install behavior:** proceeds without prompting (CLI and MCP).
184
+
185
+ ### `APPROVE`
186
+
187
+ - **Meaning:** No policy violations detected, but not explicitly trusted either. Safe default.
188
+ - **Triggered when:** the target passes all checks - below warn threshold, no install scripts, no shell access, no network access, sufficient data.
189
+ - **Risk shown:** actual level.
190
+ - **Recommended action:** `SAFE TO INSTALL`.
191
+ - **Install behavior:** CLI prompts for confirmation unless `--yes` is passed; MCP `install` proceeds when `confirm: true`.
192
+
193
+ ### `WARN`
194
+
195
+ - **Meaning:** Installable, but something deserves attention.
196
+ - **Triggered when:** `warnOnInstallScript` is on and the package defines install scripts (`preinstall`/`install`/`postinstall`/`prepare`), OR `warnOnShellAccess` is on and shell access was detected (and not already escalated), OR the risk score is at/above `warnThreshold` (default 50).
197
+ - **Risk shown:** actual level.
198
+ - **Recommended action:** `PROCEED WITH CAUTION`.
199
+ - **Install behavior:** CLI prompts (the `--yes` flag still auto-approves a `WARN`); MCP `install` proceeds when `confirm: true`.
200
+
201
+ ### `REQUIRE_APPROVAL`
202
+
203
+ - **Meaning:** A human must explicitly approve - either because a sensitive capability was detected, or because Sentinel could not gather enough data to judge the target on its own.
204
+ - **Triggered when:** any of:
205
+ - `warnOnShellAccess` detects shell access (escalated from the default);
206
+ - `requireApprovalForNetwork` is on and a network permission / non-`LOW` network finding is present;
207
+ - the data assessment (`src/engine/data-assessment.ts`) is insufficient - acquisition failed (network error, package not found, unsupported ecosystem), no files could be scanned, a remote target returned no usable metadata, or analysis confidence is below `minConfidence` (default 40%). In this case the report's `dataAssessment` lists the specific reasons.
208
+ - **Risk shown:** actual level (may be `LOW` with low confidence when data is thin).
209
+ - **Recommended action:** `REVIEW AND APPROVE BEFORE INSTALLING`.
210
+ - **Install behavior:** CLI prompts for explicit `y/N`; the `--yes` flag (and MCP `install` with `confirm: true`) treats this as approved, asserting that a human has reviewed it.
211
+
212
+ ### `BLOCK`
213
+
214
+ - **Meaning:** Installation is forbidden. Cannot be overridden by `--yes` or by the MCP `confirm` flag.
215
+ - **Triggered when:** the package name matches the `corporateBlacklist`, OR the risk score is at/above `blockThreshold` (default 80, i.e. `CRITICAL`).
216
+ - **Risk shown:** actual level (typically `CRITICAL`).
217
+ - **Recommended action:** `DO NOT INSTALL`.
218
+ - **Install behavior:** refused everywhere. CLI exits with code `2`.
219
+
220
+ ### Decision summary
221
+
222
+ | Decision | Trigger | Risk shown | Recommended action | Auto-installable |
223
+ |----------|---------|------------|--------------------|------------------|
224
+ | `AUTO_APPROVE` | Whitelist / verified / trusted publisher | actual | SAFE TO INSTALL | Yes |
225
+ | `APPROVE` | No violations | actual | SAFE TO INSTALL | With confirm/`--yes` |
226
+ | `WARN` | Install script or warn-threshold | actual | PROCEED WITH CAUTION | With confirm/`--yes` |
227
+ | `REQUIRE_APPROVAL` | Shell/network access, or insufficient data | actual | REVIEW AND APPROVE BEFORE INSTALLING | With confirm/`--yes` (asserts human approval) |
228
+ | `BLOCK` | Blacklist or score >= block threshold | actual | DO NOT INSTALL | No |
229
+
230
+ ---
231
+
232
+ ## Policy configuration
233
+
234
+ Pass a JSON file via `--policy <file>` to override defaults (`src/engine/default-policy.ts`).
235
+
236
+ | Field | Type | Default | Effect |
237
+ |-------|------|---------|--------|
238
+ | `blockThreshold` | number | `80` | Score at/above this -> `BLOCK`. |
239
+ | `warnThreshold` | number | `50` | Score at/above this -> at least `WARN`. |
240
+ | `minConfidence` | number | `40` | Confidence below this -> data treated as insufficient -> `REQUIRE_APPROVAL`. |
241
+ | `trustedPublishers` | string[] | `[]` | Name substrings auto-approved when `autoApproveTrusted` is on. |
242
+ | `corporateWhitelist` | string[] | `[]` | Name substrings forced to `AUTO_APPROVE`. |
243
+ | `corporateBlacklist` | string[] | `[]` | Name substrings forced to `BLOCK`. |
244
+ | `autoApproveTrusted` | boolean | `true` | Enable verified/trusted-publisher auto-approval. |
245
+ | `requireApprovalForNetwork` | boolean | `true` | Network access -> `REQUIRE_APPROVAL`. |
246
+ | `warnOnInstallScript` | boolean | `true` | Install scripts -> `WARN`. |
247
+ | `warnOnShellAccess` | boolean | `true` | Shell access -> `WARN`/`REQUIRE_APPROVAL`. |
248
+ | `allowOverrides` | boolean | `true` | Allow human overrides of non-`BLOCK` decisions. |
249
+ | `scoreTestCodeFully` | boolean | `false` | When `true`, findings in test/fixture paths score at full weight. |
250
+
251
+ Example `policy.json`:
252
+
253
+ ```json
254
+ {
255
+ "blockThreshold": 70,
256
+ "minConfidence": 50,
257
+ "trustedPublishers": ["@myorg/"],
258
+ "corporateBlacklist": ["leftpad-evil"]
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Analyzers
265
+
266
+ | Analyzer | Looks for |
267
+ |----------|-----------|
268
+ | Metadata | name, license, repo, first publish date, recent version update, downloads, verified publisher |
269
+ | Source | HTTPS, suspicious TLDs, npm typosquatting |
270
+ | Static code | `eval`, `exec`, `spawn`, `child_process`, `subprocess`, dynamic imports, base64/hex blobs, entropy, minified files |
271
+ | Install script | `postinstall`/`preinstall`/`prepare`, `curl\|bash`, `wget\|bash`, `sudo`, `rm -rf`, `npx` |
272
+ | AI prompt | prompt injection, zero-width / invisible Unicode, jailbreaks, hidden goals, memory poisoning, tool escalation |
273
+ | Secret | AWS / GCP / OpenAI / Anthropic / GitHub keys, JWT, private keys |
274
+ | Dependency | dependency count, insecure sources, local file deps, OSV lookups |
275
+ | Network | hardcoded IPs, Discord/Telegram webhooks, pastebin, ngrok, crypto miners, Tor |
276
+
277
+ Reports include **evidence** for code-based findings: the matching source line (file, line number, and a snippet up to 120 characters). Terminal output shows this under each finding; JSON includes `file`, `line`, and `evidence` fields.
278
+
279
+ ---
280
+
281
+ ## Development
282
+
283
+ ```bash
284
+ npm run build # compile TypeScript to dist/
285
+ npm test # run behavioral tests (vitest)
286
+ npm run test:watch # watch mode
287
+ ```
288
+
289
+ Architecture follows ports-and-adapters: analyzers, the risk calculator, and the policy engine are pure and unit-testable, while I/O boundaries (registry, repo API, process runner, approval prompt) are mocked in tests.
290
+
291
+ ---
292
+
293
+ ## License
294
+
295
+ MIT
@@ -0,0 +1,38 @@
1
+ import type { Artifact, FileEntry } from '../domain/artifact.js';
2
+ import type { Ecosystem, Target } from '../domain/target.js';
3
+ import type { RegistryClient } from '../ports/registry-client.js';
4
+ import type { RepoClient } from '../ports/repo-client.js';
5
+ export interface Acquirer {
6
+ readonly ecosystem: Ecosystem;
7
+ acquire(target: Target): Promise<Artifact>;
8
+ }
9
+ export declare function walkDirectory(rootPath: string): Promise<FileEntry[]>;
10
+ export declare class NpmAcquirer implements Acquirer {
11
+ private readonly registry;
12
+ readonly ecosystem: "npm";
13
+ constructor(registry: RegistryClient);
14
+ acquire(target: Target): Promise<Artifact>;
15
+ }
16
+ export declare class GithubAcquirer implements Acquirer {
17
+ private readonly repoClient;
18
+ readonly ecosystem: "github";
19
+ constructor(repoClient: RepoClient);
20
+ acquire(target: Target): Promise<Artifact>;
21
+ }
22
+ export declare class LocalAcquirer implements Acquirer {
23
+ readonly ecosystem: Ecosystem;
24
+ constructor(ecosystem?: 'skill' | 'local');
25
+ acquire(target: Target): Promise<Artifact>;
26
+ }
27
+ export declare class UnsupportedAcquirer implements Acquirer {
28
+ readonly ecosystem: Ecosystem;
29
+ constructor(ecosystem: Ecosystem);
30
+ acquire(_target: Target): Promise<Artifact>;
31
+ }
32
+ export declare class AcquirerRegistry {
33
+ private readonly acquirers;
34
+ register(acquirer: Acquirer): void;
35
+ get(ecosystem: Ecosystem): Acquirer;
36
+ }
37
+ export declare function cleanupArtifact(artifact: Artifact): Promise<void>;
38
+ //# sourceMappingURL=acquirer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acquirer.d.ts","sourceRoot":"","sources":["../../src/acquire/acquirer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAmB,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAU1D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC5C;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAsB1E;AAUD,qBAAa,WAAY,YAAW,QAAQ;IAG9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,QAAQ,CAAC,SAAS,EAAG,KAAK,CAAU;gBAEP,QAAQ,EAAE,cAAc;IAE/C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CA2CjD;AAED,qBAAa,cAAe,YAAW,QAAQ;IAGjC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,QAAQ,CAAC,SAAS,EAAG,QAAQ,CAAU;gBAEV,UAAU,EAAE,UAAU;IAE7C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAsBjD;AAED,qBAAa,aAAc,YAAW,QAAQ;IAC5C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;gBAElB,SAAS,GAAE,OAAO,GAAG,OAAiB;IAI5C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CA4BjD;AAED,qBAAa,mBAAoB,YAAW,QAAQ;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS;gBAApB,SAAS,EAAE,SAAS;IAEnC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAKlD;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAE5D,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAIlC,GAAG,CAAC,SAAS,EAAE,SAAS,GAAG,QAAQ;CAUpC;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvE"}
@@ -0,0 +1,178 @@
1
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join, relative } from 'node:path';
4
+ import fg from 'fast-glob';
5
+ import * as tar from 'tar';
6
+ import { isMvpEcosystem } from '../domain/target.js';
7
+ const MAX_FILE_SIZE = 2 * 1024 * 1024;
8
+ const SCANNABLE_EXTENSIONS = [
9
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
10
+ '.py', '.go', '.rs', '.sh', '.bash', '.zsh',
11
+ '.json', '.yaml', '.yml', '.md', '.txt', '.lua',
12
+ '.toml', '.env', '.gitignore',
13
+ ];
14
+ export async function walkDirectory(rootPath) {
15
+ const patterns = SCANNABLE_EXTENSIONS.map((ext) => `**/*${ext}`);
16
+ const paths = await fg(patterns, {
17
+ cwd: rootPath,
18
+ absolute: true,
19
+ dot: true,
20
+ ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**'],
21
+ });
22
+ const files = [];
23
+ for (const absPath of paths) {
24
+ const stat = await import('node:fs/promises').then((fs) => fs.stat(absPath));
25
+ if (stat.size > MAX_FILE_SIZE)
26
+ continue;
27
+ const content = await readFile(absPath, 'utf-8');
28
+ files.push({
29
+ path: relative(rootPath, absPath),
30
+ content,
31
+ size: stat.size,
32
+ });
33
+ }
34
+ return files;
35
+ }
36
+ async function extractTarball(buffer, dest) {
37
+ await mkdir(dest, { recursive: true });
38
+ const tarPath = join(dest, 'package.tgz');
39
+ await writeFile(tarPath, buffer);
40
+ await tar.extract({ file: tarPath, cwd: dest, strip: 1 });
41
+ return dest;
42
+ }
43
+ export class NpmAcquirer {
44
+ registry;
45
+ ecosystem = 'npm';
46
+ constructor(registry) {
47
+ this.registry = registry;
48
+ }
49
+ async acquire(target) {
50
+ const info = await this.registry.getNpmPackage(target.name, target.version);
51
+ const response = await fetch(info.tarballUrl);
52
+ if (!response.ok) {
53
+ throw new Error(`Failed to download npm tarball: ${response.statusText}`);
54
+ }
55
+ const buffer = Buffer.from(await response.arrayBuffer());
56
+ const tempDir = await mkdtemp(join(tmpdir(), 'sentinel-npm-'));
57
+ const rootPath = await extractTarball(buffer, tempDir);
58
+ const files = await walkDirectory(rootPath);
59
+ const metadata = {
60
+ name: info.name,
61
+ version: info.version,
62
+ author: info.author,
63
+ maintainers: info.maintainers,
64
+ repository: info.repository,
65
+ homepage: info.homepage,
66
+ license: info.license,
67
+ publishDate: info.publishDate,
68
+ firstPublishDate: info.firstPublishDate,
69
+ downloadCount: info.downloadCount,
70
+ keywords: info.keywords,
71
+ verifiedPublisher: info.verifiedPublisher,
72
+ scripts: info.scripts,
73
+ dependencies: info.dependencies,
74
+ devDependencies: info.devDependencies,
75
+ optionalDependencies: info.optionalDependencies,
76
+ sourceUrl: info.sourceUrl,
77
+ description: info.description,
78
+ };
79
+ let manifest;
80
+ try {
81
+ const pkgContent = await readFile(join(rootPath, 'package.json'), 'utf-8');
82
+ manifest = JSON.parse(pkgContent);
83
+ }
84
+ catch {
85
+ // package.json may not exist in tarball
86
+ }
87
+ return { target, rootPath, files, metadata, manifest };
88
+ }
89
+ }
90
+ export class GithubAcquirer {
91
+ repoClient;
92
+ ecosystem = 'github';
93
+ constructor(repoClient) {
94
+ this.repoClient = repoClient;
95
+ }
96
+ async acquire(target) {
97
+ const [owner, repo] = target.name.split('/');
98
+ if (!owner || !repo) {
99
+ throw new Error(`Invalid GitHub target: ${target.name}. Expected owner/repo`);
100
+ }
101
+ const repoInfo = await this.repoClient.getRepo(owner, repo);
102
+ const buffer = await this.repoClient.downloadTarball(repoInfo.tarballUrl);
103
+ const tempDir = await mkdtemp(join(tmpdir(), 'sentinel-github-'));
104
+ const rootPath = await extractTarball(buffer, tempDir);
105
+ const files = await walkDirectory(rootPath);
106
+ const metadata = {
107
+ name: repoInfo.fullName,
108
+ repository: `https://github.com/${repoInfo.fullName}`,
109
+ description: repoInfo.description,
110
+ publishDate: repoInfo.createdAt,
111
+ sourceUrl: `https://github.com/${repoInfo.fullName}`,
112
+ };
113
+ return { target, rootPath, files, metadata };
114
+ }
115
+ }
116
+ export class LocalAcquirer {
117
+ ecosystem;
118
+ constructor(ecosystem = 'local') {
119
+ this.ecosystem = ecosystem;
120
+ }
121
+ async acquire(target) {
122
+ const rootPath = target.raw.startsWith('/') || target.raw.startsWith('.')
123
+ ? target.raw
124
+ : join(process.cwd(), target.raw);
125
+ const files = await walkDirectory(rootPath);
126
+ let manifest;
127
+ let metadata = {
128
+ name: target.name,
129
+ sourceUrl: rootPath,
130
+ };
131
+ try {
132
+ const pkgContent = await readFile(join(rootPath, 'package.json'), 'utf-8');
133
+ manifest = JSON.parse(pkgContent);
134
+ metadata = {
135
+ ...metadata,
136
+ name: manifest.name ?? metadata.name,
137
+ version: manifest.version,
138
+ scripts: manifest.scripts,
139
+ dependencies: manifest.dependencies,
140
+ };
141
+ }
142
+ catch {
143
+ // no package.json
144
+ }
145
+ return { target, rootPath, files, metadata, manifest };
146
+ }
147
+ }
148
+ export class UnsupportedAcquirer {
149
+ ecosystem;
150
+ constructor(ecosystem) {
151
+ this.ecosystem = ecosystem;
152
+ }
153
+ async acquire(_target) {
154
+ throw new Error(`Ecosystem "${this.ecosystem}" is not yet implemented. MVP supports: npm, github, skill, local`);
155
+ }
156
+ }
157
+ export class AcquirerRegistry {
158
+ acquirers = new Map();
159
+ register(acquirer) {
160
+ this.acquirers.set(acquirer.ecosystem, acquirer);
161
+ }
162
+ get(ecosystem) {
163
+ const acquirer = this.acquirers.get(ecosystem);
164
+ if (!acquirer) {
165
+ if (!isMvpEcosystem(ecosystem)) {
166
+ return new UnsupportedAcquirer(ecosystem);
167
+ }
168
+ throw new Error(`No acquirer registered for ecosystem: ${ecosystem}`);
169
+ }
170
+ return acquirer;
171
+ }
172
+ }
173
+ export async function cleanupArtifact(artifact) {
174
+ if (artifact.rootPath.includes('sentinel-npm-') || artifact.rootPath.includes('sentinel-github-')) {
175
+ await rm(artifact.rootPath, { recursive: true, force: true }).catch(() => undefined);
176
+ }
177
+ }
178
+ //# sourceMappingURL=acquirer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acquirer.js","sourceRoot":"","sources":["../../src/acquire/acquirer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,MAAM,WAAW,CAAC;AAC3B,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIrD,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACtC,MAAM,oBAAoB,GAAG;IAC3B,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAC5C,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM;IAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;IAC/C,OAAO,EAAE,MAAM,EAAE,YAAY;CAC9B,CAAC;AAOF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,IAAI;QACd,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,CAAC,oBAAoB,EAAE,YAAY,EAAE,YAAY,CAAC;KAC3D,CAAC,CAAC;IAEH,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,IAAI,GAAG,aAAa;YAAE,SAAS;QAExC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;YACjC,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,IAAY;IACxD,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAC1C,MAAM,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,OAAO,WAAW;IAGO;IAFpB,SAAS,GAAG,KAAc,CAAC;IAEpC,YAA6B,QAAwB;QAAxB,aAAQ,GAAR,QAAQ,CAAgB;IAAG,CAAC;IAEzD,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,mCAAmC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE5C,MAAM,QAAQ,GAAoB;YAChC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;YACzC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;YAC/C,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC;QAEF,IAAI,QAA6C,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC;YAC3E,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAA4B,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;QAC1C,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACzD,CAAC;CACF;AAED,MAAM,OAAO,cAAc;IAGI;IAFpB,SAAS,GAAG,QAAiB,CAAC;IAEvC,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAEvD,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,IAAI,uBAAuB,CAAC,CAAC;QAChF,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC1E,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE5C,MAAM,QAAQ,GAAoB;YAChC,IAAI,EAAE,QAAQ,CAAC,QAAQ;YACvB,UAAU,EAAE,sBAAsB,QAAQ,CAAC,QAAQ,EAAE;YACrD,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,WAAW,EAAE,QAAQ,CAAC,SAAS;YAC/B,SAAS,EAAE,sBAAsB,QAAQ,CAAC,QAAQ,EAAE;SACrD,CAAC;QAEF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC/C,CAAC;CACF;AAED,MAAM,OAAO,aAAa;IACf,SAAS,CAAY;IAE9B,YAAY,YAA+B,OAAO;QAChD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;YACvE,CAAC,CAAC,MAAM,CAAC,GAAG;YACZ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QAEpC,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,QAA6C,CAAC;QAClD,IAAI,QAAQ,GAAoB;YAC9B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,QAAQ;SACpB,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC;YAC3E,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAA4B,CAAC;YAC7D,QAAQ,GAAG;gBACT,GAAG,QAAQ;gBACX,IAAI,EAAG,QAAQ,CAAC,IAAe,IAAI,QAAQ,CAAC,IAAI;gBAChD,OAAO,EAAE,QAAQ,CAAC,OAAiB;gBACnC,OAAO,EAAE,QAAQ,CAAC,OAAiC;gBACnD,YAAY,EAAE,QAAQ,CAAC,YAAsC;aAC9D,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACzD,CAAC;CACF;AAED,MAAM,OAAO,mBAAmB;IACT;IAArB,YAAqB,SAAoB;QAApB,cAAS,GAAT,SAAS,CAAW;IAAG,CAAC;IAE7C,KAAK,CAAC,OAAO,CAAC,OAAe;QAC3B,MAAM,IAAI,KAAK,CACb,cAAc,IAAI,CAAC,SAAS,mEAAmE,CAChG,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,gBAAgB;IACV,SAAS,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE5D,QAAQ,CAAC,QAAkB;QACzB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,GAAG,CAAC,SAAoB;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC/B,OAAO,IAAI,mBAAmB,CAAC,SAAS,CAAC,CAAC;YAC5C,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,yCAAyC,SAAS,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAkB;IACtD,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAClG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACvF,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { VerificationReport } from '../domain/report.js';
2
+ import type { ApprovalPrompt } from '../ports/approval-prompt.js';
3
+ export declare class CliApprovalPrompt implements ApprovalPrompt {
4
+ private readonly autoApprove;
5
+ constructor(autoApprove?: boolean);
6
+ requestApproval(report: VerificationReport): Promise<boolean>;
7
+ }
8
+ export declare class AutoApprovalPrompt implements ApprovalPrompt {
9
+ private readonly approved;
10
+ constructor(approved: boolean);
11
+ requestApproval(_report: VerificationReport): Promise<boolean>;
12
+ }
13
+ //# sourceMappingURL=cli-approval-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-approval-prompt.d.ts","sourceRoot":"","sources":["../../src/adapters/cli-approval-prompt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,qBAAa,iBAAkB,YAAW,cAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,UAAQ;IAE1C,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;CA+BpE;AAED,qBAAa,kBAAmB,YAAW,cAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,OAAO;IAExC,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;CAGrE"}
@@ -0,0 +1,44 @@
1
+ import { createInterface } from 'node:readline';
2
+ import kleur from 'kleur';
3
+ export class CliApprovalPrompt {
4
+ autoApprove;
5
+ constructor(autoApprove = false) {
6
+ this.autoApprove = autoApprove;
7
+ }
8
+ async requestApproval(report) {
9
+ if (this.autoApprove && report.policy.decision !== 'BLOCK') {
10
+ return true;
11
+ }
12
+ if (report.policy.decision === 'AUTO_APPROVE') {
13
+ return true;
14
+ }
15
+ if (report.policy.decision === 'BLOCK') {
16
+ console.error(kleur.red().bold('\nInstallation BLOCKED by policy.'));
17
+ return false;
18
+ }
19
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
20
+ return new Promise((resolve) => {
21
+ console.log('');
22
+ console.log(kleur.yellow(`Risk: ${report.risk.level} (${report.risk.score}/100)`));
23
+ console.log(kleur.yellow(`Decision: ${report.policy.decision}`));
24
+ for (const reason of report.policy.reasons) {
25
+ console.log(kleur.dim(` • ${reason}`));
26
+ }
27
+ console.log('');
28
+ rl.question(kleur.bold('Approve installation? [y/N] '), (answer) => {
29
+ rl.close();
30
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
31
+ });
32
+ });
33
+ }
34
+ }
35
+ export class AutoApprovalPrompt {
36
+ approved;
37
+ constructor(approved) {
38
+ this.approved = approved;
39
+ }
40
+ async requestApproval(_report) {
41
+ return this.approved;
42
+ }
43
+ }
44
+ //# sourceMappingURL=cli-approval-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-approval-prompt.js","sourceRoot":"","sources":["../../src/adapters/cli-approval-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,MAAM,OAAO,iBAAiB;IACC;IAA7B,YAA6B,cAAc,KAAK;QAAnB,gBAAW,GAAX,WAAW,CAAQ;IAAG,CAAC;IAEpD,KAAK,CAAC,eAAe,CAAC,MAA0B;QAC9C,IAAI,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,KAAK,cAAc,EAAE,CAAC;YAC9C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACvC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC,CAAC;YACrE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAE7E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;YACnF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;YACjE,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC;YAC1C,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE;gBACjE,EAAE,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;YAC1E,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,OAAO,kBAAkB;IACA;IAA7B,YAA6B,QAAiB;QAAjB,aAAQ,GAAR,QAAQ,CAAS;IAAG,CAAC;IAElD,KAAK,CAAC,eAAe,CAAC,OAA2B;QAC/C,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import type { RepoClient, RepoInfo } from '../ports/repo-client.js';
2
+ export declare class GithubRepoClient implements RepoClient {
3
+ private readonly token?;
4
+ constructor(token?: string);
5
+ private headers;
6
+ getRepo(owner: string, repo: string): Promise<RepoInfo>;
7
+ downloadTarball(url: string): Promise<Buffer>;
8
+ }
9
+ //# sourceMappingURL=github-repo-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-repo-client.d.ts","sourceRoot":"","sources":["../../src/adapters/github-repo-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAepE,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAS;gBAEpB,KAAK,CAAC,EAAE,MAAM;IAI1B,OAAO,CAAC,OAAO;IAWT,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAuBvD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAUpD"}
@@ -0,0 +1,48 @@
1
+ export class GithubRepoClient {
2
+ token;
3
+ constructor(token) {
4
+ this.token = token ?? process.env.GITHUB_TOKEN;
5
+ }
6
+ headers() {
7
+ const headers = {
8
+ Accept: 'application/vnd.github+json',
9
+ 'User-Agent': 'sentinel-security',
10
+ };
11
+ if (this.token) {
12
+ headers.Authorization = `Bearer ${this.token}`;
13
+ }
14
+ return headers;
15
+ }
16
+ async getRepo(owner, repo) {
17
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
18
+ const response = await fetch(url, { headers: this.headers() });
19
+ if (!response.ok) {
20
+ throw new Error(`GitHub repo not found: ${owner}/${repo} (${response.status})`);
21
+ }
22
+ const data = (await response.json());
23
+ return {
24
+ fullName: data.full_name,
25
+ description: data.description,
26
+ stars: data.stargazers_count,
27
+ forks: data.forks_count,
28
+ defaultBranch: data.default_branch,
29
+ archived: data.archived,
30
+ createdAt: data.created_at,
31
+ updatedAt: data.updated_at,
32
+ hasSecurityPolicy: !!data.security_policy,
33
+ tarballUrl: `https://api.github.com/repos/${owner}/${repo}/tarball/${data.default_branch}`,
34
+ topics: data.topics,
35
+ };
36
+ }
37
+ async downloadTarball(url) {
38
+ const response = await fetch(url, {
39
+ headers: this.headers(),
40
+ redirect: 'follow',
41
+ });
42
+ if (!response.ok) {
43
+ throw new Error(`Failed to download tarball: ${response.statusText}`);
44
+ }
45
+ return Buffer.from(await response.arrayBuffer());
46
+ }
47
+ }
48
+ //# sourceMappingURL=github-repo-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-repo-client.js","sourceRoot":"","sources":["../../src/adapters/github-repo-client.ts"],"names":[],"mappings":"AAeA,MAAM,OAAO,gBAAgB;IACV,KAAK,CAAU;IAEhC,YAAY,KAAc;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACjD,CAAC;IAEO,OAAO;QACb,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,6BAA6B;YACrC,YAAY,EAAE,mBAAmB;SAClC,CAAC;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;QACjD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAa,EAAE,IAAY;QACvC,MAAM,GAAG,GAAG,gCAAgC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC5D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,IAAI,IAAI,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAClF,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;QAC3D,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,SAAS;YACxB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,KAAK,EAAE,IAAI,CAAC,gBAAgB;YAC5B,KAAK,EAAE,IAAI,CAAC,WAAW;YACvB,aAAa,EAAE,IAAI,CAAC,cAAc;YAClC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,CAAC,UAAU;YAC1B,SAAS,EAAE,IAAI,CAAC,UAAU;YAC1B,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe;YACzC,UAAU,EAAE,gCAAgC,KAAK,IAAI,IAAI,YAAY,IAAI,CAAC,cAAc,EAAE;YAC1F,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,GAAW;QAC/B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;YACvB,QAAQ,EAAE,QAAQ;SACnB,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IACnD,CAAC;CACF"}