@node9/proxy 1.5.0 → 1.5.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/dist/cli.js +616 -246
- package/dist/cli.mjs +596 -227
- package/dist/index.js +222 -35
- package/dist/index.mjs +218 -25
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -30,7 +30,52 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
32
|
|
|
33
|
+
// src/audit/hasher.ts
|
|
34
|
+
function canonicalise(value) {
|
|
35
|
+
return _canonicalise(value, /* @__PURE__ */ new WeakSet());
|
|
36
|
+
}
|
|
37
|
+
function _canonicalise(value, seen) {
|
|
38
|
+
if (value === null || typeof value !== "object") return value;
|
|
39
|
+
if (value instanceof Date) return value.toISOString();
|
|
40
|
+
if (value instanceof RegExp) return value.toString();
|
|
41
|
+
if (Buffer.isBuffer(value)) return value.toString("base64");
|
|
42
|
+
if (seen.has(value)) return "[Circular]";
|
|
43
|
+
seen.add(value);
|
|
44
|
+
let result;
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
result = value.map((v) => _canonicalise(v, seen));
|
|
47
|
+
} else {
|
|
48
|
+
const obj = value;
|
|
49
|
+
result = Object.fromEntries(
|
|
50
|
+
Object.keys(obj).sort().map((k) => [k, _canonicalise(obj[k], seen)])
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
seen.delete(value);
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
function hashArgs(args) {
|
|
57
|
+
const canonical = JSON.stringify(canonicalise(args) ?? null);
|
|
58
|
+
return (0, import_crypto.createHash)("sha256").update(canonical).digest("hex").slice(0, 32);
|
|
59
|
+
}
|
|
60
|
+
var import_crypto;
|
|
61
|
+
var init_hasher = __esm({
|
|
62
|
+
"src/audit/hasher.ts"() {
|
|
63
|
+
"use strict";
|
|
64
|
+
import_crypto = require("crypto");
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
33
68
|
// src/audit/index.ts
|
|
69
|
+
var audit_exports = {};
|
|
70
|
+
__export(audit_exports, {
|
|
71
|
+
HOOK_DEBUG_LOG: () => HOOK_DEBUG_LOG,
|
|
72
|
+
LOCAL_AUDIT_LOG: () => LOCAL_AUDIT_LOG,
|
|
73
|
+
appendConfigAudit: () => appendConfigAudit,
|
|
74
|
+
appendHookDebug: () => appendHookDebug,
|
|
75
|
+
appendLocalAudit: () => appendLocalAudit,
|
|
76
|
+
appendToLog: () => appendToLog,
|
|
77
|
+
redactSecrets: () => redactSecrets
|
|
78
|
+
});
|
|
34
79
|
function redactSecrets(text) {
|
|
35
80
|
if (!text) return text;
|
|
36
81
|
let redacted = text;
|
|
@@ -52,24 +97,24 @@ function appendToLog(logPath, entry) {
|
|
|
52
97
|
} catch {
|
|
53
98
|
}
|
|
54
99
|
}
|
|
55
|
-
function appendHookDebug(toolName, args, meta) {
|
|
56
|
-
const
|
|
100
|
+
function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
101
|
+
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
57
102
|
appendToLog(HOOK_DEBUG_LOG, {
|
|
58
103
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
59
104
|
tool: toolName,
|
|
60
|
-
|
|
105
|
+
...argsField,
|
|
61
106
|
agent: meta?.agent,
|
|
62
107
|
mcpServer: meta?.mcpServer,
|
|
63
108
|
hostname: import_os.default.hostname(),
|
|
64
109
|
cwd: process.cwd()
|
|
65
110
|
});
|
|
66
111
|
}
|
|
67
|
-
function appendLocalAudit(toolName, args, decision, checkedBy, meta) {
|
|
68
|
-
const
|
|
112
|
+
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
113
|
+
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
69
114
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
70
115
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
71
116
|
tool: toolName,
|
|
72
|
-
|
|
117
|
+
...argsField,
|
|
73
118
|
decision,
|
|
74
119
|
checkedBy,
|
|
75
120
|
agent: meta?.agent,
|
|
@@ -91,6 +136,7 @@ var init_audit = __esm({
|
|
|
91
136
|
import_fs = __toESM(require("fs"));
|
|
92
137
|
import_path = __toESM(require("path"));
|
|
93
138
|
import_os = __toESM(require("os"));
|
|
139
|
+
init_hasher();
|
|
94
140
|
LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
|
|
95
141
|
HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
|
|
96
142
|
}
|
|
@@ -114,8 +160,8 @@ function sanitizeConfig(raw) {
|
|
|
114
160
|
}
|
|
115
161
|
}
|
|
116
162
|
const lines = result.error.issues.map((issue) => {
|
|
117
|
-
const
|
|
118
|
-
return ` \u2022 ${
|
|
163
|
+
const path28 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
164
|
+
return ` \u2022 ${path28}: ${issue.message}`;
|
|
119
165
|
});
|
|
120
166
|
return {
|
|
121
167
|
sanitized,
|
|
@@ -188,7 +234,8 @@ var init_config_schema = __esm({
|
|
|
188
234
|
environment: import_zod.z.string().optional(),
|
|
189
235
|
slackEnabled: import_zod.z.boolean().optional(),
|
|
190
236
|
enableTrustSessions: import_zod.z.boolean().optional(),
|
|
191
|
-
allowGlobalPause: import_zod.z.boolean().optional()
|
|
237
|
+
allowGlobalPause: import_zod.z.boolean().optional(),
|
|
238
|
+
auditHashArgs: import_zod.z.boolean().optional()
|
|
192
239
|
}).optional(),
|
|
193
240
|
policy: import_zod.z.object({
|
|
194
241
|
sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
|
|
@@ -269,7 +316,7 @@ function readShieldsFile() {
|
|
|
269
316
|
}
|
|
270
317
|
function writeShieldsFile(data) {
|
|
271
318
|
import_fs2.default.mkdirSync(import_path2.default.dirname(SHIELDS_STATE_FILE), { recursive: true });
|
|
272
|
-
const tmp = `${SHIELDS_STATE_FILE}.${
|
|
319
|
+
const tmp = `${SHIELDS_STATE_FILE}.${import_crypto2.default.randomBytes(6).toString("hex")}.tmp`;
|
|
273
320
|
const toWrite = { active: data.active };
|
|
274
321
|
if (data.overrides && Object.keys(data.overrides).length > 0) toWrite.overrides = data.overrides;
|
|
275
322
|
import_fs2.default.writeFileSync(tmp, JSON.stringify(toWrite, null, 2), { mode: 384 });
|
|
@@ -318,14 +365,14 @@ function resolveShieldRule(shieldName, identifier) {
|
|
|
318
365
|
}
|
|
319
366
|
return null;
|
|
320
367
|
}
|
|
321
|
-
var import_fs2, import_path2, import_os2,
|
|
368
|
+
var import_fs2, import_path2, import_os2, import_crypto2, SHIELDS, SHIELDS_STATE_FILE;
|
|
322
369
|
var init_shields = __esm({
|
|
323
370
|
"src/shields.ts"() {
|
|
324
371
|
"use strict";
|
|
325
372
|
import_fs2 = __toESM(require("fs"));
|
|
326
373
|
import_path2 = __toESM(require("path"));
|
|
327
374
|
import_os2 = __toESM(require("os"));
|
|
328
|
-
|
|
375
|
+
import_crypto2 = __toESM(require("crypto"));
|
|
329
376
|
SHIELDS = {
|
|
330
377
|
postgres: {
|
|
331
378
|
name: "postgres",
|
|
@@ -746,6 +793,7 @@ var init_config = __esm({
|
|
|
746
793
|
approvalTimeoutMs: 12e4,
|
|
747
794
|
// 120-second auto-deny timeout
|
|
748
795
|
flightRecorder: true,
|
|
796
|
+
auditHashArgs: true,
|
|
749
797
|
approvers: { native: true, browser: true, cloud: false, terminal: true }
|
|
750
798
|
},
|
|
751
799
|
policy: {
|
|
@@ -1710,9 +1758,9 @@ function matchesPattern(text, patterns) {
|
|
|
1710
1758
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1711
1759
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1712
1760
|
}
|
|
1713
|
-
function getNestedValue(obj,
|
|
1761
|
+
function getNestedValue(obj, path28) {
|
|
1714
1762
|
if (!obj || typeof obj !== "object") return null;
|
|
1715
|
-
return
|
|
1763
|
+
return path28.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1716
1764
|
}
|
|
1717
1765
|
function shouldSnapshot(toolName, args, config) {
|
|
1718
1766
|
if (!config.settings.enableUndo) return false;
|
|
@@ -2488,6 +2536,58 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
2488
2536
|
const { id, allowCount } = await res.json();
|
|
2489
2537
|
return { id, allowCount: allowCount ?? 1 };
|
|
2490
2538
|
}
|
|
2539
|
+
async function notifyTaint(filePath, source) {
|
|
2540
|
+
if (!isDaemonRunning()) return;
|
|
2541
|
+
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2542
|
+
try {
|
|
2543
|
+
await fetch(`${base}/taint`, {
|
|
2544
|
+
method: "POST",
|
|
2545
|
+
headers: { "Content-Type": "application/json" },
|
|
2546
|
+
body: JSON.stringify({ path: filePath, source }),
|
|
2547
|
+
signal: AbortSignal.timeout(1e3)
|
|
2548
|
+
});
|
|
2549
|
+
} catch {
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
async function notifyTaintPropagate(src, dest, clearSource = false) {
|
|
2553
|
+
if (!isDaemonRunning()) return;
|
|
2554
|
+
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2555
|
+
try {
|
|
2556
|
+
await fetch(`${base}/taint/propagate`, {
|
|
2557
|
+
method: "POST",
|
|
2558
|
+
headers: { "Content-Type": "application/json" },
|
|
2559
|
+
body: JSON.stringify({ src, dest, clearSource }),
|
|
2560
|
+
signal: AbortSignal.timeout(1e3)
|
|
2561
|
+
});
|
|
2562
|
+
} catch {
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
async function checkTaint(paths) {
|
|
2566
|
+
if (paths.length === 0) return { tainted: false };
|
|
2567
|
+
if (!isDaemonRunning()) return { tainted: false, daemonUnavailable: true };
|
|
2568
|
+
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2569
|
+
try {
|
|
2570
|
+
const res = await fetch(`${base}/taint/check`, {
|
|
2571
|
+
method: "POST",
|
|
2572
|
+
headers: { "Content-Type": "application/json" },
|
|
2573
|
+
body: JSON.stringify({ paths }),
|
|
2574
|
+
signal: AbortSignal.timeout(2e3)
|
|
2575
|
+
});
|
|
2576
|
+
return await res.json();
|
|
2577
|
+
} catch (err) {
|
|
2578
|
+
try {
|
|
2579
|
+
const { appendToLog: appendToLog2, HOOK_DEBUG_LOG: HOOK_DEBUG_LOG2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
2580
|
+
appendToLog2(HOOK_DEBUG_LOG2, {
|
|
2581
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2582
|
+
event: "checkTaint-error",
|
|
2583
|
+
error: String(err),
|
|
2584
|
+
paths
|
|
2585
|
+
});
|
|
2586
|
+
} catch {
|
|
2587
|
+
}
|
|
2588
|
+
return { tainted: false, daemonUnavailable: true };
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2491
2591
|
async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
2492
2592
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2493
2593
|
await fetch(`${base}/resolve/${id}`, {
|
|
@@ -2959,6 +3059,40 @@ var init_cloud = __esm({
|
|
|
2959
3059
|
});
|
|
2960
3060
|
|
|
2961
3061
|
// src/auth/orchestrator.ts
|
|
3062
|
+
function isWriteTool(toolName) {
|
|
3063
|
+
const t = toolName.toLowerCase().replace(/[^a-z_]/g, "_");
|
|
3064
|
+
return WRITE_TOOLS.has(t);
|
|
3065
|
+
}
|
|
3066
|
+
function extractFilePaths(toolName, args) {
|
|
3067
|
+
const paths = [];
|
|
3068
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return paths;
|
|
3069
|
+
const a = args;
|
|
3070
|
+
for (const key of ["file_path", "path", "filename", "source", "src", "input"]) {
|
|
3071
|
+
if (typeof a[key] === "string" && a[key]) paths.push(a[key]);
|
|
3072
|
+
}
|
|
3073
|
+
const cmd = typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : "";
|
|
3074
|
+
if (cmd) {
|
|
3075
|
+
for (const m of cmd.matchAll(/(?:-T|--upload-file|--data(?:-binary)?)\s+@?(\S+)/g)) {
|
|
3076
|
+
paths.push(m[1]);
|
|
3077
|
+
}
|
|
3078
|
+
for (const m of cmd.matchAll(/\b(?:scp|rsync)\s+(?:-\S+\s+)*(\S+)\s+\S+@/g)) {
|
|
3079
|
+
paths.push(m[1]);
|
|
3080
|
+
}
|
|
3081
|
+
for (const m of cmd.matchAll(/<\s*(\S+)/g)) {
|
|
3082
|
+
paths.push(m[1]);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
return paths.filter(Boolean);
|
|
3086
|
+
}
|
|
3087
|
+
function isNetworkTool(toolName, args) {
|
|
3088
|
+
const t = toolName.toLowerCase();
|
|
3089
|
+
if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
|
|
3090
|
+
const a = args;
|
|
3091
|
+
const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
|
|
3092
|
+
return /\b(curl|wget|scp|rsync|nc|ncat|netcat|ssh)\b/.test(cmd);
|
|
3093
|
+
}
|
|
3094
|
+
return false;
|
|
3095
|
+
}
|
|
2962
3096
|
function notifyActivity(data) {
|
|
2963
3097
|
return new Promise((resolve) => {
|
|
2964
3098
|
try {
|
|
@@ -2976,7 +3110,7 @@ function notifyActivity(data) {
|
|
|
2976
3110
|
}
|
|
2977
3111
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
2978
3112
|
if (!options?.calledFromDaemon) {
|
|
2979
|
-
const actId = (0,
|
|
3113
|
+
const actId = (0, import_crypto3.randomUUID)();
|
|
2980
3114
|
const actTs = Date.now();
|
|
2981
3115
|
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
2982
3116
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
@@ -2988,7 +3122,7 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
2988
3122
|
id: actId,
|
|
2989
3123
|
tool: toolName,
|
|
2990
3124
|
ts: actTs,
|
|
2991
|
-
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
|
|
3125
|
+
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : result.blockedByLabel?.includes("Taint") ? "taint" : "block",
|
|
2992
3126
|
label: result.blockedByLabel
|
|
2993
3127
|
});
|
|
2994
3128
|
}
|
|
@@ -3002,6 +3136,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3002
3136
|
if (pauseState.paused) return { approved: true, checkedBy: "paused" };
|
|
3003
3137
|
const creds = getCredentials();
|
|
3004
3138
|
const config = getConfig(options?.cwd);
|
|
3139
|
+
const hashAuditArgs = config.settings.auditHashArgs === true;
|
|
3005
3140
|
const isTestEnv2 = !!(process.env.VITEST || process.env.NODE_ENV === "test" || process.env.CI || process.env.NODE9_TESTING === "1");
|
|
3006
3141
|
const approvers = {
|
|
3007
3142
|
...config.settings.approvers || { native: true, browser: true, cloud: true, terminal: true }
|
|
@@ -3012,13 +3147,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3012
3147
|
approvers.terminal = false;
|
|
3013
3148
|
}
|
|
3014
3149
|
if (config.settings.enableHookLogDebug && !isTestEnv2) {
|
|
3015
|
-
appendHookDebug(toolName, args, meta);
|
|
3150
|
+
appendHookDebug(toolName, args, meta, hashAuditArgs);
|
|
3016
3151
|
}
|
|
3017
3152
|
const isManual = meta?.agent === "Terminal";
|
|
3018
3153
|
let explainableLabel = "Local Config";
|
|
3019
3154
|
let policyMatchedField;
|
|
3020
3155
|
let policyMatchedWord;
|
|
3021
3156
|
let riskMetadata;
|
|
3157
|
+
let taintWarning = null;
|
|
3158
|
+
if (isNetworkTool(toolName, args)) {
|
|
3159
|
+
const filePaths = extractFilePaths(toolName, args);
|
|
3160
|
+
if (filePaths.length > 0) {
|
|
3161
|
+
const taintResult = await checkTaint(filePaths);
|
|
3162
|
+
if (taintResult.tainted && taintResult.record) {
|
|
3163
|
+
const { path: taintedPath, source: taintSource } = taintResult.record;
|
|
3164
|
+
taintWarning = `\u26A0\uFE0F ${taintedPath} was flagged by ${taintSource} \u2014 this file may contain sensitive data`;
|
|
3165
|
+
} else if (taintResult.daemonUnavailable) {
|
|
3166
|
+
taintWarning = `\u26A0\uFE0F Taint service unavailable \u2014 cannot verify if ${filePaths.join(", ")} is clean`;
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3022
3170
|
if (config.policy.dlp.enabled && (!isIgnoredTool(toolName) || config.policy.dlp.scanIgnoredTools)) {
|
|
3023
3171
|
const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
3024
3172
|
const filePath = String(argsObj.file_path ?? argsObj.path ?? argsObj.filename ?? "");
|
|
@@ -3026,7 +3174,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3026
3174
|
if (dlpMatch) {
|
|
3027
3175
|
const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
|
|
3028
3176
|
if (dlpMatch.severity === "block") {
|
|
3029
|
-
if (!isManual) appendLocalAudit(toolName, args, "deny", "dlp-block", meta);
|
|
3177
|
+
if (!isManual) appendLocalAudit(toolName, args, "deny", "dlp-block", meta, true);
|
|
3178
|
+
if (isWriteTool(toolName) && filePath) {
|
|
3179
|
+
await notifyTaint(filePath, `DLP:${dlpMatch.patternName}`);
|
|
3180
|
+
}
|
|
3030
3181
|
return {
|
|
3031
3182
|
approved: false,
|
|
3032
3183
|
reason: dlpReason,
|
|
@@ -3034,7 +3185,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3034
3185
|
blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
|
|
3035
3186
|
};
|
|
3036
3187
|
}
|
|
3037
|
-
if (!isManual)
|
|
3188
|
+
if (!isManual)
|
|
3189
|
+
appendLocalAudit(toolName, args, "allow", "dlp-review-flagged", meta, hashAuditArgs);
|
|
3038
3190
|
explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
|
|
3039
3191
|
}
|
|
3040
3192
|
}
|
|
@@ -3042,7 +3194,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3042
3194
|
if (!isIgnoredTool(toolName)) {
|
|
3043
3195
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent, options?.cwd);
|
|
3044
3196
|
if (policyResult.decision === "review") {
|
|
3045
|
-
appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
|
|
3197
|
+
appendLocalAudit(toolName, args, "allow", "audit-mode", meta, hashAuditArgs);
|
|
3046
3198
|
if (approvers.cloud && creds?.apiKey) {
|
|
3047
3199
|
await auditLocalAllow(toolName, args, "audit-mode", creds, meta);
|
|
3048
3200
|
}
|
|
@@ -3050,22 +3202,23 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3050
3202
|
}
|
|
3051
3203
|
return { approved: true, checkedBy: "audit" };
|
|
3052
3204
|
}
|
|
3053
|
-
if (!isIgnoredTool(toolName)) {
|
|
3205
|
+
if (!taintWarning && !isIgnoredTool(toolName)) {
|
|
3054
3206
|
if (getActiveTrustSession(toolName)) {
|
|
3055
3207
|
if (approvers.cloud && creds?.apiKey)
|
|
3056
3208
|
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3057
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta);
|
|
3209
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3058
3210
|
return { approved: true, checkedBy: "trust" };
|
|
3059
3211
|
}
|
|
3060
3212
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
3061
3213
|
if (policyResult.decision === "allow") {
|
|
3062
3214
|
if (approvers.cloud && creds?.apiKey)
|
|
3063
3215
|
auditLocalAllow(toolName, args, "local-policy", creds, meta);
|
|
3064
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta);
|
|
3216
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
|
|
3065
3217
|
return { approved: true, checkedBy: "local-policy" };
|
|
3066
3218
|
}
|
|
3067
3219
|
if (policyResult.decision === "block") {
|
|
3068
|
-
if (!isManual)
|
|
3220
|
+
if (!isManual)
|
|
3221
|
+
appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
|
|
3069
3222
|
return {
|
|
3070
3223
|
approved: false,
|
|
3071
3224
|
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
@@ -3084,15 +3237,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3084
3237
|
policyMatchedWord,
|
|
3085
3238
|
policyResult.ruleName
|
|
3086
3239
|
);
|
|
3087
|
-
const persistent = getPersistentDecision(toolName);
|
|
3240
|
+
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
3088
3241
|
if (persistent === "allow") {
|
|
3089
3242
|
if (approvers.cloud && creds?.apiKey)
|
|
3090
3243
|
await auditLocalAllow(toolName, args, "persistent", creds, meta);
|
|
3091
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta);
|
|
3244
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
|
|
3092
3245
|
return { approved: true, checkedBy: "persistent" };
|
|
3093
3246
|
}
|
|
3094
3247
|
if (persistent === "deny") {
|
|
3095
|
-
if (!isManual)
|
|
3248
|
+
if (!isManual)
|
|
3249
|
+
appendLocalAudit(toolName, args, "deny", "persistent-deny", meta, hashAuditArgs);
|
|
3096
3250
|
return {
|
|
3097
3251
|
approved: false,
|
|
3098
3252
|
reason: `This tool ("${toolName}") is explicitly listed in your 'Always Deny' list.`,
|
|
@@ -3100,10 +3254,21 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3100
3254
|
blockedByLabel: "Persistent User Rule"
|
|
3101
3255
|
};
|
|
3102
3256
|
}
|
|
3103
|
-
} else {
|
|
3104
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta);
|
|
3257
|
+
} else if (!taintWarning) {
|
|
3258
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
|
|
3105
3259
|
return { approved: true };
|
|
3106
3260
|
}
|
|
3261
|
+
if (taintWarning) {
|
|
3262
|
+
explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
|
|
3263
|
+
riskMetadata = computeRiskMetadata(
|
|
3264
|
+
args,
|
|
3265
|
+
7,
|
|
3266
|
+
explainableLabel,
|
|
3267
|
+
void 0,
|
|
3268
|
+
void 0,
|
|
3269
|
+
taintWarning
|
|
3270
|
+
);
|
|
3271
|
+
}
|
|
3107
3272
|
let cloudRequestId = null;
|
|
3108
3273
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
3109
3274
|
if (cloudEnforced) {
|
|
@@ -3122,7 +3287,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3122
3287
|
};
|
|
3123
3288
|
}
|
|
3124
3289
|
cloudRequestId = initResult.requestId || null;
|
|
3125
|
-
explainableLabel = "Organization Policy (SaaS)";
|
|
3290
|
+
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
3126
3291
|
} catch {
|
|
3127
3292
|
}
|
|
3128
3293
|
}
|
|
@@ -3309,19 +3474,20 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
3309
3474
|
args,
|
|
3310
3475
|
finalResult.approved ? "allow" : "deny",
|
|
3311
3476
|
finalResult.checkedBy || finalResult.blockedBy || "unknown",
|
|
3312
|
-
meta
|
|
3477
|
+
meta,
|
|
3478
|
+
hashAuditArgs
|
|
3313
3479
|
);
|
|
3314
3480
|
}
|
|
3315
3481
|
return finalResult;
|
|
3316
3482
|
}
|
|
3317
|
-
var import_net, import_path13, import_os10,
|
|
3483
|
+
var import_net, import_path13, import_os10, import_crypto3, WRITE_TOOLS, ACTIVITY_SOCKET_PATH;
|
|
3318
3484
|
var init_orchestrator = __esm({
|
|
3319
3485
|
"src/auth/orchestrator.ts"() {
|
|
3320
3486
|
"use strict";
|
|
3321
3487
|
import_net = __toESM(require("net"));
|
|
3322
3488
|
import_path13 = __toESM(require("path"));
|
|
3323
3489
|
import_os10 = __toESM(require("os"));
|
|
3324
|
-
|
|
3490
|
+
import_crypto3 = require("crypto");
|
|
3325
3491
|
init_native();
|
|
3326
3492
|
init_context_sniper();
|
|
3327
3493
|
init_dlp();
|
|
@@ -3331,6 +3497,17 @@ var init_orchestrator = __esm({
|
|
|
3331
3497
|
init_state();
|
|
3332
3498
|
init_daemon();
|
|
3333
3499
|
init_cloud();
|
|
3500
|
+
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
3501
|
+
"write",
|
|
3502
|
+
"write_file",
|
|
3503
|
+
"create_file",
|
|
3504
|
+
"edit",
|
|
3505
|
+
"multiedit",
|
|
3506
|
+
"str_replace_based_edit_tool",
|
|
3507
|
+
"replace",
|
|
3508
|
+
"notebook_edit",
|
|
3509
|
+
"notebookedit"
|
|
3510
|
+
]);
|
|
3334
3511
|
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path13.default.join(import_os10.default.tmpdir(), "node9-activity.sock");
|
|
3335
3512
|
}
|
|
3336
3513
|
});
|
|
@@ -4515,12 +4692,15 @@ var init_ui = __esm({
|
|
|
4515
4692
|
const badgeClass = isEdit ? 'sniper-badge-edit' : 'sniper-badge-exec';
|
|
4516
4693
|
const badgeLabel = isEdit ? '\u{1F4DD} Code Edit' : '\u{1F6D1} Execution';
|
|
4517
4694
|
const tierLabel = \`Tier \${rm.tier} \xB7 \${esc(rm.blockedByLabel)}\`;
|
|
4695
|
+
const isTaint = rm.blockedByLabel?.includes('Taint');
|
|
4518
4696
|
const fileLine =
|
|
4519
|
-
|
|
4520
|
-
? \`<div class="sniper-
|
|
4521
|
-
:
|
|
4522
|
-
? \`<div class="sniper-
|
|
4523
|
-
:
|
|
4697
|
+
isTaint && rm.ruleName
|
|
4698
|
+
? \`<div class="sniper-match">\u26A0\uFE0F \${esc(rm.ruleName)}</div>\`
|
|
4699
|
+
: isEdit && rm.editFilePath
|
|
4700
|
+
? \`<div class="sniper-filepath">\u{1F4C2} \${esc(rm.editFilePath)}</div>\`
|
|
4701
|
+
: !isEdit && rm.matchedWord
|
|
4702
|
+
? \`<div class="sniper-match">Matched: <code>\${esc(rm.matchedWord)}</code>\${rm.matchedField ? \` in <code>\${esc(rm.matchedField)}</code>\` : ''}</div>\`
|
|
4703
|
+
: '';
|
|
4524
4704
|
const snippetHtml = rm.contextSnippet ? \`<pre>\${esc(rm.contextSnippet)}</pre>\` : '';
|
|
4525
4705
|
return \`
|
|
4526
4706
|
<div class="sniper-header">
|
|
@@ -4991,11 +5171,11 @@ function commonPathPrefix(paths) {
|
|
|
4991
5171
|
const prefix = common.join("/").replace(/\/?$/, "/");
|
|
4992
5172
|
return prefix.length > 1 ? prefix : null;
|
|
4993
5173
|
}
|
|
4994
|
-
var
|
|
5174
|
+
var import_crypto4, SuggestionTracker;
|
|
4995
5175
|
var init_suggestion_tracker = __esm({
|
|
4996
5176
|
"src/daemon/suggestion-tracker.ts"() {
|
|
4997
5177
|
"use strict";
|
|
4998
|
-
|
|
5178
|
+
import_crypto4 = require("crypto");
|
|
4999
5179
|
SuggestionTracker = class {
|
|
5000
5180
|
events = /* @__PURE__ */ new Map();
|
|
5001
5181
|
threshold;
|
|
@@ -5041,7 +5221,7 @@ var init_suggestion_tracker = __esm({
|
|
|
5041
5221
|
}
|
|
5042
5222
|
} : { type: "ignoredTool", toolName };
|
|
5043
5223
|
return {
|
|
5044
|
-
id: (0,
|
|
5224
|
+
id: (0, import_crypto4.randomUUID)(),
|
|
5045
5225
|
toolName,
|
|
5046
5226
|
allowCount: events.length,
|
|
5047
5227
|
suggestedRule,
|
|
@@ -5054,11 +5234,91 @@ var init_suggestion_tracker = __esm({
|
|
|
5054
5234
|
}
|
|
5055
5235
|
});
|
|
5056
5236
|
|
|
5237
|
+
// src/daemon/taint-store.ts
|
|
5238
|
+
var import_fs12, import_path15, DEFAULT_TTL_MS, TaintStore;
|
|
5239
|
+
var init_taint_store = __esm({
|
|
5240
|
+
"src/daemon/taint-store.ts"() {
|
|
5241
|
+
"use strict";
|
|
5242
|
+
import_fs12 = __toESM(require("fs"));
|
|
5243
|
+
import_path15 = __toESM(require("path"));
|
|
5244
|
+
DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
5245
|
+
TaintStore = class {
|
|
5246
|
+
records = /* @__PURE__ */ new Map();
|
|
5247
|
+
/** Add or refresh taint on an absolute path. */
|
|
5248
|
+
taint(filePath, source, ttlMs = DEFAULT_TTL_MS) {
|
|
5249
|
+
const resolved = this._resolve(filePath);
|
|
5250
|
+
const now = Date.now();
|
|
5251
|
+
this.records.set(resolved, {
|
|
5252
|
+
path: resolved,
|
|
5253
|
+
source,
|
|
5254
|
+
createdAt: now,
|
|
5255
|
+
expiresAt: now + ttlMs
|
|
5256
|
+
});
|
|
5257
|
+
}
|
|
5258
|
+
/**
|
|
5259
|
+
* Check whether a path is currently tainted.
|
|
5260
|
+
* Returns the TaintRecord if tainted (and not expired), null otherwise.
|
|
5261
|
+
* Expired records are pruned on access.
|
|
5262
|
+
*/
|
|
5263
|
+
check(filePath) {
|
|
5264
|
+
const resolved = this._resolve(filePath);
|
|
5265
|
+
const record = this.records.get(resolved);
|
|
5266
|
+
if (!record) return null;
|
|
5267
|
+
if (Date.now() > record.expiresAt) {
|
|
5268
|
+
this.records.delete(resolved);
|
|
5269
|
+
return null;
|
|
5270
|
+
}
|
|
5271
|
+
return record;
|
|
5272
|
+
}
|
|
5273
|
+
/**
|
|
5274
|
+
* Propagate taint from sourcePath to destPath (e.g. cp, mv).
|
|
5275
|
+
* For mv semantics (clearSource=true) the source taint is removed.
|
|
5276
|
+
*/
|
|
5277
|
+
propagate(sourcePath, destPath, clearSource = false) {
|
|
5278
|
+
const taintRecord = this.check(sourcePath);
|
|
5279
|
+
if (!taintRecord) return;
|
|
5280
|
+
const remainingMs = taintRecord.expiresAt - Date.now();
|
|
5281
|
+
if (remainingMs > 0) {
|
|
5282
|
+
const baseSource = taintRecord.source.replace(/^(propagated:)+/, "");
|
|
5283
|
+
this.taint(destPath, `propagated:${baseSource}`, remainingMs);
|
|
5284
|
+
}
|
|
5285
|
+
if (clearSource) {
|
|
5286
|
+
this.records.delete(this._resolve(sourcePath));
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
/** Remove all expired records. Called periodically by the daemon. */
|
|
5290
|
+
prune() {
|
|
5291
|
+
const now = Date.now();
|
|
5292
|
+
for (const [key, record] of this.records) {
|
|
5293
|
+
if (now > record.expiresAt) this.records.delete(key);
|
|
5294
|
+
}
|
|
5295
|
+
}
|
|
5296
|
+
/** Return all non-expired taint records (for audit/debug). */
|
|
5297
|
+
list() {
|
|
5298
|
+
this.prune();
|
|
5299
|
+
return [...this.records.values()];
|
|
5300
|
+
}
|
|
5301
|
+
/** Remove all taint records atomically. Used by tests to reset state between runs. */
|
|
5302
|
+
clear() {
|
|
5303
|
+
this.records.clear();
|
|
5304
|
+
}
|
|
5305
|
+
/** Resolve to absolute path, falling back to path.resolve if file doesn't exist yet. */
|
|
5306
|
+
_resolve(filePath) {
|
|
5307
|
+
try {
|
|
5308
|
+
return import_fs12.default.realpathSync.native(import_path15.default.resolve(filePath));
|
|
5309
|
+
} catch {
|
|
5310
|
+
return import_path15.default.resolve(filePath);
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
};
|
|
5314
|
+
}
|
|
5315
|
+
});
|
|
5316
|
+
|
|
5057
5317
|
// src/daemon/state.ts
|
|
5058
5318
|
function loadInsightCounts() {
|
|
5059
5319
|
try {
|
|
5060
|
-
if (!
|
|
5061
|
-
const data = JSON.parse(
|
|
5320
|
+
if (!import_fs13.default.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
5321
|
+
const data = JSON.parse(import_fs13.default.readFileSync(INSIGHT_COUNTS_FILE, "utf-8"));
|
|
5062
5322
|
for (const [tool, count] of Object.entries(data)) {
|
|
5063
5323
|
if (typeof count === "number" && count > 0) insightCounts.set(tool, count);
|
|
5064
5324
|
}
|
|
@@ -5097,23 +5357,23 @@ function markRejectionHandlerRegistered() {
|
|
|
5097
5357
|
daemonRejectionHandlerRegistered = true;
|
|
5098
5358
|
}
|
|
5099
5359
|
function atomicWriteSync2(filePath, data, options) {
|
|
5100
|
-
const dir =
|
|
5101
|
-
if (!
|
|
5102
|
-
const tmpPath = `${filePath}.${(0,
|
|
5360
|
+
const dir = import_path16.default.dirname(filePath);
|
|
5361
|
+
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
5362
|
+
const tmpPath = `${filePath}.${(0, import_crypto5.randomUUID)()}.tmp`;
|
|
5103
5363
|
try {
|
|
5104
|
-
|
|
5364
|
+
import_fs13.default.writeFileSync(tmpPath, data, options);
|
|
5105
5365
|
} catch (err) {
|
|
5106
5366
|
try {
|
|
5107
|
-
|
|
5367
|
+
import_fs13.default.unlinkSync(tmpPath);
|
|
5108
5368
|
} catch {
|
|
5109
5369
|
}
|
|
5110
5370
|
throw err;
|
|
5111
5371
|
}
|
|
5112
5372
|
try {
|
|
5113
|
-
|
|
5373
|
+
import_fs13.default.renameSync(tmpPath, filePath);
|
|
5114
5374
|
} catch (err) {
|
|
5115
5375
|
try {
|
|
5116
|
-
|
|
5376
|
+
import_fs13.default.unlinkSync(tmpPath);
|
|
5117
5377
|
} catch {
|
|
5118
5378
|
}
|
|
5119
5379
|
throw err;
|
|
@@ -5137,16 +5397,16 @@ function appendAuditLog(data) {
|
|
|
5137
5397
|
decision: data.decision,
|
|
5138
5398
|
source: "daemon"
|
|
5139
5399
|
};
|
|
5140
|
-
const dir =
|
|
5141
|
-
if (!
|
|
5142
|
-
|
|
5400
|
+
const dir = import_path16.default.dirname(AUDIT_LOG_FILE);
|
|
5401
|
+
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
5402
|
+
import_fs13.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
5143
5403
|
} catch {
|
|
5144
5404
|
}
|
|
5145
5405
|
}
|
|
5146
5406
|
function getAuditHistory(limit = 20) {
|
|
5147
5407
|
try {
|
|
5148
|
-
if (!
|
|
5149
|
-
const lines =
|
|
5408
|
+
if (!import_fs13.default.existsSync(AUDIT_LOG_FILE)) return [];
|
|
5409
|
+
const lines = import_fs13.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
5150
5410
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
5151
5411
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
5152
5412
|
} catch {
|
|
@@ -5155,19 +5415,19 @@ function getAuditHistory(limit = 20) {
|
|
|
5155
5415
|
}
|
|
5156
5416
|
function getOrgName() {
|
|
5157
5417
|
try {
|
|
5158
|
-
if (
|
|
5418
|
+
if (import_fs13.default.existsSync(CREDENTIALS_FILE)) return "Node9 Cloud";
|
|
5159
5419
|
} catch {
|
|
5160
5420
|
}
|
|
5161
5421
|
return null;
|
|
5162
5422
|
}
|
|
5163
5423
|
function hasStoredSlackKey() {
|
|
5164
|
-
return
|
|
5424
|
+
return import_fs13.default.existsSync(CREDENTIALS_FILE);
|
|
5165
5425
|
}
|
|
5166
5426
|
function writeGlobalSetting(key, value) {
|
|
5167
5427
|
let config = {};
|
|
5168
5428
|
try {
|
|
5169
|
-
if (
|
|
5170
|
-
config = JSON.parse(
|
|
5429
|
+
if (import_fs13.default.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
5430
|
+
config = JSON.parse(import_fs13.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
5171
5431
|
}
|
|
5172
5432
|
} catch {
|
|
5173
5433
|
}
|
|
@@ -5179,8 +5439,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5179
5439
|
try {
|
|
5180
5440
|
let trust = { entries: [] };
|
|
5181
5441
|
try {
|
|
5182
|
-
if (
|
|
5183
|
-
trust = JSON.parse(
|
|
5442
|
+
if (import_fs13.default.existsSync(TRUST_FILE2))
|
|
5443
|
+
trust = JSON.parse(import_fs13.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
5184
5444
|
} catch {
|
|
5185
5445
|
}
|
|
5186
5446
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -5191,8 +5451,8 @@ function writeTrustEntry(toolName, durationMs) {
|
|
|
5191
5451
|
}
|
|
5192
5452
|
function readPersistentDecisions() {
|
|
5193
5453
|
try {
|
|
5194
|
-
if (
|
|
5195
|
-
return JSON.parse(
|
|
5454
|
+
if (import_fs13.default.existsSync(DECISIONS_FILE)) {
|
|
5455
|
+
return JSON.parse(import_fs13.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
5196
5456
|
}
|
|
5197
5457
|
} catch {
|
|
5198
5458
|
}
|
|
@@ -5257,7 +5517,7 @@ function abandonPending() {
|
|
|
5257
5517
|
});
|
|
5258
5518
|
if (autoStarted) {
|
|
5259
5519
|
try {
|
|
5260
|
-
|
|
5520
|
+
import_fs13.default.unlinkSync(DAEMON_PID_FILE);
|
|
5261
5521
|
} catch {
|
|
5262
5522
|
}
|
|
5263
5523
|
setTimeout(() => {
|
|
@@ -5268,7 +5528,7 @@ function abandonPending() {
|
|
|
5268
5528
|
}
|
|
5269
5529
|
function startActivitySocket() {
|
|
5270
5530
|
try {
|
|
5271
|
-
|
|
5531
|
+
import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5272
5532
|
} catch {
|
|
5273
5533
|
}
|
|
5274
5534
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -5310,35 +5570,37 @@ function startActivitySocket() {
|
|
|
5310
5570
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
5311
5571
|
process.on("exit", () => {
|
|
5312
5572
|
try {
|
|
5313
|
-
|
|
5573
|
+
import_fs13.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
5314
5574
|
} catch {
|
|
5315
5575
|
}
|
|
5316
5576
|
});
|
|
5317
5577
|
}
|
|
5318
|
-
var import_net2,
|
|
5578
|
+
var import_net2, import_fs13, import_path16, import_os12, import_child_process3, import_crypto5, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, suggestions, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, SECRET_KEY_RE;
|
|
5319
5579
|
var init_state2 = __esm({
|
|
5320
5580
|
"src/daemon/state.ts"() {
|
|
5321
5581
|
"use strict";
|
|
5322
5582
|
import_net2 = __toESM(require("net"));
|
|
5323
|
-
|
|
5324
|
-
|
|
5583
|
+
import_fs13 = __toESM(require("fs"));
|
|
5584
|
+
import_path16 = __toESM(require("path"));
|
|
5325
5585
|
import_os12 = __toESM(require("os"));
|
|
5326
5586
|
import_child_process3 = require("child_process");
|
|
5327
|
-
|
|
5587
|
+
import_crypto5 = require("crypto");
|
|
5328
5588
|
init_daemon();
|
|
5329
5589
|
init_suggestion_tracker();
|
|
5590
|
+
init_taint_store();
|
|
5330
5591
|
homeDir = import_os12.default.homedir();
|
|
5331
|
-
DAEMON_PID_FILE =
|
|
5332
|
-
DECISIONS_FILE =
|
|
5333
|
-
AUDIT_LOG_FILE =
|
|
5334
|
-
TRUST_FILE2 =
|
|
5335
|
-
GLOBAL_CONFIG_FILE =
|
|
5336
|
-
CREDENTIALS_FILE =
|
|
5337
|
-
INSIGHT_COUNTS_FILE =
|
|
5592
|
+
DAEMON_PID_FILE = import_path16.default.join(homeDir, ".node9", "daemon.pid");
|
|
5593
|
+
DECISIONS_FILE = import_path16.default.join(homeDir, ".node9", "decisions.json");
|
|
5594
|
+
AUDIT_LOG_FILE = import_path16.default.join(homeDir, ".node9", "audit.log");
|
|
5595
|
+
TRUST_FILE2 = import_path16.default.join(homeDir, ".node9", "trust.json");
|
|
5596
|
+
GLOBAL_CONFIG_FILE = import_path16.default.join(homeDir, ".node9", "config.json");
|
|
5597
|
+
CREDENTIALS_FILE = import_path16.default.join(homeDir, ".node9", "credentials.json");
|
|
5598
|
+
INSIGHT_COUNTS_FILE = import_path16.default.join(homeDir, ".node9", "insight-counts.json");
|
|
5338
5599
|
pending = /* @__PURE__ */ new Map();
|
|
5339
5600
|
sseClients = /* @__PURE__ */ new Set();
|
|
5340
5601
|
suggestionTracker = new SuggestionTracker(3);
|
|
5341
5602
|
suggestions = /* @__PURE__ */ new Map();
|
|
5603
|
+
taintStore = new TaintStore();
|
|
5342
5604
|
insightCounts = /* @__PURE__ */ new Map();
|
|
5343
5605
|
_abandonTimer = null;
|
|
5344
5606
|
_hadBrowserClient = false;
|
|
@@ -5351,7 +5613,7 @@ var init_state2 = __esm({
|
|
|
5351
5613
|
"2h": 2 * 60 * 6e4
|
|
5352
5614
|
};
|
|
5353
5615
|
autoStarted = process.env.NODE9_AUTO_STARTED === "1";
|
|
5354
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
5616
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path16.default.join(import_os12.default.tmpdir(), "node9-activity.sock");
|
|
5355
5617
|
ACTIVITY_RING_SIZE = 100;
|
|
5356
5618
|
activityRing = [];
|
|
5357
5619
|
SECRET_KEY_RE = /password|secret|token|key|apikey|credential|auth/i;
|
|
@@ -5362,8 +5624,8 @@ var init_state2 = __esm({
|
|
|
5362
5624
|
function patchConfig(configPath, patch) {
|
|
5363
5625
|
let config = {};
|
|
5364
5626
|
try {
|
|
5365
|
-
if (
|
|
5366
|
-
config = JSON.parse(
|
|
5627
|
+
if (import_fs14.default.existsSync(configPath)) {
|
|
5628
|
+
config = JSON.parse(import_fs14.default.readFileSync(configPath, "utf8"));
|
|
5367
5629
|
}
|
|
5368
5630
|
} catch {
|
|
5369
5631
|
throw new Error(`Cannot read config at ${configPath} \u2014 file may be corrupted`);
|
|
@@ -5382,44 +5644,44 @@ function patchConfig(configPath, patch) {
|
|
|
5382
5644
|
ignored.push(patch.toolName);
|
|
5383
5645
|
}
|
|
5384
5646
|
}
|
|
5385
|
-
const dir =
|
|
5386
|
-
|
|
5647
|
+
const dir = import_path17.default.dirname(configPath);
|
|
5648
|
+
import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
5387
5649
|
const tmp = configPath + ".node9-tmp";
|
|
5388
5650
|
try {
|
|
5389
|
-
|
|
5651
|
+
import_fs14.default.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5390
5652
|
} catch (err) {
|
|
5391
5653
|
try {
|
|
5392
|
-
|
|
5654
|
+
import_fs14.default.unlinkSync(tmp);
|
|
5393
5655
|
} catch {
|
|
5394
5656
|
}
|
|
5395
5657
|
throw err;
|
|
5396
5658
|
}
|
|
5397
5659
|
try {
|
|
5398
|
-
|
|
5660
|
+
import_fs14.default.renameSync(tmp, configPath);
|
|
5399
5661
|
} catch (err) {
|
|
5400
5662
|
try {
|
|
5401
|
-
|
|
5663
|
+
import_fs14.default.unlinkSync(tmp);
|
|
5402
5664
|
} catch {
|
|
5403
5665
|
}
|
|
5404
5666
|
throw err;
|
|
5405
5667
|
}
|
|
5406
5668
|
}
|
|
5407
|
-
var
|
|
5669
|
+
var import_fs14, import_path17, import_os13, GLOBAL_CONFIG_PATH;
|
|
5408
5670
|
var init_patch = __esm({
|
|
5409
5671
|
"src/config/patch.ts"() {
|
|
5410
5672
|
"use strict";
|
|
5411
|
-
|
|
5412
|
-
|
|
5673
|
+
import_fs14 = __toESM(require("fs"));
|
|
5674
|
+
import_path17 = __toESM(require("path"));
|
|
5413
5675
|
import_os13 = __toESM(require("os"));
|
|
5414
|
-
GLOBAL_CONFIG_PATH =
|
|
5676
|
+
GLOBAL_CONFIG_PATH = import_path17.default.join(import_os13.default.homedir(), ".node9", "config.json");
|
|
5415
5677
|
}
|
|
5416
5678
|
});
|
|
5417
5679
|
|
|
5418
5680
|
// src/daemon/server.ts
|
|
5419
5681
|
function startDaemon() {
|
|
5420
5682
|
loadInsightCounts();
|
|
5421
|
-
const csrfToken = (0,
|
|
5422
|
-
const internalToken = (0,
|
|
5683
|
+
const csrfToken = (0, import_crypto6.randomUUID)();
|
|
5684
|
+
const internalToken = (0, import_crypto6.randomUUID)();
|
|
5423
5685
|
const UI_HTML = UI_HTML_TEMPLATE.replace("{{CSRF_TOKEN}}", csrfToken);
|
|
5424
5686
|
const validToken = (req) => req.headers["x-node9-token"] === csrfToken;
|
|
5425
5687
|
const IDLE_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
|
|
@@ -5432,7 +5694,7 @@ function startDaemon() {
|
|
|
5432
5694
|
idleTimer = setTimeout(() => {
|
|
5433
5695
|
if (autoStarted) {
|
|
5434
5696
|
try {
|
|
5435
|
-
|
|
5697
|
+
import_fs15.default.unlinkSync(DAEMON_PID_FILE);
|
|
5436
5698
|
} catch {
|
|
5437
5699
|
}
|
|
5438
5700
|
}
|
|
@@ -5547,7 +5809,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
5547
5809
|
activityId,
|
|
5548
5810
|
cwd
|
|
5549
5811
|
} = JSON.parse(body);
|
|
5550
|
-
const id = fromCLI && typeof activityId === "string" && activityId || (0,
|
|
5812
|
+
const id = fromCLI && typeof activityId === "string" && activityId || (0, import_crypto6.randomUUID)();
|
|
5551
5813
|
const entry = {
|
|
5552
5814
|
id,
|
|
5553
5815
|
toolName,
|
|
@@ -5587,7 +5849,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
5587
5849
|
status: "pending"
|
|
5588
5850
|
});
|
|
5589
5851
|
}
|
|
5590
|
-
const projectCwd = typeof cwd === "string" &&
|
|
5852
|
+
const projectCwd = typeof cwd === "string" && import_path18.default.isAbsolute(cwd) ? cwd : void 0;
|
|
5591
5853
|
const projectConfig = getConfig(projectCwd);
|
|
5592
5854
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
5593
5855
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
@@ -5938,8 +6200,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
5938
6200
|
const body = await readBody(req);
|
|
5939
6201
|
const data = body ? JSON.parse(body) : {};
|
|
5940
6202
|
const configPath = data.configPath ?? GLOBAL_CONFIG_PATH;
|
|
5941
|
-
const node9Dir =
|
|
5942
|
-
if (!
|
|
6203
|
+
const node9Dir = import_path18.default.dirname(GLOBAL_CONFIG_PATH);
|
|
6204
|
+
if (!import_path18.default.resolve(configPath).startsWith(node9Dir + import_path18.default.sep)) {
|
|
5943
6205
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
5944
6206
|
return res.end(
|
|
5945
6207
|
JSON.stringify({ error: "configPath must be within the node9 config directory" })
|
|
@@ -5987,20 +6249,77 @@ data: ${JSON.stringify(item.data)}
|
|
|
5987
6249
|
res.writeHead(400).end();
|
|
5988
6250
|
}
|
|
5989
6251
|
}
|
|
6252
|
+
if (req.method === "POST" && pathname === "/taint") {
|
|
6253
|
+
try {
|
|
6254
|
+
const body = JSON.parse(await readBody(req));
|
|
6255
|
+
if (typeof body.path !== "string" || typeof body.source !== "string") {
|
|
6256
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6257
|
+
return res.end(JSON.stringify({ error: "path and source are required strings" }));
|
|
6258
|
+
}
|
|
6259
|
+
const ttlMs = typeof body.ttlMs === "number" ? body.ttlMs : void 0;
|
|
6260
|
+
taintStore.taint(body.path, body.source, ttlMs);
|
|
6261
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6262
|
+
return res.end(JSON.stringify({ ok: true }));
|
|
6263
|
+
} catch {
|
|
6264
|
+
res.writeHead(400).end();
|
|
6265
|
+
return;
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
if (req.method === "POST" && pathname === "/taint/check") {
|
|
6269
|
+
try {
|
|
6270
|
+
const body = JSON.parse(await readBody(req));
|
|
6271
|
+
if (!Array.isArray(body.paths)) {
|
|
6272
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6273
|
+
return res.end(JSON.stringify({ error: "paths must be an array" }));
|
|
6274
|
+
}
|
|
6275
|
+
if (body.paths.some((p) => typeof p !== "string")) {
|
|
6276
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6277
|
+
return res.end(JSON.stringify({ error: "all paths must be strings" }));
|
|
6278
|
+
}
|
|
6279
|
+
for (const p of body.paths) {
|
|
6280
|
+
const record = taintStore.check(p);
|
|
6281
|
+
if (record) {
|
|
6282
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6283
|
+
return res.end(JSON.stringify({ tainted: true, record }));
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
6286
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6287
|
+
return res.end(JSON.stringify({ tainted: false }));
|
|
6288
|
+
} catch {
|
|
6289
|
+
res.writeHead(400).end();
|
|
6290
|
+
return;
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
6293
|
+
if (req.method === "POST" && pathname === "/taint/propagate") {
|
|
6294
|
+
try {
|
|
6295
|
+
const body = JSON.parse(await readBody(req));
|
|
6296
|
+
if (typeof body.src !== "string" || typeof body.dest !== "string") {
|
|
6297
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
6298
|
+
return res.end(JSON.stringify({ error: "src and dest are required strings" }));
|
|
6299
|
+
}
|
|
6300
|
+
const clearSource = body.clearSource === true;
|
|
6301
|
+
taintStore.propagate(body.src, body.dest, clearSource);
|
|
6302
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6303
|
+
return res.end(JSON.stringify({ ok: true }));
|
|
6304
|
+
} catch {
|
|
6305
|
+
res.writeHead(400).end();
|
|
6306
|
+
return;
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
5990
6309
|
res.writeHead(404).end();
|
|
5991
6310
|
});
|
|
5992
6311
|
setDaemonServer(server);
|
|
5993
6312
|
server.on("error", (e) => {
|
|
5994
6313
|
if (e.code === "EADDRINUSE") {
|
|
5995
6314
|
try {
|
|
5996
|
-
if (
|
|
5997
|
-
const { pid } = JSON.parse(
|
|
6315
|
+
if (import_fs15.default.existsSync(DAEMON_PID_FILE)) {
|
|
6316
|
+
const { pid } = JSON.parse(import_fs15.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
5998
6317
|
process.kill(pid, 0);
|
|
5999
6318
|
return process.exit(0);
|
|
6000
6319
|
}
|
|
6001
6320
|
} catch {
|
|
6002
6321
|
try {
|
|
6003
|
-
|
|
6322
|
+
import_fs15.default.unlinkSync(DAEMON_PID_FILE);
|
|
6004
6323
|
} catch {
|
|
6005
6324
|
}
|
|
6006
6325
|
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
@@ -6059,14 +6378,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
6059
6378
|
}
|
|
6060
6379
|
startActivitySocket();
|
|
6061
6380
|
}
|
|
6062
|
-
var import_http,
|
|
6381
|
+
var import_http, import_fs15, import_path18, import_crypto6, import_child_process4, import_chalk2;
|
|
6063
6382
|
var init_server = __esm({
|
|
6064
6383
|
"src/daemon/server.ts"() {
|
|
6065
6384
|
"use strict";
|
|
6066
6385
|
import_http = __toESM(require("http"));
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6386
|
+
import_fs15 = __toESM(require("fs"));
|
|
6387
|
+
import_path18 = __toESM(require("path"));
|
|
6388
|
+
import_crypto6 = require("crypto");
|
|
6070
6389
|
import_child_process4 = require("child_process");
|
|
6071
6390
|
import_chalk2 = __toESM(require("chalk"));
|
|
6072
6391
|
init_core();
|
|
@@ -6080,24 +6399,24 @@ var init_server = __esm({
|
|
|
6080
6399
|
|
|
6081
6400
|
// src/daemon/index.ts
|
|
6082
6401
|
function stopDaemon() {
|
|
6083
|
-
if (!
|
|
6402
|
+
if (!import_fs16.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
|
|
6084
6403
|
try {
|
|
6085
|
-
const { pid } = JSON.parse(
|
|
6404
|
+
const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6086
6405
|
process.kill(pid, "SIGTERM");
|
|
6087
6406
|
console.log(import_chalk3.default.green("\u2705 Stopped."));
|
|
6088
6407
|
} catch {
|
|
6089
6408
|
console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
|
|
6090
6409
|
} finally {
|
|
6091
6410
|
try {
|
|
6092
|
-
|
|
6411
|
+
import_fs16.default.unlinkSync(DAEMON_PID_FILE);
|
|
6093
6412
|
} catch {
|
|
6094
6413
|
}
|
|
6095
6414
|
}
|
|
6096
6415
|
}
|
|
6097
6416
|
function daemonStatus() {
|
|
6098
|
-
if (
|
|
6417
|
+
if (import_fs16.default.existsSync(DAEMON_PID_FILE)) {
|
|
6099
6418
|
try {
|
|
6100
|
-
const { pid } = JSON.parse(
|
|
6419
|
+
const { pid } = JSON.parse(import_fs16.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
6101
6420
|
process.kill(pid, 0);
|
|
6102
6421
|
console.log(import_chalk3.default.green("Node9 daemon: running"));
|
|
6103
6422
|
return;
|
|
@@ -6116,11 +6435,11 @@ function daemonStatus() {
|
|
|
6116
6435
|
console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
|
|
6117
6436
|
}
|
|
6118
6437
|
}
|
|
6119
|
-
var
|
|
6438
|
+
var import_fs16, import_chalk3, import_child_process5;
|
|
6120
6439
|
var init_daemon2 = __esm({
|
|
6121
6440
|
"src/daemon/index.ts"() {
|
|
6122
6441
|
"use strict";
|
|
6123
|
-
|
|
6442
|
+
import_fs16 = __toESM(require("fs"));
|
|
6124
6443
|
import_chalk3 = __toESM(require("chalk"));
|
|
6125
6444
|
import_child_process5 = require("child_process");
|
|
6126
6445
|
init_server();
|
|
@@ -6171,9 +6490,9 @@ function renderPending(activity) {
|
|
|
6171
6490
|
}
|
|
6172
6491
|
async function ensureDaemon() {
|
|
6173
6492
|
let pidPort = null;
|
|
6174
|
-
if (
|
|
6493
|
+
if (import_fs24.default.existsSync(PID_FILE)) {
|
|
6175
6494
|
try {
|
|
6176
|
-
const { port } = JSON.parse(
|
|
6495
|
+
const { port } = JSON.parse(import_fs24.default.readFileSync(PID_FILE, "utf-8"));
|
|
6177
6496
|
pidPort = port;
|
|
6178
6497
|
} catch {
|
|
6179
6498
|
console.error(import_chalk16.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -6244,9 +6563,12 @@ function buildCardLines(req, localCount = 0) {
|
|
|
6244
6563
|
``,
|
|
6245
6564
|
`${BOLD}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET}`,
|
|
6246
6565
|
`${CYAN}\u2551${RESET} Tool: ${BOLD}${req.toolName}${RESET}`,
|
|
6247
|
-
`${CYAN}\u2551${RESET} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET}
|
|
6248
|
-
`${CYAN}\u2551${RESET} Args: ${GRAY}${argsPreview}${RESET}`
|
|
6566
|
+
`${CYAN}\u2551${RESET} Reason: ${tierLabel} \u2014 ${blockedBy}${RESET}`
|
|
6249
6567
|
];
|
|
6568
|
+
if (req.riskMetadata?.ruleName && blockedBy.includes("Taint")) {
|
|
6569
|
+
lines.push(`${CYAN}\u2551${RESET} ${YELLOW}\u26A0 ${req.riskMetadata.ruleName}${RESET}`);
|
|
6570
|
+
}
|
|
6571
|
+
lines.push(`${CYAN}\u2551${RESET} Args: ${GRAY}${argsPreview}${RESET}`);
|
|
6250
6572
|
if (localCount >= 2) {
|
|
6251
6573
|
lines.push(
|
|
6252
6574
|
`${CYAN}\u2551${RESET} ${YELLOW}\u{1F4A1}${RESET} Approved ${localCount}\xD7 before \u2014 ${BOLD}[a]${RESET}${YELLOW} creates a permanent rule${RESET}`
|
|
@@ -6308,12 +6630,13 @@ async function startTail(options = {}) {
|
|
|
6308
6630
|
if (canApprove) import_readline3.default.emitKeypressEvents(process.stdin);
|
|
6309
6631
|
function clearCard() {
|
|
6310
6632
|
if (cardLineCount > 0) {
|
|
6311
|
-
process.stdout
|
|
6633
|
+
import_readline3.default.moveCursor(process.stdout, 0, -cardLineCount);
|
|
6634
|
+
process.stdout.write(ERASE_DOWN);
|
|
6312
6635
|
cardLineCount = 0;
|
|
6313
6636
|
}
|
|
6314
6637
|
}
|
|
6315
6638
|
function printCard(req2) {
|
|
6316
|
-
process.stdout.write(HIDE_CURSOR
|
|
6639
|
+
process.stdout.write(HIDE_CURSOR);
|
|
6317
6640
|
const daemonPrior = req2.allowCount !== void 0 ? req2.allowCount - 1 : 0;
|
|
6318
6641
|
const localPrior = localAllowCounts.get(req2.toolName) ?? 0;
|
|
6319
6642
|
const priorCount = Math.max(daemonPrior, localPrior);
|
|
@@ -6349,7 +6672,7 @@ async function startTail(options = {}) {
|
|
|
6349
6672
|
if (settled) return;
|
|
6350
6673
|
settled = true;
|
|
6351
6674
|
cleanup();
|
|
6352
|
-
|
|
6675
|
+
clearCard();
|
|
6353
6676
|
const stampedLines = buildCardLines(
|
|
6354
6677
|
req2,
|
|
6355
6678
|
Math.max(
|
|
@@ -6380,8 +6703,8 @@ async function startTail(options = {}) {
|
|
|
6380
6703
|
}
|
|
6381
6704
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err) => {
|
|
6382
6705
|
try {
|
|
6383
|
-
|
|
6384
|
-
|
|
6706
|
+
import_fs24.default.appendFileSync(
|
|
6707
|
+
import_path26.default.join(import_os21.default.homedir(), ".node9", "hook-debug.log"),
|
|
6385
6708
|
`[tail] POST /decision failed: ${String(err)}
|
|
6386
6709
|
`
|
|
6387
6710
|
);
|
|
@@ -6396,7 +6719,7 @@ async function startTail(options = {}) {
|
|
|
6396
6719
|
if (settled) return;
|
|
6397
6720
|
settled = true;
|
|
6398
6721
|
cleanup();
|
|
6399
|
-
|
|
6722
|
+
clearCard();
|
|
6400
6723
|
const priorCount = Math.max(
|
|
6401
6724
|
req2.allowCount !== void 0 ? req2.allowCount - 1 : 0,
|
|
6402
6725
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
@@ -6593,21 +6916,21 @@ async function startTail(options = {}) {
|
|
|
6593
6916
|
process.exit(1);
|
|
6594
6917
|
});
|
|
6595
6918
|
}
|
|
6596
|
-
var import_http2, import_chalk16,
|
|
6919
|
+
var import_http2, import_chalk16, import_fs24, import_os21, import_path26, import_readline3, import_child_process13, PID_FILE, ICONS, RESET, BOLD, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN;
|
|
6597
6920
|
var init_tail = __esm({
|
|
6598
6921
|
"src/tui/tail.ts"() {
|
|
6599
6922
|
"use strict";
|
|
6600
6923
|
import_http2 = __toESM(require("http"));
|
|
6601
6924
|
import_chalk16 = __toESM(require("chalk"));
|
|
6602
|
-
|
|
6925
|
+
import_fs24 = __toESM(require("fs"));
|
|
6603
6926
|
import_os21 = __toESM(require("os"));
|
|
6604
|
-
|
|
6927
|
+
import_path26 = __toESM(require("path"));
|
|
6605
6928
|
import_readline3 = __toESM(require("readline"));
|
|
6606
6929
|
import_child_process13 = require("child_process");
|
|
6607
6930
|
init_daemon2();
|
|
6608
6931
|
init_daemon();
|
|
6609
6932
|
init_core();
|
|
6610
|
-
PID_FILE =
|
|
6933
|
+
PID_FILE = import_path26.default.join(import_os21.default.homedir(), ".node9", "daemon.pid");
|
|
6611
6934
|
ICONS = {
|
|
6612
6935
|
bash: "\u{1F4BB}",
|
|
6613
6936
|
shell: "\u{1F4BB}",
|
|
@@ -6635,8 +6958,6 @@ var init_tail = __esm({
|
|
|
6635
6958
|
HIDE_CURSOR = "\x1B[?25l";
|
|
6636
6959
|
SHOW_CURSOR = "\x1B[?25h";
|
|
6637
6960
|
ERASE_DOWN = "\x1B[J";
|
|
6638
|
-
SAVE_CURSOR = "\x1B7";
|
|
6639
|
-
RESTORE_CURSOR = "\x1B8";
|
|
6640
6961
|
}
|
|
6641
6962
|
});
|
|
6642
6963
|
|
|
@@ -7033,8 +7354,8 @@ async function setupCursor() {
|
|
|
7033
7354
|
// src/cli.ts
|
|
7034
7355
|
init_daemon2();
|
|
7035
7356
|
var import_chalk17 = __toESM(require("chalk"));
|
|
7036
|
-
var
|
|
7037
|
-
var
|
|
7357
|
+
var import_fs25 = __toESM(require("fs"));
|
|
7358
|
+
var import_path27 = __toESM(require("path"));
|
|
7038
7359
|
var import_os22 = __toESM(require("os"));
|
|
7039
7360
|
var import_prompts3 = require("@inquirer/prompts");
|
|
7040
7361
|
|
|
@@ -7256,8 +7577,8 @@ async function autoStartDaemonAndWait() {
|
|
|
7256
7577
|
|
|
7257
7578
|
// src/cli/commands/check.ts
|
|
7258
7579
|
var import_chalk5 = __toESM(require("chalk"));
|
|
7259
|
-
var
|
|
7260
|
-
var
|
|
7580
|
+
var import_fs18 = __toESM(require("fs"));
|
|
7581
|
+
var import_path20 = __toESM(require("path"));
|
|
7261
7582
|
var import_os15 = __toESM(require("os"));
|
|
7262
7583
|
init_orchestrator();
|
|
7263
7584
|
init_daemon();
|
|
@@ -7266,26 +7587,26 @@ init_policy();
|
|
|
7266
7587
|
|
|
7267
7588
|
// src/undo.ts
|
|
7268
7589
|
var import_child_process8 = require("child_process");
|
|
7269
|
-
var
|
|
7270
|
-
var
|
|
7271
|
-
var
|
|
7590
|
+
var import_crypto7 = __toESM(require("crypto"));
|
|
7591
|
+
var import_fs17 = __toESM(require("fs"));
|
|
7592
|
+
var import_path19 = __toESM(require("path"));
|
|
7272
7593
|
var import_os14 = __toESM(require("os"));
|
|
7273
|
-
var SNAPSHOT_STACK_PATH =
|
|
7274
|
-
var UNDO_LATEST_PATH =
|
|
7594
|
+
var SNAPSHOT_STACK_PATH = import_path19.default.join(import_os14.default.homedir(), ".node9", "snapshots.json");
|
|
7595
|
+
var UNDO_LATEST_PATH = import_path19.default.join(import_os14.default.homedir(), ".node9", "undo_latest.txt");
|
|
7275
7596
|
var MAX_SNAPSHOTS = 10;
|
|
7276
7597
|
var GIT_TIMEOUT = 15e3;
|
|
7277
7598
|
function readStack() {
|
|
7278
7599
|
try {
|
|
7279
|
-
if (
|
|
7280
|
-
return JSON.parse(
|
|
7600
|
+
if (import_fs17.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
7601
|
+
return JSON.parse(import_fs17.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
7281
7602
|
} catch {
|
|
7282
7603
|
}
|
|
7283
7604
|
return [];
|
|
7284
7605
|
}
|
|
7285
7606
|
function writeStack(stack) {
|
|
7286
|
-
const dir =
|
|
7287
|
-
if (!
|
|
7288
|
-
|
|
7607
|
+
const dir = import_path19.default.dirname(SNAPSHOT_STACK_PATH);
|
|
7608
|
+
if (!import_fs17.default.existsSync(dir)) import_fs17.default.mkdirSync(dir, { recursive: true });
|
|
7609
|
+
import_fs17.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
7289
7610
|
}
|
|
7290
7611
|
function buildArgsSummary(tool, args) {
|
|
7291
7612
|
if (!args || typeof args !== "object") return "";
|
|
@@ -7301,7 +7622,7 @@ function buildArgsSummary(tool, args) {
|
|
|
7301
7622
|
function normalizeCwdForHash(cwd) {
|
|
7302
7623
|
let normalized;
|
|
7303
7624
|
try {
|
|
7304
|
-
normalized =
|
|
7625
|
+
normalized = import_fs17.default.realpathSync(cwd);
|
|
7305
7626
|
} catch {
|
|
7306
7627
|
normalized = cwd;
|
|
7307
7628
|
}
|
|
@@ -7310,17 +7631,17 @@ function normalizeCwdForHash(cwd) {
|
|
|
7310
7631
|
return normalized;
|
|
7311
7632
|
}
|
|
7312
7633
|
function getShadowRepoDir(cwd) {
|
|
7313
|
-
const hash =
|
|
7314
|
-
return
|
|
7634
|
+
const hash = import_crypto7.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
7635
|
+
return import_path19.default.join(import_os14.default.homedir(), ".node9", "snapshots", hash);
|
|
7315
7636
|
}
|
|
7316
7637
|
function cleanOrphanedIndexFiles(shadowDir) {
|
|
7317
7638
|
try {
|
|
7318
7639
|
const cutoff = Date.now() - 6e4;
|
|
7319
|
-
for (const f of
|
|
7640
|
+
for (const f of import_fs17.default.readdirSync(shadowDir)) {
|
|
7320
7641
|
if (f.startsWith("index_")) {
|
|
7321
|
-
const fp =
|
|
7642
|
+
const fp = import_path19.default.join(shadowDir, f);
|
|
7322
7643
|
try {
|
|
7323
|
-
if (
|
|
7644
|
+
if (import_fs17.default.statSync(fp).mtimeMs < cutoff) import_fs17.default.unlinkSync(fp);
|
|
7324
7645
|
} catch {
|
|
7325
7646
|
}
|
|
7326
7647
|
}
|
|
@@ -7332,7 +7653,7 @@ function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
|
7332
7653
|
const hardcoded = [".git", ".node9"];
|
|
7333
7654
|
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
7334
7655
|
try {
|
|
7335
|
-
|
|
7656
|
+
import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
7336
7657
|
} catch {
|
|
7337
7658
|
}
|
|
7338
7659
|
}
|
|
@@ -7345,25 +7666,25 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
7345
7666
|
timeout: 3e3
|
|
7346
7667
|
});
|
|
7347
7668
|
if (check.status === 0) {
|
|
7348
|
-
const ptPath =
|
|
7669
|
+
const ptPath = import_path19.default.join(shadowDir, "project-path.txt");
|
|
7349
7670
|
try {
|
|
7350
|
-
const stored =
|
|
7671
|
+
const stored = import_fs17.default.readFileSync(ptPath, "utf8").trim();
|
|
7351
7672
|
if (stored === normalizedCwd) return true;
|
|
7352
7673
|
if (process.env.NODE9_DEBUG === "1")
|
|
7353
7674
|
console.error(
|
|
7354
7675
|
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
7355
7676
|
);
|
|
7356
|
-
|
|
7677
|
+
import_fs17.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
7357
7678
|
} catch {
|
|
7358
7679
|
try {
|
|
7359
|
-
|
|
7680
|
+
import_fs17.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
7360
7681
|
} catch {
|
|
7361
7682
|
}
|
|
7362
7683
|
return true;
|
|
7363
7684
|
}
|
|
7364
7685
|
}
|
|
7365
7686
|
try {
|
|
7366
|
-
|
|
7687
|
+
import_fs17.default.mkdirSync(shadowDir, { recursive: true });
|
|
7367
7688
|
} catch {
|
|
7368
7689
|
}
|
|
7369
7690
|
const init = (0, import_child_process8.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
@@ -7372,7 +7693,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
7372
7693
|
console.error("[Node9] git init --bare failed:", init.stderr?.toString());
|
|
7373
7694
|
return false;
|
|
7374
7695
|
}
|
|
7375
|
-
const configFile =
|
|
7696
|
+
const configFile = import_path19.default.join(shadowDir, "config");
|
|
7376
7697
|
(0, import_child_process8.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
7377
7698
|
timeout: 3e3
|
|
7378
7699
|
});
|
|
@@ -7380,7 +7701,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
7380
7701
|
timeout: 3e3
|
|
7381
7702
|
});
|
|
7382
7703
|
try {
|
|
7383
|
-
|
|
7704
|
+
import_fs17.default.writeFileSync(import_path19.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
7384
7705
|
} catch {
|
|
7385
7706
|
}
|
|
7386
7707
|
return true;
|
|
@@ -7403,7 +7724,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
7403
7724
|
const shadowDir = getShadowRepoDir(cwd);
|
|
7404
7725
|
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
7405
7726
|
writeShadowExcludes(shadowDir, ignorePaths);
|
|
7406
|
-
indexFile =
|
|
7727
|
+
indexFile = import_path19.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
7407
7728
|
const shadowEnv = {
|
|
7408
7729
|
...process.env,
|
|
7409
7730
|
GIT_DIR: shadowDir,
|
|
@@ -7432,7 +7753,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
7432
7753
|
const shouldGc = stack.length % 5 === 0;
|
|
7433
7754
|
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
|
|
7434
7755
|
writeStack(stack);
|
|
7435
|
-
|
|
7756
|
+
import_fs17.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
7436
7757
|
if (shouldGc) {
|
|
7437
7758
|
(0, import_child_process8.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
7438
7759
|
}
|
|
@@ -7443,7 +7764,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
7443
7764
|
} finally {
|
|
7444
7765
|
if (indexFile) {
|
|
7445
7766
|
try {
|
|
7446
|
-
|
|
7767
|
+
import_fs17.default.unlinkSync(indexFile);
|
|
7447
7768
|
} catch {
|
|
7448
7769
|
}
|
|
7449
7770
|
}
|
|
@@ -7512,9 +7833,9 @@ function applyUndo(hash, cwd) {
|
|
|
7512
7833
|
timeout: GIT_TIMEOUT
|
|
7513
7834
|
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
7514
7835
|
for (const file of [...tracked, ...untracked]) {
|
|
7515
|
-
const fullPath =
|
|
7516
|
-
if (!snapshotFiles.has(file) &&
|
|
7517
|
-
|
|
7836
|
+
const fullPath = import_path19.default.join(dir, file);
|
|
7837
|
+
if (!snapshotFiles.has(file) && import_fs17.default.existsSync(fullPath)) {
|
|
7838
|
+
import_fs17.default.unlinkSync(fullPath);
|
|
7518
7839
|
}
|
|
7519
7840
|
}
|
|
7520
7841
|
return true;
|
|
@@ -7538,9 +7859,9 @@ function registerCheckCommand(program2) {
|
|
|
7538
7859
|
} catch (err) {
|
|
7539
7860
|
const tempConfig = getConfig();
|
|
7540
7861
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
7541
|
-
const logPath =
|
|
7862
|
+
const logPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
7542
7863
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7543
|
-
|
|
7864
|
+
import_fs18.default.appendFileSync(
|
|
7544
7865
|
logPath,
|
|
7545
7866
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
7546
7867
|
RAW: ${raw}
|
|
@@ -7551,10 +7872,10 @@ RAW: ${raw}
|
|
|
7551
7872
|
}
|
|
7552
7873
|
const config = getConfig(payload.cwd || void 0);
|
|
7553
7874
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
7554
|
-
const logPath =
|
|
7555
|
-
if (!
|
|
7556
|
-
|
|
7557
|
-
|
|
7875
|
+
const logPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
7876
|
+
if (!import_fs18.default.existsSync(import_path20.default.dirname(logPath)))
|
|
7877
|
+
import_fs18.default.mkdirSync(import_path20.default.dirname(logPath), { recursive: true });
|
|
7878
|
+
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
7558
7879
|
`);
|
|
7559
7880
|
}
|
|
7560
7881
|
const toolName = sanitize2(payload.tool_name ?? payload.name ?? "");
|
|
@@ -7567,8 +7888,8 @@ RAW: ${raw}
|
|
|
7567
7888
|
const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
|
|
7568
7889
|
let ttyFd = null;
|
|
7569
7890
|
try {
|
|
7570
|
-
ttyFd =
|
|
7571
|
-
const writeTty = (line) =>
|
|
7891
|
+
ttyFd = import_fs18.default.openSync("/dev/tty", "w");
|
|
7892
|
+
const writeTty = (line) => import_fs18.default.writeSync(ttyFd, line + "\n");
|
|
7572
7893
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
7573
7894
|
writeTty(import_chalk5.default.bgRed.white.bold(`
|
|
7574
7895
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
@@ -7584,7 +7905,7 @@ RAW: ${raw}
|
|
|
7584
7905
|
} finally {
|
|
7585
7906
|
if (ttyFd !== null)
|
|
7586
7907
|
try {
|
|
7587
|
-
|
|
7908
|
+
import_fs18.default.closeSync(ttyFd);
|
|
7588
7909
|
} catch {
|
|
7589
7910
|
}
|
|
7590
7911
|
}
|
|
@@ -7615,7 +7936,7 @@ RAW: ${raw}
|
|
|
7615
7936
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
7616
7937
|
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
7617
7938
|
}
|
|
7618
|
-
const safeCwdForAuth = typeof payload.cwd === "string" &&
|
|
7939
|
+
const safeCwdForAuth = typeof payload.cwd === "string" && import_path20.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
7619
7940
|
const result = await authorizeHeadless(toolName, toolInput, meta, {
|
|
7620
7941
|
cwd: safeCwdForAuth
|
|
7621
7942
|
});
|
|
@@ -7627,12 +7948,12 @@ RAW: ${raw}
|
|
|
7627
7948
|
}
|
|
7628
7949
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
|
|
7629
7950
|
try {
|
|
7630
|
-
const tty =
|
|
7631
|
-
|
|
7951
|
+
const tty = import_fs18.default.openSync("/dev/tty", "w");
|
|
7952
|
+
import_fs18.default.writeSync(
|
|
7632
7953
|
tty,
|
|
7633
7954
|
import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
7634
7955
|
);
|
|
7635
|
-
|
|
7956
|
+
import_fs18.default.closeSync(tty);
|
|
7636
7957
|
} catch {
|
|
7637
7958
|
}
|
|
7638
7959
|
const daemonReady = await autoStartDaemonAndWait();
|
|
@@ -7659,9 +7980,9 @@ RAW: ${raw}
|
|
|
7659
7980
|
});
|
|
7660
7981
|
} catch (err) {
|
|
7661
7982
|
if (process.env.NODE9_DEBUG === "1") {
|
|
7662
|
-
const logPath =
|
|
7983
|
+
const logPath = import_path20.default.join(import_os15.default.homedir(), ".node9", "hook-debug.log");
|
|
7663
7984
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
7664
|
-
|
|
7985
|
+
import_fs18.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
7665
7986
|
`);
|
|
7666
7987
|
}
|
|
7667
7988
|
process.exit(0);
|
|
@@ -7695,12 +8016,52 @@ RAW: ${raw}
|
|
|
7695
8016
|
}
|
|
7696
8017
|
|
|
7697
8018
|
// src/cli/commands/log.ts
|
|
7698
|
-
var
|
|
7699
|
-
var
|
|
8019
|
+
var import_fs19 = __toESM(require("fs"));
|
|
8020
|
+
var import_path21 = __toESM(require("path"));
|
|
7700
8021
|
var import_os16 = __toESM(require("os"));
|
|
7701
8022
|
init_audit();
|
|
7702
8023
|
init_config();
|
|
7703
8024
|
init_policy();
|
|
8025
|
+
init_daemon();
|
|
8026
|
+
|
|
8027
|
+
// src/utils/cp-mv-parser.ts
|
|
8028
|
+
function parseCpMvOp(command) {
|
|
8029
|
+
const trimmed = command.trim();
|
|
8030
|
+
const tokens = trimmed.split(/\s+/);
|
|
8031
|
+
if (tokens.length < 3) return null;
|
|
8032
|
+
const [cmd, ...rest] = tokens;
|
|
8033
|
+
const base = cmd.split("/").pop() ?? cmd;
|
|
8034
|
+
if (base !== "cp" && base !== "mv") return null;
|
|
8035
|
+
const args = [];
|
|
8036
|
+
for (const tok of rest) {
|
|
8037
|
+
if (tok === "--") {
|
|
8038
|
+
args.push(...rest.slice(rest.indexOf("--") + 1));
|
|
8039
|
+
break;
|
|
8040
|
+
}
|
|
8041
|
+
if (tok === "-t" || tok === "--target-directory") return null;
|
|
8042
|
+
if (tok.startsWith("--target-directory=")) return null;
|
|
8043
|
+
if (tok.startsWith("-") && !tok.startsWith("--")) {
|
|
8044
|
+
if (tok.includes("t")) return null;
|
|
8045
|
+
continue;
|
|
8046
|
+
}
|
|
8047
|
+
if (tok.startsWith("--")) {
|
|
8048
|
+
continue;
|
|
8049
|
+
}
|
|
8050
|
+
args.push(tok);
|
|
8051
|
+
}
|
|
8052
|
+
if (args.length !== 2) return null;
|
|
8053
|
+
const [src, dest] = args;
|
|
8054
|
+
if (!src || !dest) return null;
|
|
8055
|
+
if (containsShellMetachar(src) || containsShellMetachar(dest)) return null;
|
|
8056
|
+
return { src, dest, clearSource: base === "mv" };
|
|
8057
|
+
}
|
|
8058
|
+
function containsShellMetachar(token) {
|
|
8059
|
+
if (/[$`{;*?]/.test(token)) return true;
|
|
8060
|
+
if (token.includes("\0")) return true;
|
|
8061
|
+
return false;
|
|
8062
|
+
}
|
|
8063
|
+
|
|
8064
|
+
// src/cli/commands/log.ts
|
|
7704
8065
|
function sanitize3(value) {
|
|
7705
8066
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
7706
8067
|
}
|
|
@@ -7719,11 +8080,20 @@ function registerLogCommand(program2) {
|
|
|
7719
8080
|
decision: "allowed",
|
|
7720
8081
|
source: "post-hook"
|
|
7721
8082
|
};
|
|
7722
|
-
const logPath =
|
|
7723
|
-
if (!
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
8083
|
+
const logPath = import_path21.default.join(import_os16.default.homedir(), ".node9", "audit.log");
|
|
8084
|
+
if (!import_fs19.default.existsSync(import_path21.default.dirname(logPath)))
|
|
8085
|
+
import_fs19.default.mkdirSync(import_path21.default.dirname(logPath), { recursive: true });
|
|
8086
|
+
import_fs19.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
8087
|
+
if ((tool === "Bash" || tool === "bash") && isDaemonRunning()) {
|
|
8088
|
+
const command = typeof rawInput === "object" && rawInput !== null && "command" in rawInput && typeof rawInput.command === "string" ? rawInput.command : null;
|
|
8089
|
+
if (command) {
|
|
8090
|
+
const op = parseCpMvOp(command);
|
|
8091
|
+
if (op) {
|
|
8092
|
+
await notifyTaintPropagate(op.src, op.dest, op.clearSource);
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
}
|
|
8096
|
+
const safeCwd = typeof payload.cwd === "string" && import_path21.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
7727
8097
|
const config = getConfig(safeCwd);
|
|
7728
8098
|
if (shouldSnapshot(tool, {}, config)) {
|
|
7729
8099
|
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
@@ -7732,9 +8102,9 @@ function registerLogCommand(program2) {
|
|
|
7732
8102
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7733
8103
|
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
7734
8104
|
`);
|
|
7735
|
-
const debugPath =
|
|
8105
|
+
const debugPath = import_path21.default.join(import_os16.default.homedir(), ".node9", "hook-debug.log");
|
|
7736
8106
|
try {
|
|
7737
|
-
|
|
8107
|
+
import_fs19.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
7738
8108
|
`);
|
|
7739
8109
|
} catch {
|
|
7740
8110
|
}
|
|
@@ -8038,8 +8408,8 @@ function registerConfigShowCommand(program2) {
|
|
|
8038
8408
|
|
|
8039
8409
|
// src/cli/commands/doctor.ts
|
|
8040
8410
|
var import_chalk7 = __toESM(require("chalk"));
|
|
8041
|
-
var
|
|
8042
|
-
var
|
|
8411
|
+
var import_fs20 = __toESM(require("fs"));
|
|
8412
|
+
var import_path22 = __toESM(require("path"));
|
|
8043
8413
|
var import_os17 = __toESM(require("os"));
|
|
8044
8414
|
var import_child_process9 = require("child_process");
|
|
8045
8415
|
init_daemon();
|
|
@@ -8094,10 +8464,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8094
8464
|
);
|
|
8095
8465
|
}
|
|
8096
8466
|
section("Configuration");
|
|
8097
|
-
const globalConfigPath =
|
|
8098
|
-
if (
|
|
8467
|
+
const globalConfigPath = import_path22.default.join(homeDir2, ".node9", "config.json");
|
|
8468
|
+
if (import_fs20.default.existsSync(globalConfigPath)) {
|
|
8099
8469
|
try {
|
|
8100
|
-
JSON.parse(
|
|
8470
|
+
JSON.parse(import_fs20.default.readFileSync(globalConfigPath, "utf-8"));
|
|
8101
8471
|
pass("~/.node9/config.json found and valid");
|
|
8102
8472
|
} catch {
|
|
8103
8473
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -8105,10 +8475,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8105
8475
|
} else {
|
|
8106
8476
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
8107
8477
|
}
|
|
8108
|
-
const projectConfigPath =
|
|
8109
|
-
if (
|
|
8478
|
+
const projectConfigPath = import_path22.default.join(process.cwd(), "node9.config.json");
|
|
8479
|
+
if (import_fs20.default.existsSync(projectConfigPath)) {
|
|
8110
8480
|
try {
|
|
8111
|
-
JSON.parse(
|
|
8481
|
+
JSON.parse(import_fs20.default.readFileSync(projectConfigPath, "utf-8"));
|
|
8112
8482
|
pass("node9.config.json found and valid (project)");
|
|
8113
8483
|
} catch {
|
|
8114
8484
|
fail(
|
|
@@ -8117,8 +8487,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8117
8487
|
);
|
|
8118
8488
|
}
|
|
8119
8489
|
}
|
|
8120
|
-
const credsPath =
|
|
8121
|
-
if (
|
|
8490
|
+
const credsPath = import_path22.default.join(homeDir2, ".node9", "credentials.json");
|
|
8491
|
+
if (import_fs20.default.existsSync(credsPath)) {
|
|
8122
8492
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
8123
8493
|
} else {
|
|
8124
8494
|
warn(
|
|
@@ -8127,10 +8497,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8127
8497
|
);
|
|
8128
8498
|
}
|
|
8129
8499
|
section("Agent Hooks");
|
|
8130
|
-
const claudeSettingsPath =
|
|
8131
|
-
if (
|
|
8500
|
+
const claudeSettingsPath = import_path22.default.join(homeDir2, ".claude", "settings.json");
|
|
8501
|
+
if (import_fs20.default.existsSync(claudeSettingsPath)) {
|
|
8132
8502
|
try {
|
|
8133
|
-
const cs = JSON.parse(
|
|
8503
|
+
const cs = JSON.parse(import_fs20.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
8134
8504
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
8135
8505
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
8136
8506
|
);
|
|
@@ -8146,10 +8516,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8146
8516
|
} else {
|
|
8147
8517
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
8148
8518
|
}
|
|
8149
|
-
const geminiSettingsPath =
|
|
8150
|
-
if (
|
|
8519
|
+
const geminiSettingsPath = import_path22.default.join(homeDir2, ".gemini", "settings.json");
|
|
8520
|
+
if (import_fs20.default.existsSync(geminiSettingsPath)) {
|
|
8151
8521
|
try {
|
|
8152
|
-
const gs = JSON.parse(
|
|
8522
|
+
const gs = JSON.parse(import_fs20.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
8153
8523
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
8154
8524
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
8155
8525
|
);
|
|
@@ -8165,10 +8535,10 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8165
8535
|
} else {
|
|
8166
8536
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
8167
8537
|
}
|
|
8168
|
-
const cursorHooksPath =
|
|
8169
|
-
if (
|
|
8538
|
+
const cursorHooksPath = import_path22.default.join(homeDir2, ".cursor", "hooks.json");
|
|
8539
|
+
if (import_fs20.default.existsSync(cursorHooksPath)) {
|
|
8170
8540
|
try {
|
|
8171
|
-
const cur = JSON.parse(
|
|
8541
|
+
const cur = JSON.parse(import_fs20.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
8172
8542
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
8173
8543
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
8174
8544
|
);
|
|
@@ -8206,8 +8576,8 @@ function registerDoctorCommand(program2, version2) {
|
|
|
8206
8576
|
|
|
8207
8577
|
// src/cli/commands/audit.ts
|
|
8208
8578
|
var import_chalk8 = __toESM(require("chalk"));
|
|
8209
|
-
var
|
|
8210
|
-
var
|
|
8579
|
+
var import_fs21 = __toESM(require("fs"));
|
|
8580
|
+
var import_path23 = __toESM(require("path"));
|
|
8211
8581
|
var import_os18 = __toESM(require("os"));
|
|
8212
8582
|
function formatRelativeTime(timestamp) {
|
|
8213
8583
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
@@ -8221,14 +8591,14 @@ function formatRelativeTime(timestamp) {
|
|
|
8221
8591
|
}
|
|
8222
8592
|
function registerAuditCommand(program2) {
|
|
8223
8593
|
program2.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
8224
|
-
const logPath =
|
|
8225
|
-
if (!
|
|
8594
|
+
const logPath = import_path23.default.join(import_os18.default.homedir(), ".node9", "audit.log");
|
|
8595
|
+
if (!import_fs21.default.existsSync(logPath)) {
|
|
8226
8596
|
console.log(
|
|
8227
8597
|
import_chalk8.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
8228
8598
|
);
|
|
8229
8599
|
return;
|
|
8230
8600
|
}
|
|
8231
|
-
const raw =
|
|
8601
|
+
const raw = import_fs21.default.readFileSync(logPath, "utf-8");
|
|
8232
8602
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
8233
8603
|
let entries = lines.flatMap((line) => {
|
|
8234
8604
|
try {
|
|
@@ -8346,14 +8716,14 @@ function registerDaemonCommand(program2) {
|
|
|
8346
8716
|
|
|
8347
8717
|
// src/cli/commands/status.ts
|
|
8348
8718
|
var import_chalk10 = __toESM(require("chalk"));
|
|
8349
|
-
var
|
|
8350
|
-
var
|
|
8719
|
+
var import_fs22 = __toESM(require("fs"));
|
|
8720
|
+
var import_path24 = __toESM(require("path"));
|
|
8351
8721
|
var import_os19 = __toESM(require("os"));
|
|
8352
8722
|
init_core();
|
|
8353
8723
|
init_daemon();
|
|
8354
8724
|
function readJson2(filePath) {
|
|
8355
8725
|
try {
|
|
8356
|
-
if (
|
|
8726
|
+
if (import_fs22.default.existsSync(filePath)) return JSON.parse(import_fs22.default.readFileSync(filePath, "utf-8"));
|
|
8357
8727
|
} catch {
|
|
8358
8728
|
}
|
|
8359
8729
|
return null;
|
|
@@ -8418,13 +8788,13 @@ function registerStatusCommand(program2) {
|
|
|
8418
8788
|
console.log("");
|
|
8419
8789
|
const modeLabel = settings.mode === "audit" ? import_chalk10.default.blue("audit") : settings.mode === "strict" ? import_chalk10.default.red("strict") : import_chalk10.default.white("standard");
|
|
8420
8790
|
console.log(` Mode: ${modeLabel}`);
|
|
8421
|
-
const projectConfig =
|
|
8422
|
-
const globalConfig =
|
|
8791
|
+
const projectConfig = import_path24.default.join(process.cwd(), "node9.config.json");
|
|
8792
|
+
const globalConfig = import_path24.default.join(import_os19.default.homedir(), ".node9", "config.json");
|
|
8423
8793
|
console.log(
|
|
8424
|
-
` Local: ${
|
|
8794
|
+
` Local: ${import_fs22.default.existsSync(projectConfig) ? import_chalk10.default.green("Active (node9.config.json)") : import_chalk10.default.gray("Not present")}`
|
|
8425
8795
|
);
|
|
8426
8796
|
console.log(
|
|
8427
|
-
` Global: ${
|
|
8797
|
+
` Global: ${import_fs22.default.existsSync(globalConfig) ? import_chalk10.default.green("Active (~/.node9/config.json)") : import_chalk10.default.gray("Not present")}`
|
|
8428
8798
|
);
|
|
8429
8799
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
8430
8800
|
console.log(
|
|
@@ -8433,13 +8803,13 @@ function registerStatusCommand(program2) {
|
|
|
8433
8803
|
}
|
|
8434
8804
|
const homeDir2 = import_os19.default.homedir();
|
|
8435
8805
|
const claudeSettings = readJson2(
|
|
8436
|
-
|
|
8806
|
+
import_path24.default.join(homeDir2, ".claude", "settings.json")
|
|
8437
8807
|
);
|
|
8438
|
-
const claudeConfig = readJson2(
|
|
8808
|
+
const claudeConfig = readJson2(import_path24.default.join(homeDir2, ".claude.json"));
|
|
8439
8809
|
const geminiSettings = readJson2(
|
|
8440
|
-
|
|
8810
|
+
import_path24.default.join(homeDir2, ".gemini", "settings.json")
|
|
8441
8811
|
);
|
|
8442
|
-
const cursorConfig = readJson2(
|
|
8812
|
+
const cursorConfig = readJson2(import_path24.default.join(homeDir2, ".cursor", "mcp.json"));
|
|
8443
8813
|
const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig;
|
|
8444
8814
|
if (agentFound) {
|
|
8445
8815
|
console.log("");
|
|
@@ -8498,15 +8868,15 @@ function registerStatusCommand(program2) {
|
|
|
8498
8868
|
|
|
8499
8869
|
// src/cli/commands/init.ts
|
|
8500
8870
|
var import_chalk11 = __toESM(require("chalk"));
|
|
8501
|
-
var
|
|
8502
|
-
var
|
|
8871
|
+
var import_fs23 = __toESM(require("fs"));
|
|
8872
|
+
var import_path25 = __toESM(require("path"));
|
|
8503
8873
|
var import_os20 = __toESM(require("os"));
|
|
8504
8874
|
init_core();
|
|
8505
8875
|
function registerInitCommand(program2) {
|
|
8506
8876
|
program2.command("init").description("Set up Node9: create config and wire all detected AI agents").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").option("--skip-setup", "Only create config \u2014 do not wire AI agents").action(async (options) => {
|
|
8507
8877
|
console.log(import_chalk11.default.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
8508
|
-
const configPath =
|
|
8509
|
-
if (
|
|
8878
|
+
const configPath = import_path25.default.join(import_os20.default.homedir(), ".node9", "config.json");
|
|
8879
|
+
if (import_fs23.default.existsSync(configPath) && !options.force) {
|
|
8510
8880
|
console.log(import_chalk11.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
8511
8881
|
} else {
|
|
8512
8882
|
const requestedMode = options.mode.toLowerCase();
|
|
@@ -8515,9 +8885,9 @@ function registerInitCommand(program2) {
|
|
|
8515
8885
|
...DEFAULT_CONFIG,
|
|
8516
8886
|
settings: { ...DEFAULT_CONFIG.settings, mode: safeMode }
|
|
8517
8887
|
};
|
|
8518
|
-
const dir =
|
|
8519
|
-
if (!
|
|
8520
|
-
|
|
8888
|
+
const dir = import_path25.default.dirname(configPath);
|
|
8889
|
+
if (!import_fs23.default.existsSync(dir)) import_fs23.default.mkdirSync(dir, { recursive: true });
|
|
8890
|
+
import_fs23.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
8521
8891
|
console.log(import_chalk11.default.green(`\u2705 Config created: ${configPath}`));
|
|
8522
8892
|
console.log(import_chalk11.default.gray(` Mode: ${safeMode}`));
|
|
8523
8893
|
}
|
|
@@ -8973,20 +9343,20 @@ function registerTrustCommand(program2) {
|
|
|
8973
9343
|
|
|
8974
9344
|
// src/cli.ts
|
|
8975
9345
|
var { version } = JSON.parse(
|
|
8976
|
-
|
|
9346
|
+
import_fs25.default.readFileSync(import_path27.default.join(__dirname, "../package.json"), "utf-8")
|
|
8977
9347
|
);
|
|
8978
9348
|
var program = new import_commander.Command();
|
|
8979
9349
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
8980
9350
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
8981
9351
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
8982
|
-
const credPath =
|
|
8983
|
-
if (!
|
|
8984
|
-
|
|
9352
|
+
const credPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "credentials.json");
|
|
9353
|
+
if (!import_fs25.default.existsSync(import_path27.default.dirname(credPath)))
|
|
9354
|
+
import_fs25.default.mkdirSync(import_path27.default.dirname(credPath), { recursive: true });
|
|
8985
9355
|
const profileName = options.profile || "default";
|
|
8986
9356
|
let existingCreds = {};
|
|
8987
9357
|
try {
|
|
8988
|
-
if (
|
|
8989
|
-
const raw = JSON.parse(
|
|
9358
|
+
if (import_fs25.default.existsSync(credPath)) {
|
|
9359
|
+
const raw = JSON.parse(import_fs25.default.readFileSync(credPath, "utf-8"));
|
|
8990
9360
|
if (raw.apiKey) {
|
|
8991
9361
|
existingCreds = {
|
|
8992
9362
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -8998,13 +9368,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
8998
9368
|
} catch {
|
|
8999
9369
|
}
|
|
9000
9370
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
9001
|
-
|
|
9371
|
+
import_fs25.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
9002
9372
|
if (profileName === "default") {
|
|
9003
|
-
const configPath =
|
|
9373
|
+
const configPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "config.json");
|
|
9004
9374
|
let config = {};
|
|
9005
9375
|
try {
|
|
9006
|
-
if (
|
|
9007
|
-
config = JSON.parse(
|
|
9376
|
+
if (import_fs25.default.existsSync(configPath))
|
|
9377
|
+
config = JSON.parse(import_fs25.default.readFileSync(configPath, "utf-8"));
|
|
9008
9378
|
} catch {
|
|
9009
9379
|
}
|
|
9010
9380
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -9019,9 +9389,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
9019
9389
|
approvers.cloud = false;
|
|
9020
9390
|
}
|
|
9021
9391
|
s.approvers = approvers;
|
|
9022
|
-
if (!
|
|
9023
|
-
|
|
9024
|
-
|
|
9392
|
+
if (!import_fs25.default.existsSync(import_path27.default.dirname(configPath)))
|
|
9393
|
+
import_fs25.default.mkdirSync(import_path27.default.dirname(configPath), { recursive: true });
|
|
9394
|
+
import_fs25.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
9025
9395
|
}
|
|
9026
9396
|
if (options.profile && profileName !== "default") {
|
|
9027
9397
|
console.log(import_chalk17.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -9107,15 +9477,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
9107
9477
|
}
|
|
9108
9478
|
}
|
|
9109
9479
|
if (options.purge) {
|
|
9110
|
-
const node9Dir =
|
|
9111
|
-
if (
|
|
9480
|
+
const node9Dir = import_path27.default.join(import_os22.default.homedir(), ".node9");
|
|
9481
|
+
if (import_fs25.default.existsSync(node9Dir)) {
|
|
9112
9482
|
const confirmed = await (0, import_prompts3.confirm)({
|
|
9113
9483
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
9114
9484
|
default: false
|
|
9115
9485
|
});
|
|
9116
9486
|
if (confirmed) {
|
|
9117
|
-
|
|
9118
|
-
if (
|
|
9487
|
+
import_fs25.default.rmSync(node9Dir, { recursive: true });
|
|
9488
|
+
if (import_fs25.default.existsSync(node9Dir)) {
|
|
9119
9489
|
console.error(
|
|
9120
9490
|
import_chalk17.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
9121
9491
|
);
|
|
@@ -9327,9 +9697,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
9327
9697
|
const isCheckHook = process.argv[2] === "check";
|
|
9328
9698
|
if (isCheckHook) {
|
|
9329
9699
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
9330
|
-
const logPath =
|
|
9700
|
+
const logPath = import_path27.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log");
|
|
9331
9701
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
9332
|
-
|
|
9702
|
+
import_fs25.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
9333
9703
|
`);
|
|
9334
9704
|
}
|
|
9335
9705
|
process.exit(0);
|