sentinelayer-cli 0.4.4 → 0.6.2
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 +996 -998
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +63 -63
- package/src/agents/jules/config/definition.js +160 -209
- package/src/agents/jules/config/system-prompt.js +182 -175
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +17 -377
- package/src/agents/jules/loop.js +450 -367
- package/src/agents/jules/pulse.js +10 -327
- package/src/agents/jules/stream.js +186 -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 +309 -308
- package/src/agents/jules/tools/aidenid-email.js +189 -0
- package/src/agents/jules/tools/auth-audit.js +1691 -557
- package/src/agents/jules/tools/dispatch.js +335 -327
- package/src/agents/jules/tools/file-edit.js +2 -180
- package/src/agents/jules/tools/file-read.js +2 -100
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +2 -168
- package/src/agents/jules/tools/grep.js +2 -228
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +2 -161
- package/src/agents/jules/tools/runtime-audit.js +507 -503
- package/src/agents/jules/tools/shell.js +2 -383
- package/src/agents/jules/tools/url-policy.js +100 -100
- package/src/agents/persona-visuals.js +61 -0
- package/src/agents/shared-tools/dispatch-core.js +315 -0
- package/src/agents/shared-tools/file-edit.js +180 -0
- package/src/agents/shared-tools/file-read.js +100 -0
- package/src/agents/shared-tools/glob.js +168 -0
- package/src/agents/shared-tools/grep.js +228 -0
- package/src/agents/shared-tools/index.js +46 -0
- package/src/agents/shared-tools/path-guards.js +161 -0
- package/src/agents/shared-tools/shell.js +383 -0
- package/src/ai/aidenid.js +1009 -972
- package/src/ai/client.js +553 -508
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/proxy.js +137 -0
- 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 +371 -126
- package/src/auth/http.js +611 -270
- package/src/auth/service.js +1106 -891
- package/src/auth/session-store.js +813 -359
- package/src/cli.js +252 -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 +1166 -1166
- package/src/commands/auth.js +419 -375
- package/src/commands/chat.js +191 -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 +10 -10
- package/src/commands/mcp.js +461 -461
- package/src/commands/omargate.js +29 -21
- package/src/commands/persona.js +20 -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 +872 -866
- package/src/commands/spec.js +716 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +511 -510
- 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/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +770 -770
- 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 +626 -626
- package/src/daemon/fix-cycle.js +377 -0
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/jira-lifecycle.js +632 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/pulse.js +327 -0
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/watchdog.js +971 -971
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/interactive/index.js +97 -95
- package/src/legacy-cli.js +2994 -2592
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +118 -118
- package/src/review/ai-review.js +679 -669
- package/src/review/local-review.js +1305 -1295
- package/src/review/omargate-interactive.js +68 -0
- package/src/review/omargate-orchestrator.js +300 -0
- package/src/review/persona-prompts.js +296 -0
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/scan-modes.js +42 -0
- 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/spec/generator.js +519 -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 +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/session-tracker.js +234 -118
- package/src/telemetry/sync.js +203 -199
- package/src/ui/command-hints.js +13 -0
- package/src/ui/markdown.js +220 -220
package/src/auth/gate.js
CHANGED
|
@@ -1,126 +1,371 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
+
const TEST_BYPASS_NONCE_ENV = "SENTINELAYER_CLI_TEST_BYPASS_NONCE";
|
|
37
|
+
const TEST_BYPASS_SECRET_ENV = "SENTINELAYER_CLI_TEST_BYPASS_SECRET";
|
|
38
|
+
const TEST_BYPASS_TOKEN_ENV = "SENTINELAYER_CLI_TEST_BYPASS_TOKEN";
|
|
39
|
+
const TEST_BYPASS_NONCE_FILENAME_PREFIX = "sentinelayer-cli-test-bypass";
|
|
40
|
+
const TEST_BYPASS_NONCE_MAX_AGE_MS = 5 * 60 * 1000;
|
|
41
|
+
const TEST_BYPASS_ALLOWED_EXECUTABLES = new Set([
|
|
42
|
+
"create-sentinelayer.js",
|
|
43
|
+
"sentinelayer-cli.js",
|
|
44
|
+
"sl.js",
|
|
45
|
+
"cli.js",
|
|
46
|
+
]);
|
|
47
|
+
const TEST_BYPASS_ALLOWED_COMMANDS = new Set([
|
|
48
|
+
"audit",
|
|
49
|
+
"chat",
|
|
50
|
+
"config",
|
|
51
|
+
"cost",
|
|
52
|
+
"daemon",
|
|
53
|
+
"guide",
|
|
54
|
+
"ingest",
|
|
55
|
+
"mcp",
|
|
56
|
+
"plugin",
|
|
57
|
+
"policy",
|
|
58
|
+
"prompt",
|
|
59
|
+
"review",
|
|
60
|
+
"scan",
|
|
61
|
+
"spec",
|
|
62
|
+
"swarm",
|
|
63
|
+
"telemetry",
|
|
64
|
+
"watch",
|
|
65
|
+
]);
|
|
66
|
+
const TEST_BYPASS_BLOCKED_FLAGS = new Set([
|
|
67
|
+
"--apply",
|
|
68
|
+
"--delete",
|
|
69
|
+
"--deploy",
|
|
70
|
+
"--destroy",
|
|
71
|
+
"--execute",
|
|
72
|
+
"--fix",
|
|
73
|
+
"--force",
|
|
74
|
+
"--merge",
|
|
75
|
+
"--promote",
|
|
76
|
+
"--publish",
|
|
77
|
+
"--push",
|
|
78
|
+
"--regenerate",
|
|
79
|
+
"--revoke",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
function isTruthy(value) {
|
|
83
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
84
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isPackagedBuild() {
|
|
88
|
+
if (process.pkg) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
const execPath = String(process.execPath || "");
|
|
92
|
+
const execBase = path.basename(execPath).toLowerCase();
|
|
93
|
+
return execBase !== "node" && execBase !== "node.exe";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readNonceEnvelope(nonce) {
|
|
97
|
+
const normalizedNonce = String(nonce || "").trim();
|
|
98
|
+
if (!normalizedNonce) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const nonceFile = path.join(os.tmpdir(), `${TEST_BYPASS_NONCE_FILENAME_PREFIX}-${normalizedNonce}.nonce`);
|
|
102
|
+
try {
|
|
103
|
+
const stats = fs.statSync(nonceFile);
|
|
104
|
+
if (!stats.isFile()) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (typeof process.getuid === "function") {
|
|
108
|
+
if (stats.uid !== process.getuid()) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (stats.mode & 0o022) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (stats.size <= 0 || stats.size > 1024) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const payloadRaw = fs.readFileSync(nonceFile, "utf-8");
|
|
119
|
+
const payload = JSON.parse(payloadRaw);
|
|
120
|
+
const payloadNonce = String(payload?.nonce || "").trim();
|
|
121
|
+
const payloadPid = Number(payload?.pid);
|
|
122
|
+
const payloadTs = Number(payload?.ts);
|
|
123
|
+
if (payloadNonce !== normalizedNonce) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
if (!Number.isInteger(payloadPid) || payloadPid <= 0) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (!Number.isFinite(payloadTs) || payloadTs <= 0) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (Math.abs(Date.now() - payloadTs) > TEST_BYPASS_NONCE_MAX_AGE_MS) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
nonce: payloadNonce,
|
|
137
|
+
pid: payloadPid,
|
|
138
|
+
ts: payloadTs,
|
|
139
|
+
nonceFile,
|
|
140
|
+
};
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function consumeNonceEnvelope(nonceFile) {
|
|
147
|
+
const normalizedPath = String(nonceFile || "").trim();
|
|
148
|
+
if (!normalizedPath) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const consumedPath = `${normalizedPath}.used.${process.pid}.${Date.now()}`;
|
|
152
|
+
try {
|
|
153
|
+
fs.renameSync(normalizedPath, consumedPath);
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
fs.rmSync(consumedPath, { force: true });
|
|
159
|
+
} catch {
|
|
160
|
+
// Best effort cleanup only.
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isValidTestBypassToken({ nonce, pid, ts, secret, token }) {
|
|
166
|
+
const normalizedNonce = String(nonce || "").trim();
|
|
167
|
+
const normalizedPid = Number(pid);
|
|
168
|
+
const normalizedTs = Number(ts);
|
|
169
|
+
const normalizedSecret = String(secret || "").trim();
|
|
170
|
+
const rawToken = String(token || "").trim();
|
|
171
|
+
if (
|
|
172
|
+
!normalizedNonce ||
|
|
173
|
+
!Number.isInteger(normalizedPid) ||
|
|
174
|
+
normalizedPid <= 0 ||
|
|
175
|
+
!Number.isFinite(normalizedTs) ||
|
|
176
|
+
normalizedTs <= 0 ||
|
|
177
|
+
!normalizedSecret ||
|
|
178
|
+
!rawToken
|
|
179
|
+
) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const normalizedToken = rawToken.replace(/^sha256:/i, "");
|
|
183
|
+
if (!/^[a-f0-9]{64}$/i.test(normalizedToken)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
const message = `${normalizedNonce}|${normalizedPid}|${normalizedTs}`;
|
|
187
|
+
const computed = crypto.createHmac("sha256", normalizedSecret).update(message).digest("hex");
|
|
188
|
+
const expectedBuffer = Buffer.from(computed, "hex");
|
|
189
|
+
const candidateBuffer = Buffer.from(normalizedToken.toLowerCase(), "hex");
|
|
190
|
+
if (expectedBuffer.length !== candidateBuffer.length) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
return crypto.timingSafeEqual(expectedBuffer, candidateBuffer);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isBypassCommandAllowed(args = []) {
|
|
197
|
+
const first = String(args[0] || "").trim().toLowerCase();
|
|
198
|
+
if (!first) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (first.startsWith("/")) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (!TEST_BYPASS_ALLOWED_COMMANDS.has(first)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
for (const rawArg of args.slice(1)) {
|
|
208
|
+
const arg = String(rawArg || "").trim().toLowerCase();
|
|
209
|
+
if (!arg.startsWith("--")) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const normalizedFlag = arg.split("=")[0];
|
|
213
|
+
if (TEST_BYPASS_BLOCKED_FLAGS.has(normalizedFlag)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function hasTrustedBypassExecutableContext() {
|
|
221
|
+
const argvPath = String(process.argv[1] || "").trim();
|
|
222
|
+
if (!argvPath) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const executableName = path.basename(argvPath).toLowerCase();
|
|
226
|
+
if (!TEST_BYPASS_ALLOWED_EXECUTABLES.has(executableName)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const normalizedPath = argvPath.replace(/\\/g, "/").toLowerCase();
|
|
230
|
+
if (!normalizedPath.includes("/bin/") && !normalizedPath.endsWith("/src/cli.js")) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function hasTrustedBypassContext(args = []) {
|
|
237
|
+
if (isTruthy(process.env.CI)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (process.env.NODE_ENV !== "test" || process.env.SENTINELAYER_CLI_TEST_MODE !== "1") {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (isPackagedBuild()) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
if (!hasTrustedBypassExecutableContext()) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
if (!isBypassCommandAllowed(args)) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const nonceEnvelope = readNonceEnvelope(process.env[TEST_BYPASS_NONCE_ENV]);
|
|
253
|
+
if (!nonceEnvelope) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (
|
|
257
|
+
!isValidTestBypassToken({
|
|
258
|
+
nonce: nonceEnvelope.nonce,
|
|
259
|
+
pid: nonceEnvelope.pid,
|
|
260
|
+
ts: nonceEnvelope.ts,
|
|
261
|
+
secret: process.env[TEST_BYPASS_SECRET_ENV],
|
|
262
|
+
token: process.env[TEST_BYPASS_TOKEN_ENV],
|
|
263
|
+
})
|
|
264
|
+
) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return consumeNonceEnvelope(nonceEnvelope.nonceFile);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isValidSessionToken(session) {
|
|
271
|
+
const token = String(session?.token || "");
|
|
272
|
+
if (!token || token !== token.trim()) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (/\s/.test(token)) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
// Require printable ASCII only for bearer token material in local metadata.
|
|
279
|
+
if (/[^\x21-\x7E]/.test(token)) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
const tokenPrefix = String(session?.tokenPrefix || "").trim();
|
|
283
|
+
if (tokenPrefix && !token.includes(tokenPrefix)) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isSessionUnexpired(tokenExpiresAt) {
|
|
290
|
+
const normalized = String(tokenExpiresAt || "").trim();
|
|
291
|
+
if (!normalized) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
const expiresAt = new Date(normalized).getTime();
|
|
295
|
+
if (!Number.isFinite(expiresAt)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return expiresAt >= Date.now();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isAuthenticatedSessionValid(session) {
|
|
302
|
+
if (!isValidSessionToken(session)) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Persisted sessions must include a valid expiry bound. Env/config tokens
|
|
307
|
+
// are accepted as active auth sources and validated downstream by API calls.
|
|
308
|
+
if (String(session?.source || "").trim() === "session") {
|
|
309
|
+
return isSessionUnexpired(session?.tokenExpiresAt);
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Check if the current command requires authentication.
|
|
316
|
+
* Returns true if auth is required but user is not logged in.
|
|
317
|
+
*
|
|
318
|
+
* @param {string[]} args - CLI arguments (after normalization)
|
|
319
|
+
* @returns {Promise<{ authenticated: boolean, session: object|null, bypassReason: string|null }>}
|
|
320
|
+
*/
|
|
321
|
+
export async function checkAuthGate(args) {
|
|
322
|
+
const first = String(args[0] || "").trim().toLowerCase();
|
|
323
|
+
|
|
324
|
+
if (!first || AUTH_BYPASS_COMMANDS.has(first)) {
|
|
325
|
+
return { authenticated: true, session: null, bypassReason: "auth_bypass_command" };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (NO_AUTH_REQUIRED.has(first)) {
|
|
329
|
+
return { authenticated: true, session: null, bypassReason: "no_auth_required" };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (process.env.SENTINELAYER_CLI_SKIP_AUTH === "1" && hasTrustedBypassContext(args)) {
|
|
333
|
+
return { authenticated: true, session: null, bypassReason: "env_bypass_guarded" };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check for active auth session across env -> config -> stored session.
|
|
337
|
+
try {
|
|
338
|
+
const session = await resolveActiveAuthSession({
|
|
339
|
+
cwd: process.cwd(),
|
|
340
|
+
env: process.env,
|
|
341
|
+
autoRotate: false,
|
|
342
|
+
});
|
|
343
|
+
if (session && isAuthenticatedSessionValid(session)) {
|
|
344
|
+
return { authenticated: true, session, bypassReason: null };
|
|
345
|
+
}
|
|
346
|
+
} catch {
|
|
347
|
+
// Session read failed — treat as not authenticated
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return { authenticated: false, session: null, bypassReason: null };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Print auth required message and exit.
|
|
355
|
+
*/
|
|
356
|
+
export function printAuthRequired() {
|
|
357
|
+
console.error("");
|
|
358
|
+
console.error(pc.bold(pc.red("Authentication required.")));
|
|
359
|
+
console.error("");
|
|
360
|
+
console.error(" Log in to SentinelLayer to use CLI commands:");
|
|
361
|
+
console.error("");
|
|
362
|
+
console.error(" " + pc.cyan(authLoginHint()));
|
|
363
|
+
console.error("");
|
|
364
|
+
console.error(" This opens your browser to authenticate via GitHub or Google.");
|
|
365
|
+
console.error(" Your session is encrypted and stored locally.");
|
|
366
|
+
console.error("");
|
|
367
|
+
console.error(" " + pc.gray("Why? All CLI operations sync to your SentinelLayer account —"));
|
|
368
|
+
console.error(" " + pc.gray("audit reports, findings, cost tracking, and run history."));
|
|
369
|
+
console.error("");
|
|
370
|
+
process.exitCode = 1;
|
|
371
|
+
}
|