@node9/proxy 1.19.2 → 1.19.4
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 +200 -112
- package/dist/cli.mjs +168 -80
- package/dist/dashboard.mjs +4651 -0
- package/dist/index.js +21 -23
- package/dist/index.mjs +17 -19
- package/package.json +4 -1
package/dist/cli.mjs
CHANGED
|
@@ -1692,7 +1692,11 @@ function extractCanonicalFindings(call, ctx) {
|
|
|
1692
1692
|
})
|
|
1693
1693
|
);
|
|
1694
1694
|
}
|
|
1695
|
-
|
|
1695
|
+
const ast = analyzeShellCommand(command);
|
|
1696
|
+
const sudoVariant = ast.actions.includes("sudo") || ast.actions.includes("su");
|
|
1697
|
+
const chmodVariant = ast.actions.includes("chmod") && (ast.allTokens.includes("777") || ast.allTokens.includes("0777") || ast.allTokens.includes("+x"));
|
|
1698
|
+
const chownVariant = ast.actions.includes("chown") && ast.allTokens.includes("root");
|
|
1699
|
+
if (sudoVariant || chmodVariant || chownVariant) {
|
|
1696
1700
|
out.push(
|
|
1697
1701
|
makeFinding({
|
|
1698
1702
|
type: "privilege-escalation",
|
|
@@ -1824,7 +1828,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
1824
1828
|
}
|
|
1825
1829
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
1826
1830
|
}
|
|
1827
|
-
var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE,
|
|
1831
|
+
var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, COST_PER_LOOP_ITER_USD, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC_RE, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE;
|
|
1828
1832
|
var init_dist = __esm({
|
|
1829
1833
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1830
1834
|
"use strict";
|
|
@@ -3224,7 +3228,6 @@ var init_dist = __esm({
|
|
|
3224
3228
|
LOOP_THRESHOLD_FOR_WASTE = 3;
|
|
3225
3229
|
COST_PER_LOOP_ITER_USD = 6e-3;
|
|
3226
3230
|
DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
|
|
3227
|
-
PRIVILEGE_ESCALATION_RE = /\b(sudo|su)\b\s+[a-z]|\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
|
|
3228
3231
|
SENSITIVE_PATH_RE = /\.aws\/(credentials|config)\b|\.ssh\/(id_rsa|id_ed25519|id_ecdsa|id_dsa)\b|\.env(\.|$|\b)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
|
|
3229
3232
|
FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
3230
3233
|
"read",
|
|
@@ -3244,7 +3247,7 @@ var init_dist = __esm({
|
|
|
3244
3247
|
PII_PHONE_RE = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/;
|
|
3245
3248
|
PII_CC_RE = /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6\d{3})[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/;
|
|
3246
3249
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
3247
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
3250
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
3248
3251
|
DEDUPE_PREVIEW_LEN = 120;
|
|
3249
3252
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
3250
3253
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -4651,7 +4654,6 @@ import fs9 from "fs";
|
|
|
4651
4654
|
import net from "net";
|
|
4652
4655
|
import path9 from "path";
|
|
4653
4656
|
import os8 from "os";
|
|
4654
|
-
import { spawnSync } from "child_process";
|
|
4655
4657
|
function notifyActivitySocket(data) {
|
|
4656
4658
|
return new Promise((resolve) => {
|
|
4657
4659
|
try {
|
|
@@ -4721,19 +4723,16 @@ function isDaemonRunning() {
|
|
|
4721
4723
|
}
|
|
4722
4724
|
return false;
|
|
4723
4725
|
}
|
|
4724
|
-
|
|
4725
|
-
encoding: "utf8",
|
|
4726
|
-
timeout: 300
|
|
4727
|
-
});
|
|
4728
|
-
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) return true;
|
|
4729
|
-
return false;
|
|
4726
|
+
return true;
|
|
4730
4727
|
}
|
|
4728
|
+
return false;
|
|
4729
|
+
}
|
|
4730
|
+
async function isDaemonReachable(timeoutMs = 500) {
|
|
4731
4731
|
try {
|
|
4732
|
-
const
|
|
4733
|
-
|
|
4734
|
-
timeout: 300
|
|
4732
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/settings`, {
|
|
4733
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
4735
4734
|
});
|
|
4736
|
-
return
|
|
4735
|
+
return res.ok;
|
|
4737
4736
|
} catch {
|
|
4738
4737
|
return false;
|
|
4739
4738
|
}
|
|
@@ -4792,7 +4791,7 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
4792
4791
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
4793
4792
|
}
|
|
4794
4793
|
}
|
|
4795
|
-
async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
4794
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
|
|
4796
4795
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
4797
4796
|
const res = await fetch(`${base}/check`, {
|
|
4798
4797
|
method: "POST",
|
|
@@ -4803,7 +4802,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
4803
4802
|
slackDelegated: true,
|
|
4804
4803
|
agent: meta?.agent,
|
|
4805
4804
|
mcpServer: meta?.mcpServer,
|
|
4806
|
-
...riskMetadata && { riskMetadata }
|
|
4805
|
+
...riskMetadata && { riskMetadata },
|
|
4806
|
+
// fromCLI=true tells the daemon the CLI already sent the activity
|
|
4807
|
+
// event via socket. Same contract as registerDaemonEntry — without
|
|
4808
|
+
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
4809
|
+
fromCLI: socketActivitySent !== false,
|
|
4810
|
+
activityId
|
|
4807
4811
|
}),
|
|
4808
4812
|
signal: AbortSignal.timeout(3e3)
|
|
4809
4813
|
});
|
|
@@ -5944,7 +5948,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5944
5948
|
let daemonAllowCount = 1;
|
|
5945
5949
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
5946
5950
|
if (cloudEnforced && cloudRequestId) {
|
|
5947
|
-
const viewer = await notifyDaemonViewer(
|
|
5951
|
+
const viewer = await notifyDaemonViewer(
|
|
5952
|
+
toolName,
|
|
5953
|
+
args,
|
|
5954
|
+
meta,
|
|
5955
|
+
riskMetadata,
|
|
5956
|
+
options?.activityId,
|
|
5957
|
+
options?.socketActivitySent
|
|
5958
|
+
).catch(() => null);
|
|
5948
5959
|
viewerId = viewer?.id ?? null;
|
|
5949
5960
|
daemonEntryId = viewerId;
|
|
5950
5961
|
if (viewer) daemonAllowCount = viewer.allowCount;
|
|
@@ -8034,6 +8045,7 @@ __export(scan_watermark_exports, {
|
|
|
8034
8045
|
markUploadComplete: () => markUploadComplete,
|
|
8035
8046
|
saveWatermark: () => saveWatermark,
|
|
8036
8047
|
scanDelta: () => scanDelta,
|
|
8048
|
+
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
8037
8049
|
tickScanWatcher: () => tickScanWatcher
|
|
8038
8050
|
});
|
|
8039
8051
|
import fs17 from "fs";
|
|
@@ -8241,6 +8253,25 @@ function extractFindingsFromLine(line, sessionId, lineIndex) {
|
|
|
8241
8253
|
}
|
|
8242
8254
|
return findings;
|
|
8243
8255
|
}
|
|
8256
|
+
async function tickForensicBroadcast(offsets) {
|
|
8257
|
+
const out = [];
|
|
8258
|
+
const files = listJsonlFiles();
|
|
8259
|
+
for (const file of files) {
|
|
8260
|
+
const size = fileSize(file);
|
|
8261
|
+
const offset = offsets.get(file);
|
|
8262
|
+
if (offset === void 0) {
|
|
8263
|
+
offsets.set(file, size);
|
|
8264
|
+
continue;
|
|
8265
|
+
}
|
|
8266
|
+
if (size <= offset) continue;
|
|
8267
|
+
const sessionId = path19.basename(file, ".jsonl");
|
|
8268
|
+
const newOffset = await scanDelta(file, offset, (obj, lineIndex) => {
|
|
8269
|
+
out.push(...extractFindingsFromLine(obj, sessionId, lineIndex));
|
|
8270
|
+
});
|
|
8271
|
+
offsets.set(file, newOffset);
|
|
8272
|
+
}
|
|
8273
|
+
return out;
|
|
8274
|
+
}
|
|
8244
8275
|
function markUploadComplete() {
|
|
8245
8276
|
const state = loadWatermark();
|
|
8246
8277
|
if (state.status === "schema-future") return;
|
|
@@ -11010,6 +11041,19 @@ data: ${JSON.stringify(data)}
|
|
|
11010
11041
|
}
|
|
11011
11042
|
});
|
|
11012
11043
|
}
|
|
11044
|
+
function broadcastForensic(finding) {
|
|
11045
|
+
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
11046
|
+
const event = {
|
|
11047
|
+
type: "forensic",
|
|
11048
|
+
id: `fnd_${randomUUID3()}`,
|
|
11049
|
+
ts: Date.now(),
|
|
11050
|
+
sessionId: finding.sessionId,
|
|
11051
|
+
category: finding.type,
|
|
11052
|
+
severity
|
|
11053
|
+
};
|
|
11054
|
+
if (finding.patternName !== void 0) event.patternName = finding.patternName;
|
|
11055
|
+
broadcast("forensic", event);
|
|
11056
|
+
}
|
|
11013
11057
|
function abandonPending() {
|
|
11014
11058
|
setAbandonTimer(null);
|
|
11015
11059
|
pending.forEach((entry, id) => {
|
|
@@ -11193,7 +11237,7 @@ function bindActivitySocket() {
|
|
|
11193
11237
|
});
|
|
11194
11238
|
activitySocketServer = unixServer;
|
|
11195
11239
|
}
|
|
11196
|
-
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
|
|
11240
|
+
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, pending, sseClients, suggestionTracker, taintStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, CRITICAL_FORENSIC_CATEGORIES, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
|
|
11197
11241
|
var init_state2 = __esm({
|
|
11198
11242
|
"src/daemon/state.ts"() {
|
|
11199
11243
|
"use strict";
|
|
@@ -11238,6 +11282,11 @@ var init_state2 = __esm({
|
|
|
11238
11282
|
INPUT_PRICE_PER_1M = 3;
|
|
11239
11283
|
OUTPUT_PRICE_PER_1M = 15;
|
|
11240
11284
|
BYTES_PER_TOKEN = 4;
|
|
11285
|
+
CRITICAL_FORENSIC_CATEGORIES = /* @__PURE__ */ new Set([
|
|
11286
|
+
"privilege-escalation",
|
|
11287
|
+
"destructive-op",
|
|
11288
|
+
"eval-of-remote"
|
|
11289
|
+
]);
|
|
11241
11290
|
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
11242
11291
|
"write",
|
|
11243
11292
|
"write_file",
|
|
@@ -11582,7 +11631,26 @@ function startCloudSync() {
|
|
|
11582
11631
|
const recurring = setInterval(() => void syncOnce(), intervalMs);
|
|
11583
11632
|
recurring.unref();
|
|
11584
11633
|
}
|
|
11585
|
-
|
|
11634
|
+
function startForensicBroadcast() {
|
|
11635
|
+
const tick = async () => {
|
|
11636
|
+
try {
|
|
11637
|
+
const findings = await tickForensicBroadcast(forensicBroadcastOffsets);
|
|
11638
|
+
for (const f of findings) broadcastForensic(f);
|
|
11639
|
+
} catch (err2) {
|
|
11640
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
11641
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
11642
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11643
|
+
kind: "forensic-broadcast-error",
|
|
11644
|
+
error: msg
|
|
11645
|
+
});
|
|
11646
|
+
}
|
|
11647
|
+
};
|
|
11648
|
+
const initial = setTimeout(() => void tick(), FORENSIC_INITIAL_DELAY_MS);
|
|
11649
|
+
initial.unref();
|
|
11650
|
+
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
11651
|
+
recurring.unref();
|
|
11652
|
+
}
|
|
11653
|
+
var FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
11586
11654
|
var init_sync = __esm({
|
|
11587
11655
|
"src/daemon/sync.ts"() {
|
|
11588
11656
|
"use strict";
|
|
@@ -11590,6 +11658,8 @@ var init_sync = __esm({
|
|
|
11590
11658
|
init_blast();
|
|
11591
11659
|
init_dist();
|
|
11592
11660
|
init_scan_watermark();
|
|
11661
|
+
init_state2();
|
|
11662
|
+
init_audit();
|
|
11593
11663
|
FINDING_TO_SIGNAL3 = {
|
|
11594
11664
|
dlp: "dlpFindings",
|
|
11595
11665
|
pii: "piiFindings",
|
|
@@ -11606,6 +11676,9 @@ var init_sync = __esm({
|
|
|
11606
11676
|
DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
11607
11677
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
11608
11678
|
MIN_INTERVAL_HOURS = 1;
|
|
11679
|
+
FORENSIC_BROADCAST_INTERVAL_MS = 3e4;
|
|
11680
|
+
FORENSIC_INITIAL_DELAY_MS = 5e3;
|
|
11681
|
+
forensicBroadcastOffsets = /* @__PURE__ */ new Map();
|
|
11609
11682
|
}
|
|
11610
11683
|
});
|
|
11611
11684
|
|
|
@@ -11834,11 +11907,12 @@ import fs25 from "fs";
|
|
|
11834
11907
|
import path27 from "path";
|
|
11835
11908
|
import os23 from "os";
|
|
11836
11909
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11837
|
-
import { spawnSync
|
|
11910
|
+
import { spawnSync } from "child_process";
|
|
11838
11911
|
import chalk6 from "chalk";
|
|
11839
11912
|
function startDaemon() {
|
|
11840
11913
|
startCostSync();
|
|
11841
11914
|
startCloudSync();
|
|
11915
|
+
startForensicBroadcast();
|
|
11842
11916
|
startDlpScanner();
|
|
11843
11917
|
loadInsightCounts();
|
|
11844
11918
|
const internalToken = randomUUID4();
|
|
@@ -12644,13 +12718,26 @@ data: ${JSON.stringify(item.data)}
|
|
|
12644
12718
|
}).then((res) => {
|
|
12645
12719
|
if (res.ok) {
|
|
12646
12720
|
try {
|
|
12647
|
-
|
|
12721
|
+
let orphanPid = null;
|
|
12722
|
+
const ss = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
12648
12723
|
encoding: "utf8",
|
|
12649
12724
|
timeout: 1e3
|
|
12650
12725
|
});
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12726
|
+
if (!ss.error && ss.status === 0) {
|
|
12727
|
+
const m = ss.stdout?.match(/pid=(\d+)/);
|
|
12728
|
+
if (m) orphanPid = parseInt(m[1], 10);
|
|
12729
|
+
} else if (ss.error?.code === "ENOENT" || ss.status === null) {
|
|
12730
|
+
const lsof = spawnSync(
|
|
12731
|
+
"lsof",
|
|
12732
|
+
["-nP", `-iTCP:${DAEMON_PORT}`, "-sTCP:LISTEN", "-t"],
|
|
12733
|
+
{ encoding: "utf8", timeout: 1e3 }
|
|
12734
|
+
);
|
|
12735
|
+
if (!lsof.error && lsof.status === 0) {
|
|
12736
|
+
const first = (lsof.stdout ?? "").split("\n")[0].trim();
|
|
12737
|
+
if (/^\d+$/.test(first)) orphanPid = parseInt(first, 10);
|
|
12738
|
+
}
|
|
12739
|
+
}
|
|
12740
|
+
if (orphanPid !== null) {
|
|
12654
12741
|
process.kill(orphanPid, 0);
|
|
12655
12742
|
atomicWriteSync2(
|
|
12656
12743
|
DAEMON_PID_FILE,
|
|
@@ -12685,7 +12772,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
12685
12772
|
JSON.stringify({ pid: process.pid, port: DAEMON_PORT, internalToken, autoStarted }),
|
|
12686
12773
|
{ mode: 384 }
|
|
12687
12774
|
);
|
|
12688
|
-
console.error(chalk6.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE
|
|
12775
|
+
console.error(chalk6.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE on 127.0.0.1:${DAEMON_PORT}`));
|
|
12689
12776
|
});
|
|
12690
12777
|
if (watchMode) {
|
|
12691
12778
|
console.error(chalk6.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
@@ -12711,7 +12798,7 @@ var init_server = __esm({
|
|
|
12711
12798
|
import fs26 from "fs";
|
|
12712
12799
|
import path28 from "path";
|
|
12713
12800
|
import os24 from "os";
|
|
12714
|
-
import { spawnSync as
|
|
12801
|
+
import { spawnSync as spawnSync2, execFileSync } from "child_process";
|
|
12715
12802
|
function resolveNode9Binary() {
|
|
12716
12803
|
try {
|
|
12717
12804
|
const script = process.argv[1];
|
|
@@ -12722,7 +12809,7 @@ function resolveNode9Binary() {
|
|
|
12722
12809
|
}
|
|
12723
12810
|
try {
|
|
12724
12811
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
12725
|
-
const r =
|
|
12812
|
+
const r = spawnSync2(cmd, ["node9"], { encoding: "utf8", timeout: 3e3 });
|
|
12726
12813
|
if (r.status === 0 && r.stdout.trim()) {
|
|
12727
12814
|
return r.stdout.trim().split("\n")[0].trim();
|
|
12728
12815
|
}
|
|
@@ -12774,8 +12861,8 @@ function installLaunchd(binaryPath) {
|
|
|
12774
12861
|
const dir = path28.dirname(LAUNCHD_PLIST);
|
|
12775
12862
|
if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
|
|
12776
12863
|
fs26.writeFileSync(LAUNCHD_PLIST, launchdPlist(binaryPath), "utf-8");
|
|
12777
|
-
|
|
12778
|
-
const r =
|
|
12864
|
+
spawnSync2("launchctl", ["unload", LAUNCHD_PLIST], { encoding: "utf8" });
|
|
12865
|
+
const r = spawnSync2("launchctl", ["load", "-w", LAUNCHD_PLIST], {
|
|
12779
12866
|
encoding: "utf8",
|
|
12780
12867
|
timeout: 5e3
|
|
12781
12868
|
});
|
|
@@ -12785,7 +12872,7 @@ function installLaunchd(binaryPath) {
|
|
|
12785
12872
|
}
|
|
12786
12873
|
function uninstallLaunchd() {
|
|
12787
12874
|
if (fs26.existsSync(LAUNCHD_PLIST)) {
|
|
12788
|
-
|
|
12875
|
+
spawnSync2("launchctl", ["unload", "-w", LAUNCHD_PLIST], { encoding: "utf8", timeout: 5e3 });
|
|
12789
12876
|
fs26.unlinkSync(LAUNCHD_PLIST);
|
|
12790
12877
|
}
|
|
12791
12878
|
}
|
|
@@ -12817,15 +12904,15 @@ function installSystemd(binaryPath) {
|
|
|
12817
12904
|
execFileSync("loginctl", ["enable-linger", os24.userInfo().username], { timeout: 3e3 });
|
|
12818
12905
|
} catch {
|
|
12819
12906
|
}
|
|
12820
|
-
const reload =
|
|
12907
|
+
const reload = spawnSync2("systemctl", ["--user", "daemon-reload"], {
|
|
12821
12908
|
encoding: "utf8",
|
|
12822
12909
|
timeout: 5e3
|
|
12823
12910
|
});
|
|
12824
12911
|
if (reload.status !== 0) {
|
|
12825
12912
|
throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
|
|
12826
12913
|
}
|
|
12827
|
-
|
|
12828
|
-
const enable =
|
|
12914
|
+
spawnSync2("systemctl", ["--user", "stop", "node9-daemon"], { encoding: "utf8", timeout: 3e3 });
|
|
12915
|
+
const enable = spawnSync2("systemctl", ["--user", "enable", "--now", "node9-daemon"], {
|
|
12829
12916
|
encoding: "utf8",
|
|
12830
12917
|
timeout: 5e3
|
|
12831
12918
|
});
|
|
@@ -12835,11 +12922,11 @@ function installSystemd(binaryPath) {
|
|
|
12835
12922
|
}
|
|
12836
12923
|
function uninstallSystemd() {
|
|
12837
12924
|
if (fs26.existsSync(SYSTEMD_UNIT)) {
|
|
12838
|
-
|
|
12925
|
+
spawnSync2("systemctl", ["--user", "disable", "--now", "node9-daemon"], {
|
|
12839
12926
|
encoding: "utf8",
|
|
12840
12927
|
timeout: 5e3
|
|
12841
12928
|
});
|
|
12842
|
-
|
|
12929
|
+
spawnSync2("systemctl", ["--user", "daemon-reload"], { encoding: "utf8", timeout: 5e3 });
|
|
12843
12930
|
fs26.unlinkSync(SYSTEMD_UNIT);
|
|
12844
12931
|
}
|
|
12845
12932
|
}
|
|
@@ -12857,7 +12944,7 @@ function stopRunningDaemon() {
|
|
|
12857
12944
|
try {
|
|
12858
12945
|
process.kill(pid, "SIGTERM");
|
|
12859
12946
|
const deadline = Date.now() + 3e3;
|
|
12860
|
-
const pollStop =
|
|
12947
|
+
const pollStop = spawnSync2(
|
|
12861
12948
|
"sh",
|
|
12862
12949
|
["-c", `while kill -0 ${pid} 2>/dev/null; do sleep 0.1; done`],
|
|
12863
12950
|
{
|
|
@@ -12889,7 +12976,7 @@ function installDaemonService() {
|
|
|
12889
12976
|
return { ok: true, platform: "launchd", alreadyInstalled };
|
|
12890
12977
|
}
|
|
12891
12978
|
if (process.platform === "linux") {
|
|
12892
|
-
const check =
|
|
12979
|
+
const check = spawnSync2("systemctl", ["--user", "--version"], {
|
|
12893
12980
|
encoding: "utf8",
|
|
12894
12981
|
timeout: 2e3
|
|
12895
12982
|
});
|
|
@@ -12954,7 +13041,6 @@ var init_service = __esm({
|
|
|
12954
13041
|
// src/daemon/index.ts
|
|
12955
13042
|
import fs27 from "fs";
|
|
12956
13043
|
import chalk7 from "chalk";
|
|
12957
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
12958
13044
|
function stopDaemon() {
|
|
12959
13045
|
if (!fs27.existsSync(DAEMON_PID_FILE)) return console.log(chalk7.yellow("Not running."));
|
|
12960
13046
|
try {
|
|
@@ -12996,15 +13082,7 @@ function daemonStatus() {
|
|
|
12996
13082
|
processStatus = chalk7.yellow("not running (stale PID file)");
|
|
12997
13083
|
}
|
|
12998
13084
|
} else {
|
|
12999
|
-
|
|
13000
|
-
encoding: "utf8",
|
|
13001
|
-
timeout: 500
|
|
13002
|
-
});
|
|
13003
|
-
if (r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`)) {
|
|
13004
|
-
processStatus = chalk7.yellow(`running (orphaned \u2014 no PID file)`);
|
|
13005
|
-
} else {
|
|
13006
|
-
processStatus = chalk7.yellow("not running");
|
|
13007
|
-
}
|
|
13085
|
+
processStatus = chalk7.yellow("not running");
|
|
13008
13086
|
}
|
|
13009
13087
|
console.log(`
|
|
13010
13088
|
Process : ${processStatus}`);
|
|
@@ -14473,13 +14551,7 @@ async function autoStartDaemonAndWait() {
|
|
|
14473
14551
|
for (let i = 0; i < 20; i++) {
|
|
14474
14552
|
await new Promise((r) => setTimeout(r, 250));
|
|
14475
14553
|
if (!isDaemonRunning()) continue;
|
|
14476
|
-
|
|
14477
|
-
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/settings`, {
|
|
14478
|
-
signal: AbortSignal.timeout(500)
|
|
14479
|
-
});
|
|
14480
|
-
if (res.ok) return true;
|
|
14481
|
-
} catch {
|
|
14482
|
-
}
|
|
14554
|
+
if (await isDaemonReachable()) return true;
|
|
14483
14555
|
}
|
|
14484
14556
|
} catch {
|
|
14485
14557
|
}
|
|
@@ -14498,7 +14570,7 @@ import path32 from "path";
|
|
|
14498
14570
|
import os27 from "os";
|
|
14499
14571
|
|
|
14500
14572
|
// src/undo.ts
|
|
14501
|
-
import { spawnSync as
|
|
14573
|
+
import { spawnSync as spawnSync3, spawn as spawn4 } from "child_process";
|
|
14502
14574
|
import crypto3 from "crypto";
|
|
14503
14575
|
import fs29 from "fs";
|
|
14504
14576
|
import net3 from "net";
|
|
@@ -14611,7 +14683,7 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
14611
14683
|
cleanOrphanedIndexFiles(shadowDir);
|
|
14612
14684
|
const normalizedCwd = normalizeCwdForHash(cwd);
|
|
14613
14685
|
const shadowEnvBase = { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
14614
|
-
const check =
|
|
14686
|
+
const check = spawnSync3("git", ["rev-parse", "--git-dir"], {
|
|
14615
14687
|
env: shadowEnvBase,
|
|
14616
14688
|
timeout: 3e3
|
|
14617
14689
|
});
|
|
@@ -14637,17 +14709,17 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
14637
14709
|
fs29.mkdirSync(shadowDir, { recursive: true });
|
|
14638
14710
|
} catch {
|
|
14639
14711
|
}
|
|
14640
|
-
const init =
|
|
14712
|
+
const init = spawnSync3("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
14641
14713
|
if (init.status !== 0 || init.error) {
|
|
14642
14714
|
const reason = init.error ? init.error.message : init.stderr?.toString();
|
|
14643
14715
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9] git init --bare failed:", reason);
|
|
14644
14716
|
return false;
|
|
14645
14717
|
}
|
|
14646
14718
|
const configFile = path30.join(shadowDir, "config");
|
|
14647
|
-
|
|
14719
|
+
spawnSync3("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
14648
14720
|
timeout: 3e3
|
|
14649
14721
|
});
|
|
14650
|
-
|
|
14722
|
+
spawnSync3("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
|
|
14651
14723
|
timeout: 3e3
|
|
14652
14724
|
});
|
|
14653
14725
|
try {
|
|
@@ -14658,14 +14730,17 @@ function ensureShadowRepo(shadowDir, cwd) {
|
|
|
14658
14730
|
}
|
|
14659
14731
|
function buildGitEnv(cwd) {
|
|
14660
14732
|
const shadowDir = getShadowRepoDir(cwd);
|
|
14661
|
-
const check =
|
|
14733
|
+
const check = spawnSync3("git", ["rev-parse", "--git-dir"], {
|
|
14662
14734
|
env: { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd },
|
|
14663
14735
|
timeout: 2e3
|
|
14664
14736
|
});
|
|
14665
14737
|
if (check.status === 0) {
|
|
14666
14738
|
return { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
14667
14739
|
}
|
|
14668
|
-
|
|
14740
|
+
const env = { ...process.env };
|
|
14741
|
+
delete env.GIT_DIR;
|
|
14742
|
+
delete env.GIT_WORK_TREE;
|
|
14743
|
+
return env;
|
|
14669
14744
|
}
|
|
14670
14745
|
async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = []) {
|
|
14671
14746
|
let indexFile = null;
|
|
@@ -14683,11 +14758,11 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
14683
14758
|
GIT_WORK_TREE: cwd,
|
|
14684
14759
|
GIT_INDEX_FILE: indexFile
|
|
14685
14760
|
};
|
|
14686
|
-
|
|
14687
|
-
const treeRes =
|
|
14761
|
+
spawnSync3("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
14762
|
+
const treeRes = spawnSync3("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
14688
14763
|
const treeHash = treeRes.stdout?.toString().trim();
|
|
14689
14764
|
if (!treeHash || treeRes.status !== 0) return null;
|
|
14690
|
-
const commitRes =
|
|
14765
|
+
const commitRes = spawnSync3(
|
|
14691
14766
|
"git",
|
|
14692
14767
|
["commit-tree", treeHash, "-m", `Node9 AI Snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`],
|
|
14693
14768
|
{ env: shadowEnv, timeout: GIT_TIMEOUT }
|
|
@@ -14699,7 +14774,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
14699
14774
|
let capturedFiles = [];
|
|
14700
14775
|
let capturedDiff = null;
|
|
14701
14776
|
if (prevEntry) {
|
|
14702
|
-
const filesRes =
|
|
14777
|
+
const filesRes = spawnSync3("git", ["diff", "--name-only", prevEntry.hash, commitHash], {
|
|
14703
14778
|
env: shadowEnv,
|
|
14704
14779
|
timeout: GIT_TIMEOUT
|
|
14705
14780
|
});
|
|
@@ -14709,7 +14784,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
14709
14784
|
if (capturedFiles.length === 0) {
|
|
14710
14785
|
return prevEntry.hash;
|
|
14711
14786
|
}
|
|
14712
|
-
const diffRes =
|
|
14787
|
+
const diffRes = spawnSync3("git", ["diff", prevEntry.hash, commitHash], {
|
|
14713
14788
|
env: shadowEnv,
|
|
14714
14789
|
timeout: GIT_TIMEOUT
|
|
14715
14790
|
});
|
|
@@ -14717,7 +14792,7 @@ async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = [
|
|
|
14717
14792
|
capturedDiff = diffRes.stdout?.toString() || null;
|
|
14718
14793
|
}
|
|
14719
14794
|
} else {
|
|
14720
|
-
const filesRes =
|
|
14795
|
+
const filesRes = spawnSync3("git", ["ls-tree", "-r", "--name-only", commitHash], {
|
|
14721
14796
|
env: shadowEnv,
|
|
14722
14797
|
timeout: GIT_TIMEOUT
|
|
14723
14798
|
});
|
|
@@ -14771,14 +14846,14 @@ function getSnapshotHistory() {
|
|
|
14771
14846
|
function computeUndoDiff(hash, cwd) {
|
|
14772
14847
|
try {
|
|
14773
14848
|
const env = buildGitEnv(cwd);
|
|
14774
|
-
const statRes =
|
|
14849
|
+
const statRes = spawnSync3("git", ["diff", hash, "--stat", "--", "."], {
|
|
14775
14850
|
cwd,
|
|
14776
14851
|
env,
|
|
14777
14852
|
timeout: GIT_TIMEOUT
|
|
14778
14853
|
});
|
|
14779
14854
|
const stat = statRes.stdout?.toString().trim();
|
|
14780
14855
|
if (!stat || statRes.status !== 0) return null;
|
|
14781
|
-
const diffRes =
|
|
14856
|
+
const diffRes = spawnSync3("git", ["diff", hash, "--", "."], {
|
|
14782
14857
|
cwd,
|
|
14783
14858
|
env,
|
|
14784
14859
|
timeout: GIT_TIMEOUT
|
|
@@ -14797,7 +14872,7 @@ function applyUndo(hash, cwd) {
|
|
|
14797
14872
|
try {
|
|
14798
14873
|
const dir = cwd ?? process.cwd();
|
|
14799
14874
|
const env = buildGitEnv(dir);
|
|
14800
|
-
const restore =
|
|
14875
|
+
const restore = spawnSync3("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
|
|
14801
14876
|
cwd: dir,
|
|
14802
14877
|
env,
|
|
14803
14878
|
timeout: GIT_TIMEOUT
|
|
@@ -14809,7 +14884,7 @@ function applyUndo(hash, cwd) {
|
|
|
14809
14884
|
}
|
|
14810
14885
|
return false;
|
|
14811
14886
|
}
|
|
14812
|
-
const lsTree =
|
|
14887
|
+
const lsTree = spawnSync3("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
14813
14888
|
cwd: dir,
|
|
14814
14889
|
env,
|
|
14815
14890
|
timeout: GIT_TIMEOUT
|
|
@@ -14828,8 +14903,8 @@ function applyUndo(hash, cwd) {
|
|
|
14828
14903
|
`);
|
|
14829
14904
|
return false;
|
|
14830
14905
|
}
|
|
14831
|
-
const tracked =
|
|
14832
|
-
const untracked =
|
|
14906
|
+
const tracked = spawnSync3("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
14907
|
+
const untracked = spawnSync3("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
14833
14908
|
cwd: dir,
|
|
14834
14909
|
env,
|
|
14835
14910
|
timeout: GIT_TIMEOUT
|
|
@@ -16078,10 +16153,12 @@ function registerDoctorCommand(program2, version2) {
|
|
|
16078
16153
|
}
|
|
16079
16154
|
section("Daemon (optional)");
|
|
16080
16155
|
if (isDaemonRunning()) {
|
|
16081
|
-
pass(
|
|
16156
|
+
pass(
|
|
16157
|
+
`Daemon running on ${DAEMON_HOST}:${DAEMON_PORT} \u2014 terminal & native approvals enabled`
|
|
16158
|
+
);
|
|
16082
16159
|
} else {
|
|
16083
16160
|
warn(
|
|
16084
|
-
"Daemon not running \u2014
|
|
16161
|
+
"Daemon not running \u2014 terminal & native approvals unavailable",
|
|
16085
16162
|
"Run: node9 daemon --background"
|
|
16086
16163
|
);
|
|
16087
16164
|
}
|
|
@@ -17681,7 +17758,7 @@ function registerUndoCommand(program2) {
|
|
|
17681
17758
|
// src/cli/commands/watch.ts
|
|
17682
17759
|
init_daemon();
|
|
17683
17760
|
import chalk19 from "chalk";
|
|
17684
|
-
import { spawn as spawn7, spawnSync as
|
|
17761
|
+
import { spawn as spawn7, spawnSync as spawnSync4 } from "child_process";
|
|
17685
17762
|
function registerWatchCommand(program2) {
|
|
17686
17763
|
program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
|
|
17687
17764
|
let port = DAEMON_PORT;
|
|
@@ -17727,7 +17804,7 @@ function registerWatchCommand(program2) {
|
|
|
17727
17804
|
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
17728
17805
|
)
|
|
17729
17806
|
);
|
|
17730
|
-
const result =
|
|
17807
|
+
const result = spawnSync4(cmd, args, {
|
|
17731
17808
|
stdio: "inherit",
|
|
17732
17809
|
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
17733
17810
|
});
|
|
@@ -18274,7 +18351,7 @@ import readline5 from "readline";
|
|
|
18274
18351
|
import fs39 from "fs";
|
|
18275
18352
|
import os35 from "os";
|
|
18276
18353
|
import path41 from "path";
|
|
18277
|
-
import { spawnSync as
|
|
18354
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
18278
18355
|
init_core();
|
|
18279
18356
|
init_daemon();
|
|
18280
18357
|
init_shields();
|
|
@@ -18748,7 +18825,7 @@ function handleRuleAdd(args) {
|
|
|
18748
18825
|
return `Rule "${name}" added to ~/.node9/config.json \u2014 verdict: ${verdict} when ${field} matches "${pattern}"`;
|
|
18749
18826
|
}
|
|
18750
18827
|
function runCliCommand(subArgs) {
|
|
18751
|
-
const result =
|
|
18828
|
+
const result = spawnSync5(process.execPath, [process.argv[1], ...subArgs], {
|
|
18752
18829
|
encoding: "utf-8",
|
|
18753
18830
|
timeout: 6e4,
|
|
18754
18831
|
// Disable colors — stdout is piped (not a TTY), chalk auto-detects, but be explicit
|
|
@@ -20758,6 +20835,17 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
20758
20835
|
process.exit(1);
|
|
20759
20836
|
}
|
|
20760
20837
|
});
|
|
20838
|
+
program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
|
|
20839
|
+
try {
|
|
20840
|
+
const dashboardPath = path49.join(__dirname, "dashboard.mjs");
|
|
20841
|
+
const dynamicImport = new Function("id", "return import(id)");
|
|
20842
|
+
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
20843
|
+
await mod.startMonitor();
|
|
20844
|
+
} catch (err2) {
|
|
20845
|
+
console.error(chalk31.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
20846
|
+
process.exit(1);
|
|
20847
|
+
}
|
|
20848
|
+
});
|
|
20761
20849
|
registerWatchCommand(program);
|
|
20762
20850
|
registerMcpGatewayCommand(program);
|
|
20763
20851
|
registerMcpServerCommand(program);
|