sentinelayer-cli 0.6.2 → 0.8.1
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.md +1009 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +64 -63
- package/src/agents/ai-governance/index.js +12 -0
- package/src/agents/ai-governance/tools/base.js +171 -0
- package/src/agents/ai-governance/tools/eval-regression.js +47 -0
- package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
- package/src/agents/ai-governance/tools/index.js +52 -0
- package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
- package/src/agents/ai-governance/tools/provenance-check.js +69 -0
- package/src/agents/backend/index.js +12 -0
- package/src/agents/backend/tools/base.js +189 -0
- package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
- package/src/agents/backend/tools/idempotency-audit.js +105 -0
- package/src/agents/backend/tools/index.js +87 -0
- package/src/agents/backend/tools/retry-audit.js +132 -0
- package/src/agents/backend/tools/timeout-audit.js +144 -0
- package/src/agents/code-quality/index.js +12 -0
- package/src/agents/code-quality/tools/base.js +159 -0
- package/src/agents/code-quality/tools/complexity-measure.js +197 -0
- package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
- package/src/agents/code-quality/tools/cycle-detect.js +49 -0
- package/src/agents/code-quality/tools/dep-graph.js +196 -0
- package/src/agents/code-quality/tools/index.js +89 -0
- package/src/agents/data-layer/index.js +12 -0
- package/src/agents/data-layer/tools/base.js +181 -0
- package/src/agents/data-layer/tools/index-audit.js +165 -0
- package/src/agents/data-layer/tools/index.js +83 -0
- package/src/agents/data-layer/tools/migration-scan.js +135 -0
- package/src/agents/data-layer/tools/query-explain.js +120 -0
- package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
- package/src/agents/documentation/index.js +12 -0
- package/src/agents/documentation/tools/api-diff.js +91 -0
- package/src/agents/documentation/tools/base.js +151 -0
- package/src/agents/documentation/tools/dead-link-check.js +58 -0
- package/src/agents/documentation/tools/docstring-coverage.js +78 -0
- package/src/agents/documentation/tools/index.js +52 -0
- package/src/agents/documentation/tools/readme-freshness.js +61 -0
- package/src/agents/envelope/fix-cycle.js +45 -0
- package/src/agents/envelope/index.js +31 -0
- package/src/agents/envelope/loop.js +150 -0
- package/src/agents/envelope/pulse.js +18 -0
- package/src/agents/envelope/stream.js +40 -0
- package/src/agents/infrastructure/index.js +12 -0
- package/src/agents/infrastructure/tools/base.js +171 -0
- package/src/agents/infrastructure/tools/checkov-run.js +32 -0
- package/src/agents/infrastructure/tools/drift-detect.js +59 -0
- package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
- package/src/agents/infrastructure/tools/index.js +52 -0
- package/src/agents/infrastructure/tools/tflint-run.js +31 -0
- package/src/agents/jules/config/definition.js +160 -160
- package/src/agents/jules/config/system-prompt.js +182 -182
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +17 -17
- package/src/agents/jules/loop.js +460 -450
- package/src/agents/jules/pulse.js +10 -10
- package/src/agents/jules/stream.js +187 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +315 -309
- package/src/agents/jules/tools/aidenid-email.js +189 -189
- package/src/agents/jules/tools/auth-audit.js +1708 -1691
- package/src/agents/jules/tools/dispatch.js +340 -335
- package/src/agents/jules/tools/file-edit.js +2 -2
- package/src/agents/jules/tools/file-read.js +2 -2
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +2 -2
- package/src/agents/jules/tools/grep.js +2 -2
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +2 -2
- package/src/agents/jules/tools/runtime-audit.js +507 -507
- package/src/agents/jules/tools/shell.js +2 -2
- package/src/agents/jules/tools/url-policy.js +100 -100
- package/src/agents/mode.js +113 -0
- package/src/agents/observability/index.js +12 -0
- package/src/agents/observability/tools/alert-audit.js +39 -0
- package/src/agents/observability/tools/base.js +181 -0
- package/src/agents/observability/tools/dashboard-gap.js +42 -0
- package/src/agents/observability/tools/index.js +54 -0
- package/src/agents/observability/tools/log-schema-check.js +74 -0
- package/src/agents/observability/tools/span-coverage.js +74 -0
- package/src/agents/persona-visuals.js +102 -61
- package/src/agents/release/index.js +12 -0
- package/src/agents/release/tools/base.js +181 -0
- package/src/agents/release/tools/changelog-diff.js +86 -0
- package/src/agents/release/tools/feature-flag-audit.js +126 -0
- package/src/agents/release/tools/index.js +61 -0
- package/src/agents/release/tools/rollback-verify.js +129 -0
- package/src/agents/release/tools/semver-check.js +109 -0
- package/src/agents/reliability/index.js +12 -0
- package/src/agents/reliability/tools/backpressure-check.js +129 -0
- package/src/agents/reliability/tools/base.js +181 -0
- package/src/agents/reliability/tools/chaos-probe.js +109 -0
- package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
- package/src/agents/reliability/tools/health-check-audit.js +111 -0
- package/src/agents/reliability/tools/index.js +87 -0
- package/src/agents/run-persona.js +109 -0
- package/src/agents/security/index.js +12 -0
- package/src/agents/security/tools/authz-audit.js +134 -0
- package/src/agents/security/tools/base.js +190 -0
- package/src/agents/security/tools/crypto-review.js +175 -0
- package/src/agents/security/tools/index.js +97 -0
- package/src/agents/security/tools/sast-scan.js +175 -0
- package/src/agents/security/tools/secrets-scan.js +216 -0
- package/src/agents/shared-tools/dispatch-core.js +320 -315
- package/src/agents/shared-tools/file-edit.js +180 -180
- package/src/agents/shared-tools/file-read.js +100 -100
- package/src/agents/shared-tools/glob.js +168 -168
- package/src/agents/shared-tools/grep.js +228 -228
- package/src/agents/shared-tools/index.js +46 -46
- package/src/agents/shared-tools/path-guards.js +161 -161
- package/src/agents/shared-tools/shell.js +383 -383
- package/src/agents/supply-chain/index.js +12 -0
- package/src/agents/supply-chain/tools/attestation-check.js +42 -0
- package/src/agents/supply-chain/tools/base.js +151 -0
- package/src/agents/supply-chain/tools/index.js +52 -0
- package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
- package/src/agents/supply-chain/tools/package-verify.js +56 -0
- package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
- package/src/agents/testing/index.js +12 -0
- package/src/agents/testing/tools/base.js +202 -0
- package/src/agents/testing/tools/coverage-gap.js +144 -0
- package/src/agents/testing/tools/flake-detect.js +125 -0
- package/src/agents/testing/tools/index.js +85 -0
- package/src/agents/testing/tools/mutation-test.js +143 -0
- package/src/agents/testing/tools/snapshot-diff.js +103 -0
- package/src/ai/aidenid.js +1021 -1009
- package/src/ai/client.js +553 -553
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/proxy.js +137 -137
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +428 -371
- package/src/auth/http.js +681 -611
- package/src/auth/service.js +1106 -1106
- package/src/auth/session-store.js +813 -813
- package/src/cli.js +257 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1338
- package/src/commands/ai/provision-governance.js +1272 -1272
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1171 -1166
- package/src/commands/auth.js +419 -419
- package/src/commands/chat.js +184 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +20 -10
- package/src/commands/mcp.js +461 -461
- package/src/commands/omargate.js +63 -29
- package/src/commands/persona.js +65 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +865 -872
- package/src/commands/session.js +1238 -0
- package/src/commands/spec.js +771 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +511 -511
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/coord/events-log.js +141 -0
- package/src/coord/handshake.js +719 -0
- package/src/coord/index.js +35 -0
- package/src/coord/paths.js +84 -0
- package/src/coord/priority.js +62 -0
- package/src/coord/tarjan.js +157 -0
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tokenizer.js +160 -0
- package/src/cost/tracker.js +232 -171
- package/src/daemon/artifact-lineage.js +896 -534
- package/src/daemon/assignment-ledger.js +1083 -770
- package/src/daemon/ast-drift.js +496 -0
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +1209 -626
- package/src/daemon/fix-cycle.js +384 -377
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/ingest-refresh.js +79 -11
- package/src/daemon/jira-lifecycle.js +767 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/pulse.js +327 -327
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/scope-engine.js +1068 -0
- package/src/daemon/watchdog.js +971 -971
- package/src/events/schema.js +190 -0
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +933 -918
- package/src/ingest/ownership.js +380 -0
- package/src/interactive/index.js +97 -97
- package/src/legacy-cli.js +3228 -2994
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/orchestrator/kai-chen.js +126 -0
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +136 -118
- package/src/review/ai-review.js +672 -679
- package/src/review/compliance-pack.js +389 -0
- package/src/review/investor-dd-config.js +54 -0
- package/src/review/investor-dd-file-loop.js +303 -0
- package/src/review/investor-dd-file-router.js +406 -0
- package/src/review/investor-dd-html-report.js +233 -0
- package/src/review/investor-dd-notification.js +120 -0
- package/src/review/investor-dd-orchestrator.js +405 -0
- package/src/review/investor-dd-persona-runner.js +275 -0
- package/src/review/live-validator.js +253 -0
- package/src/review/local-review.js +1351 -1305
- package/src/review/omargate-interactive.js +68 -68
- package/src/review/omargate-orchestrator.js +492 -300
- package/src/review/persona-prompts.js +484 -296
- package/src/review/reconciliation-rules.js +329 -0
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/reproducibility-chain.js +136 -0
- package/src/review/scan-modes.js +147 -42
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -67
- package/src/scaffold/templates.js +150 -150
- package/src/scan/generator.js +418 -418
- package/src/scan/gh-secrets.js +107 -107
- package/src/session/agent-registry.js +359 -0
- package/src/session/analytics.js +479 -0
- package/src/session/daemon.js +1396 -0
- package/src/session/file-locks.js +666 -0
- package/src/session/paths.js +37 -0
- package/src/session/recap.js +567 -0
- package/src/session/redact.js +82 -0
- package/src/session/runtime-bridge.js +762 -0
- package/src/session/scoring.js +406 -0
- package/src/session/setup-guides.js +304 -0
- package/src/session/store.js +704 -0
- package/src/session/stream.js +333 -0
- package/src/session/sync.js +753 -0
- package/src/session/tasks.js +1054 -0
- package/src/session/templates.js +188 -0
- package/src/spec/generator.js +619 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +569 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/session-tracker.js +234 -234
- package/src/telemetry/sync.js +203 -203
- package/src/ui/command-hints.js +13 -13
- package/src/ui/markdown.js +220 -220
package/src/auth/gate.js
CHANGED
|
@@ -1,371 +1,428 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import pc from "picocolors";
|
|
7
|
-
import { resolveActiveAuthSession } from "./service.js";
|
|
8
|
-
import { authLoginHint } from "../ui/command-hints.js";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Auth gate — ensures user is logged in before running any command.
|
|
12
|
-
*
|
|
13
|
-
* Commands that bypass auth:
|
|
14
|
-
* - auth login, auth status, auth --help
|
|
15
|
-
* - --help, --version, -h, -v
|
|
16
|
-
* - config (read-only config inspection)
|
|
17
|
-
*
|
|
18
|
-
* All other commands require a valid session.
|
|
19
|
-
* If not authenticated, prints instructions and exits.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
const AUTH_BYPASS_COMMANDS = new Set([
|
|
23
|
-
"auth", // auth subcommands handle their own auth
|
|
24
|
-
"help", // help must work without login so agents can discover commands
|
|
25
|
-
"--help",
|
|
26
|
-
"-h",
|
|
27
|
-
"--version",
|
|
28
|
-
"-v",
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
// Commands that work without auth (read-only, no API calls)
|
|
32
|
-
const NO_AUTH_REQUIRED = new Set([
|
|
33
|
-
"config", // local config inspection
|
|
34
|
-
]);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"--
|
|
73
|
-
"--
|
|
74
|
-
"--
|
|
75
|
-
"--
|
|
76
|
-
"--
|
|
77
|
-
"--
|
|
78
|
-
"--
|
|
79
|
-
"--
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
fs.
|
|
159
|
-
} catch {
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
!
|
|
178
|
-
!
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
return
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import { resolveActiveAuthSession } from "./service.js";
|
|
8
|
+
import { authLoginHint } from "../ui/command-hints.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Auth gate — ensures user is logged in before running any command.
|
|
12
|
+
*
|
|
13
|
+
* Commands that bypass auth:
|
|
14
|
+
* - auth login, auth status, auth --help
|
|
15
|
+
* - --help, --version, -h, -v
|
|
16
|
+
* - config (read-only config inspection)
|
|
17
|
+
*
|
|
18
|
+
* All other commands require a valid session.
|
|
19
|
+
* If not authenticated, prints instructions and exits.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const AUTH_BYPASS_COMMANDS = new Set([
|
|
23
|
+
"auth", // auth subcommands handle their own auth
|
|
24
|
+
"help", // help must work without login so agents can discover commands
|
|
25
|
+
"--help",
|
|
26
|
+
"-h",
|
|
27
|
+
"--version",
|
|
28
|
+
"-v",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
// Commands that work without auth (read-only, no API calls)
|
|
32
|
+
const NO_AUTH_REQUIRED = new Set([
|
|
33
|
+
"config", // local config inspection
|
|
34
|
+
]);
|
|
35
|
+
const SESSION_NO_AUTH_SUBCOMMANDS = new Set([
|
|
36
|
+
"read",
|
|
37
|
+
"list",
|
|
38
|
+
"status",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const TEST_BYPASS_NONCE_ENV = "SENTINELAYER_CLI_TEST_BYPASS_NONCE";
|
|
42
|
+
const TEST_BYPASS_SECRET_ENV = "SENTINELAYER_CLI_TEST_BYPASS_SECRET";
|
|
43
|
+
const TEST_BYPASS_TOKEN_ENV = "SENTINELAYER_CLI_TEST_BYPASS_TOKEN";
|
|
44
|
+
const TEST_BYPASS_NONCE_FILENAME_PREFIX = "sentinelayer-cli-test-bypass";
|
|
45
|
+
const TEST_BYPASS_NONCE_MAX_AGE_MS = 5 * 60 * 1000;
|
|
46
|
+
const TEST_BYPASS_ALLOWED_EXECUTABLES = new Set([
|
|
47
|
+
"create-sentinelayer.js",
|
|
48
|
+
"sentinelayer-cli.js",
|
|
49
|
+
"sl.js",
|
|
50
|
+
"cli.js",
|
|
51
|
+
]);
|
|
52
|
+
const TEST_BYPASS_ALLOWED_COMMANDS = new Set([
|
|
53
|
+
"audit",
|
|
54
|
+
"chat",
|
|
55
|
+
"config",
|
|
56
|
+
"cost",
|
|
57
|
+
"daemon",
|
|
58
|
+
"guide",
|
|
59
|
+
"ingest",
|
|
60
|
+
"mcp",
|
|
61
|
+
"plugin",
|
|
62
|
+
"policy",
|
|
63
|
+
"prompt",
|
|
64
|
+
"review",
|
|
65
|
+
"scan",
|
|
66
|
+
"spec",
|
|
67
|
+
"swarm",
|
|
68
|
+
"telemetry",
|
|
69
|
+
"watch",
|
|
70
|
+
]);
|
|
71
|
+
const TEST_BYPASS_BLOCKED_FLAGS = new Set([
|
|
72
|
+
"--apply",
|
|
73
|
+
"--delete",
|
|
74
|
+
"--deploy",
|
|
75
|
+
"--destroy",
|
|
76
|
+
"--execute",
|
|
77
|
+
"--fix",
|
|
78
|
+
"--force",
|
|
79
|
+
"--merge",
|
|
80
|
+
"--promote",
|
|
81
|
+
"--publish",
|
|
82
|
+
"--push",
|
|
83
|
+
"--regenerate",
|
|
84
|
+
"--revoke",
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
function isTruthy(value) {
|
|
88
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
89
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isPackagedBuild() {
|
|
93
|
+
if (process.pkg) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
const execPath = String(process.execPath || "");
|
|
97
|
+
const execBase = path.basename(execPath).toLowerCase();
|
|
98
|
+
return execBase !== "node" && execBase !== "node.exe";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function readNonceEnvelope(nonce) {
|
|
102
|
+
const normalizedNonce = String(nonce || "").trim();
|
|
103
|
+
if (!normalizedNonce) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const nonceFile = path.join(os.tmpdir(), `${TEST_BYPASS_NONCE_FILENAME_PREFIX}-${normalizedNonce}.nonce`);
|
|
107
|
+
try {
|
|
108
|
+
const stats = fs.statSync(nonceFile);
|
|
109
|
+
if (!stats.isFile()) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (typeof process.getuid === "function") {
|
|
113
|
+
if (stats.uid !== process.getuid()) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (stats.mode & 0o022) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (stats.size <= 0 || stats.size > 1024) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const payloadRaw = fs.readFileSync(nonceFile, "utf-8");
|
|
124
|
+
const payload = JSON.parse(payloadRaw);
|
|
125
|
+
const payloadNonce = String(payload?.nonce || "").trim();
|
|
126
|
+
const payloadPid = Number(payload?.pid);
|
|
127
|
+
const payloadTs = Number(payload?.ts);
|
|
128
|
+
if (payloadNonce !== normalizedNonce) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
if (!Number.isInteger(payloadPid) || payloadPid <= 0) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (!Number.isFinite(payloadTs) || payloadTs <= 0) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
if (Math.abs(Date.now() - payloadTs) > TEST_BYPASS_NONCE_MAX_AGE_MS) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
nonce: payloadNonce,
|
|
142
|
+
pid: payloadPid,
|
|
143
|
+
ts: payloadTs,
|
|
144
|
+
nonceFile,
|
|
145
|
+
};
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function consumeNonceEnvelope(nonceFile) {
|
|
152
|
+
const normalizedPath = String(nonceFile || "").trim();
|
|
153
|
+
if (!normalizedPath) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const consumedPath = `${normalizedPath}.used.${process.pid}.${Date.now()}`;
|
|
157
|
+
try {
|
|
158
|
+
fs.renameSync(normalizedPath, consumedPath);
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
fs.rmSync(consumedPath, { force: true });
|
|
164
|
+
} catch {
|
|
165
|
+
// Best effort cleanup only.
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isValidTestBypassToken({ nonce, pid, ts, secret, token }) {
|
|
171
|
+
const normalizedNonce = String(nonce || "").trim();
|
|
172
|
+
const normalizedPid = Number(pid);
|
|
173
|
+
const normalizedTs = Number(ts);
|
|
174
|
+
const normalizedSecret = String(secret || "").trim();
|
|
175
|
+
const rawToken = String(token || "").trim();
|
|
176
|
+
if (
|
|
177
|
+
!normalizedNonce ||
|
|
178
|
+
!Number.isInteger(normalizedPid) ||
|
|
179
|
+
normalizedPid <= 0 ||
|
|
180
|
+
!Number.isFinite(normalizedTs) ||
|
|
181
|
+
normalizedTs <= 0 ||
|
|
182
|
+
!normalizedSecret ||
|
|
183
|
+
!rawToken
|
|
184
|
+
) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
const normalizedToken = rawToken.replace(/^sha256:/i, "");
|
|
188
|
+
if (!/^[a-f0-9]{64}$/i.test(normalizedToken)) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
const message = `${normalizedNonce}|${normalizedPid}|${normalizedTs}`;
|
|
192
|
+
const computed = crypto.createHmac("sha256", normalizedSecret).update(message).digest("hex");
|
|
193
|
+
const expectedBuffer = Buffer.from(computed, "hex");
|
|
194
|
+
const candidateBuffer = Buffer.from(normalizedToken.toLowerCase(), "hex");
|
|
195
|
+
if (expectedBuffer.length !== candidateBuffer.length) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return crypto.timingSafeEqual(expectedBuffer, candidateBuffer);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isBypassCommandAllowed(args = []) {
|
|
202
|
+
const first = String(args[0] || "").trim().toLowerCase();
|
|
203
|
+
if (!first) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
if (first.startsWith("/")) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
if (!TEST_BYPASS_ALLOWED_COMMANDS.has(first)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
for (const rawArg of args.slice(1)) {
|
|
213
|
+
const arg = String(rawArg || "").trim().toLowerCase();
|
|
214
|
+
if (!arg.startsWith("--")) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const normalizedFlag = arg.split("=")[0];
|
|
218
|
+
if (TEST_BYPASS_BLOCKED_FLAGS.has(normalizedFlag)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function firstPositionalArg(args = [], startIndex = 0) {
|
|
226
|
+
for (const rawArg of args.slice(startIndex)) {
|
|
227
|
+
const normalized = String(rawArg || "").trim().toLowerCase();
|
|
228
|
+
if (!normalized || normalized.startsWith("-")) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
return normalized;
|
|
232
|
+
}
|
|
233
|
+
return "";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function isSessionNoAuthCommand(args = []) {
|
|
237
|
+
const first = String(args[0] || "").trim().toLowerCase();
|
|
238
|
+
if (first !== "session") {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
const subcommand = firstPositionalArg(args, 1);
|
|
242
|
+
return SESSION_NO_AUTH_SUBCOMMANDS.has(subcommand);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function hasTrustedBypassExecutableContext() {
|
|
246
|
+
const argvPath = String(process.argv[1] || "").trim();
|
|
247
|
+
if (!argvPath) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const executableName = path.basename(argvPath).toLowerCase();
|
|
251
|
+
if (!TEST_BYPASS_ALLOWED_EXECUTABLES.has(executableName)) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const normalizedPath = argvPath.replace(/\\/g, "/").toLowerCase();
|
|
255
|
+
if (!normalizedPath.includes("/bin/") && !normalizedPath.endsWith("/src/cli.js")) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function hasTrustedBypassContext(args = []) {
|
|
262
|
+
if (isTruthy(process.env.CI)) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
if (process.env.NODE_ENV !== "test" || process.env.SENTINELAYER_CLI_TEST_MODE !== "1") {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
if (isPackagedBuild()) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (!hasTrustedBypassExecutableContext()) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
if (!isBypassCommandAllowed(args)) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
const nonceEnvelope = readNonceEnvelope(process.env[TEST_BYPASS_NONCE_ENV]);
|
|
278
|
+
if (!nonceEnvelope) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
if (
|
|
282
|
+
!isValidTestBypassToken({
|
|
283
|
+
nonce: nonceEnvelope.nonce,
|
|
284
|
+
pid: nonceEnvelope.pid,
|
|
285
|
+
ts: nonceEnvelope.ts,
|
|
286
|
+
secret: process.env[TEST_BYPASS_SECRET_ENV],
|
|
287
|
+
token: process.env[TEST_BYPASS_TOKEN_ENV],
|
|
288
|
+
})
|
|
289
|
+
) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
return consumeNonceEnvelope(nonceEnvelope.nonceFile);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isSessionUnexpired(tokenExpiresAt) {
|
|
296
|
+
const normalized = String(tokenExpiresAt || "").trim();
|
|
297
|
+
if (!normalized) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const expiresAt = new Date(normalized).getTime();
|
|
301
|
+
if (!Number.isFinite(expiresAt)) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
return expiresAt >= Date.now();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Gate-level session validation.
|
|
308
|
+
//
|
|
309
|
+
// Design principle: the gate is a "do they have a token?" check, not a
|
|
310
|
+
// "is the token cryptographically well-formed?" check. Server-side /auth/me
|
|
311
|
+
// and per-call bearer validation are the authoritative gate on the token
|
|
312
|
+
// material itself. Over-strict client-side checks (ASCII-only, exact-prefix
|
|
313
|
+
// inclusion, etc.) surface as "Authentication required" even when the user
|
|
314
|
+
// has a perfectly valid keyring entry, forcing them to logout/login repeatedly
|
|
315
|
+
// without fixing anything.
|
|
316
|
+
//
|
|
317
|
+
// So the gate checks:
|
|
318
|
+
// - session.token is present and non-empty
|
|
319
|
+
// - for source === "session", expiry is in the future
|
|
320
|
+
// - for source === "env" or "config", the downstream API call is the gate
|
|
321
|
+
function isAuthenticatedSessionValid(session) {
|
|
322
|
+
const token = String(session?.token || "").trim();
|
|
323
|
+
if (!token) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
if (String(session?.source || "").trim() === "session") {
|
|
327
|
+
return isSessionUnexpired(session?.tokenExpiresAt);
|
|
328
|
+
}
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Check if the current command requires authentication.
|
|
334
|
+
* Returns true if auth is required but user is not logged in.
|
|
335
|
+
*
|
|
336
|
+
* @param {string[]} args - CLI arguments (after normalization)
|
|
337
|
+
* @returns {Promise<{ authenticated: boolean, session: object|null, bypassReason: string|null, failureReason: string|null }>}
|
|
338
|
+
*/
|
|
339
|
+
export async function checkAuthGate(args) {
|
|
340
|
+
const first = String(args[0] || "").trim().toLowerCase();
|
|
341
|
+
|
|
342
|
+
if (!first || AUTH_BYPASS_COMMANDS.has(first)) {
|
|
343
|
+
return { authenticated: true, session: null, bypassReason: "auth_bypass_command", failureReason: null };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (NO_AUTH_REQUIRED.has(first)) {
|
|
347
|
+
return { authenticated: true, session: null, bypassReason: "no_auth_required", failureReason: null };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (isSessionNoAuthCommand(args)) {
|
|
351
|
+
return { authenticated: true, session: null, bypassReason: "session_no_auth_required", failureReason: null };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (process.env.SENTINELAYER_CLI_SKIP_AUTH === "1" && hasTrustedBypassContext(args)) {
|
|
355
|
+
return { authenticated: true, session: null, bypassReason: "env_bypass_guarded", failureReason: null };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Check for active auth session across env -> config -> stored session.
|
|
359
|
+
let resolveError = null;
|
|
360
|
+
try {
|
|
361
|
+
const session = await resolveActiveAuthSession({
|
|
362
|
+
cwd: process.cwd(),
|
|
363
|
+
env: process.env,
|
|
364
|
+
autoRotate: false,
|
|
365
|
+
});
|
|
366
|
+
if (session && isAuthenticatedSessionValid(session)) {
|
|
367
|
+
return { authenticated: true, session, bypassReason: null, failureReason: null };
|
|
368
|
+
}
|
|
369
|
+
if (session) {
|
|
370
|
+
// Session resolved but failed validation (empty token or expired).
|
|
371
|
+
const tokenPresent = Boolean(String(session?.token || "").trim());
|
|
372
|
+
if (!tokenPresent) {
|
|
373
|
+
resolveError = "session_token_missing";
|
|
374
|
+
} else if (String(session?.source || "").trim() === "session" && !isSessionUnexpired(session?.tokenExpiresAt)) {
|
|
375
|
+
resolveError = "session_expired";
|
|
376
|
+
} else {
|
|
377
|
+
resolveError = "session_invalid";
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
resolveError = "no_session";
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
resolveError = error instanceof Error ? `session_read_error: ${error.message}` : "session_read_error";
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return { authenticated: false, session: null, bypassReason: null, failureReason: resolveError };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Print auth required message and exit. Optional failureReason surfaces the
|
|
391
|
+
* specific reason so users can diagnose stale sessions, expired tokens, and
|
|
392
|
+
* keyring failures without a round trip.
|
|
393
|
+
*/
|
|
394
|
+
export function printAuthRequired(failureReason = null) {
|
|
395
|
+
const reason = String(failureReason || "").trim();
|
|
396
|
+
console.error("");
|
|
397
|
+
console.error(pc.bold(pc.red("Authentication required.")));
|
|
398
|
+
console.error("");
|
|
399
|
+
if (reason === "session_expired") {
|
|
400
|
+
console.error(" Your stored session has expired. Log in again:");
|
|
401
|
+
} else if (reason === "session_token_missing") {
|
|
402
|
+
console.error(" Your session metadata is present but the token is missing");
|
|
403
|
+
console.error(" (likely a keyring read failure or mismatched storage).");
|
|
404
|
+
console.error("");
|
|
405
|
+
console.error(" " + pc.yellow("Fix:") + " log out to clear the stale metadata, then log in:");
|
|
406
|
+
console.error(" " + pc.cyan("sentinelayer-cli auth logout"));
|
|
407
|
+
} else if (reason && reason.startsWith("session_read_error")) {
|
|
408
|
+
console.error(" Session read failed: " + pc.yellow(reason.replace(/^session_read_error:\s*/, "")));
|
|
409
|
+
console.error(" Log out and back in to reset local state:");
|
|
410
|
+
console.error(" " + pc.cyan("sentinelayer-cli auth logout"));
|
|
411
|
+
} else {
|
|
412
|
+
console.error(" Log in to SentinelLayer to use CLI commands:");
|
|
413
|
+
}
|
|
414
|
+
console.error("");
|
|
415
|
+
console.error(" " + pc.cyan(authLoginHint()));
|
|
416
|
+
console.error("");
|
|
417
|
+
console.error(" This opens your browser to authenticate via GitHub or Google.");
|
|
418
|
+
console.error(" Your session is encrypted and stored locally.");
|
|
419
|
+
console.error("");
|
|
420
|
+
if (!reason || reason === "no_session") {
|
|
421
|
+
console.error(" " + pc.gray("Why? All CLI operations sync to your SentinelLayer account —"));
|
|
422
|
+
console.error(" " + pc.gray("audit reports, findings, cost tracking, and run history."));
|
|
423
|
+
} else {
|
|
424
|
+
console.error(" " + pc.gray(`Diagnostic: ${reason}`));
|
|
425
|
+
}
|
|
426
|
+
console.error("");
|
|
427
|
+
process.exitCode = 1;
|
|
428
|
+
}
|