@node9/proxy 1.19.3 → 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 +98 -9
- package/dist/cli.mjs +98 -9
- package/dist/dashboard.mjs +4651 -0
- package/dist/index.js +15 -3
- package/dist/index.mjs +15 -3
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -1708,7 +1708,11 @@ function extractCanonicalFindings(call, ctx) {
|
|
|
1708
1708
|
})
|
|
1709
1709
|
);
|
|
1710
1710
|
}
|
|
1711
|
-
|
|
1711
|
+
const ast = analyzeShellCommand(command);
|
|
1712
|
+
const sudoVariant = ast.actions.includes("sudo") || ast.actions.includes("su");
|
|
1713
|
+
const chmodVariant = ast.actions.includes("chmod") && (ast.allTokens.includes("777") || ast.allTokens.includes("0777") || ast.allTokens.includes("+x"));
|
|
1714
|
+
const chownVariant = ast.actions.includes("chown") && ast.allTokens.includes("root");
|
|
1715
|
+
if (sudoVariant || chmodVariant || chownVariant) {
|
|
1712
1716
|
out.push(
|
|
1713
1717
|
makeFinding({
|
|
1714
1718
|
type: "privilege-escalation",
|
|
@@ -1840,7 +1844,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
1840
1844
|
}
|
|
1841
1845
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
1842
1846
|
}
|
|
1843
|
-
var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, 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,
|
|
1847
|
+
var import_safe_regex2, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto2, 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;
|
|
1844
1848
|
var init_dist = __esm({
|
|
1845
1849
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1846
1850
|
"use strict";
|
|
@@ -3246,7 +3250,6 @@ var init_dist = __esm({
|
|
|
3246
3250
|
LOOP_THRESHOLD_FOR_WASTE = 3;
|
|
3247
3251
|
COST_PER_LOOP_ITER_USD = 6e-3;
|
|
3248
3252
|
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;
|
|
3249
|
-
PRIVILEGE_ESCALATION_RE = /\b(sudo|su)\b\s+[a-z]|\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
|
|
3250
3253
|
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;
|
|
3251
3254
|
FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
3252
3255
|
"read",
|
|
@@ -3266,7 +3269,7 @@ var init_dist = __esm({
|
|
|
3266
3269
|
PII_PHONE_RE = /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b/;
|
|
3267
3270
|
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/;
|
|
3268
3271
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
3269
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
3272
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
3270
3273
|
DEDUPE_PREVIEW_LEN = 120;
|
|
3271
3274
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
3272
3275
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -4807,7 +4810,7 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
4807
4810
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
4808
4811
|
}
|
|
4809
4812
|
}
|
|
4810
|
-
async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
4813
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
|
|
4811
4814
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
4812
4815
|
const res = await fetch(`${base}/check`, {
|
|
4813
4816
|
method: "POST",
|
|
@@ -4818,7 +4821,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
4818
4821
|
slackDelegated: true,
|
|
4819
4822
|
agent: meta?.agent,
|
|
4820
4823
|
mcpServer: meta?.mcpServer,
|
|
4821
|
-
...riskMetadata && { riskMetadata }
|
|
4824
|
+
...riskMetadata && { riskMetadata },
|
|
4825
|
+
// fromCLI=true tells the daemon the CLI already sent the activity
|
|
4826
|
+
// event via socket. Same contract as registerDaemonEntry — without
|
|
4827
|
+
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
4828
|
+
fromCLI: socketActivitySent !== false,
|
|
4829
|
+
activityId
|
|
4822
4830
|
}),
|
|
4823
4831
|
signal: AbortSignal.timeout(3e3)
|
|
4824
4832
|
});
|
|
@@ -5963,7 +5971,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5963
5971
|
let daemonAllowCount = 1;
|
|
5964
5972
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
5965
5973
|
if (cloudEnforced && cloudRequestId) {
|
|
5966
|
-
const viewer = await notifyDaemonViewer(
|
|
5974
|
+
const viewer = await notifyDaemonViewer(
|
|
5975
|
+
toolName,
|
|
5976
|
+
args,
|
|
5977
|
+
meta,
|
|
5978
|
+
riskMetadata,
|
|
5979
|
+
options?.activityId,
|
|
5980
|
+
options?.socketActivitySent
|
|
5981
|
+
).catch(() => null);
|
|
5967
5982
|
viewerId = viewer?.id ?? null;
|
|
5968
5983
|
daemonEntryId = viewerId;
|
|
5969
5984
|
if (viewer) daemonAllowCount = viewer.allowCount;
|
|
@@ -8056,6 +8071,7 @@ __export(scan_watermark_exports, {
|
|
|
8056
8071
|
markUploadComplete: () => markUploadComplete,
|
|
8057
8072
|
saveWatermark: () => saveWatermark,
|
|
8058
8073
|
scanDelta: () => scanDelta,
|
|
8074
|
+
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
8059
8075
|
tickScanWatcher: () => tickScanWatcher
|
|
8060
8076
|
});
|
|
8061
8077
|
function freshWatermark() {
|
|
@@ -8259,6 +8275,25 @@ function extractFindingsFromLine(line, sessionId, lineIndex) {
|
|
|
8259
8275
|
}
|
|
8260
8276
|
return findings;
|
|
8261
8277
|
}
|
|
8278
|
+
async function tickForensicBroadcast(offsets) {
|
|
8279
|
+
const out = [];
|
|
8280
|
+
const files = listJsonlFiles();
|
|
8281
|
+
for (const file of files) {
|
|
8282
|
+
const size = fileSize(file);
|
|
8283
|
+
const offset = offsets.get(file);
|
|
8284
|
+
if (offset === void 0) {
|
|
8285
|
+
offsets.set(file, size);
|
|
8286
|
+
continue;
|
|
8287
|
+
}
|
|
8288
|
+
if (size <= offset) continue;
|
|
8289
|
+
const sessionId = import_path19.default.basename(file, ".jsonl");
|
|
8290
|
+
const newOffset = await scanDelta(file, offset, (obj, lineIndex) => {
|
|
8291
|
+
out.push(...extractFindingsFromLine(obj, sessionId, lineIndex));
|
|
8292
|
+
});
|
|
8293
|
+
offsets.set(file, newOffset);
|
|
8294
|
+
}
|
|
8295
|
+
return out;
|
|
8296
|
+
}
|
|
8262
8297
|
function markUploadComplete() {
|
|
8263
8298
|
const state = loadWatermark();
|
|
8264
8299
|
if (state.status === "schema-future") return;
|
|
@@ -11027,6 +11062,19 @@ data: ${JSON.stringify(data)}
|
|
|
11027
11062
|
}
|
|
11028
11063
|
});
|
|
11029
11064
|
}
|
|
11065
|
+
function broadcastForensic(finding) {
|
|
11066
|
+
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
11067
|
+
const event = {
|
|
11068
|
+
type: "forensic",
|
|
11069
|
+
id: `fnd_${(0, import_crypto6.randomUUID)()}`,
|
|
11070
|
+
ts: Date.now(),
|
|
11071
|
+
sessionId: finding.sessionId,
|
|
11072
|
+
category: finding.type,
|
|
11073
|
+
severity
|
|
11074
|
+
};
|
|
11075
|
+
if (finding.patternName !== void 0) event.patternName = finding.patternName;
|
|
11076
|
+
broadcast("forensic", event);
|
|
11077
|
+
}
|
|
11030
11078
|
function abandonPending() {
|
|
11031
11079
|
setAbandonTimer(null);
|
|
11032
11080
|
pending.forEach((entry, id) => {
|
|
@@ -11210,7 +11258,7 @@ function bindActivitySocket() {
|
|
|
11210
11258
|
});
|
|
11211
11259
|
activitySocketServer = unixServer;
|
|
11212
11260
|
}
|
|
11213
|
-
var import_net2, import_fs21, import_path23, import_os19, import_crypto6, 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;
|
|
11261
|
+
var import_net2, import_fs21, import_path23, import_os19, import_crypto6, 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;
|
|
11214
11262
|
var init_state2 = __esm({
|
|
11215
11263
|
"src/daemon/state.ts"() {
|
|
11216
11264
|
"use strict";
|
|
@@ -11260,6 +11308,11 @@ var init_state2 = __esm({
|
|
|
11260
11308
|
INPUT_PRICE_PER_1M = 3;
|
|
11261
11309
|
OUTPUT_PRICE_PER_1M = 15;
|
|
11262
11310
|
BYTES_PER_TOKEN = 4;
|
|
11311
|
+
CRITICAL_FORENSIC_CATEGORIES = /* @__PURE__ */ new Set([
|
|
11312
|
+
"privilege-escalation",
|
|
11313
|
+
"destructive-op",
|
|
11314
|
+
"eval-of-remote"
|
|
11315
|
+
]);
|
|
11263
11316
|
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
11264
11317
|
"write",
|
|
11265
11318
|
"write_file",
|
|
@@ -11600,7 +11653,26 @@ function startCloudSync() {
|
|
|
11600
11653
|
const recurring = setInterval(() => void syncOnce(), intervalMs);
|
|
11601
11654
|
recurring.unref();
|
|
11602
11655
|
}
|
|
11603
|
-
|
|
11656
|
+
function startForensicBroadcast() {
|
|
11657
|
+
const tick = async () => {
|
|
11658
|
+
try {
|
|
11659
|
+
const findings = await tickForensicBroadcast(forensicBroadcastOffsets);
|
|
11660
|
+
for (const f of findings) broadcastForensic(f);
|
|
11661
|
+
} catch (err2) {
|
|
11662
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
11663
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
11664
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11665
|
+
kind: "forensic-broadcast-error",
|
|
11666
|
+
error: msg
|
|
11667
|
+
});
|
|
11668
|
+
}
|
|
11669
|
+
};
|
|
11670
|
+
const initial = setTimeout(() => void tick(), FORENSIC_INITIAL_DELAY_MS);
|
|
11671
|
+
initial.unref();
|
|
11672
|
+
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
11673
|
+
recurring.unref();
|
|
11674
|
+
}
|
|
11675
|
+
var import_fs22, import_https2, import_os20, import_path24, FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_HOURS, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
11604
11676
|
var init_sync = __esm({
|
|
11605
11677
|
"src/daemon/sync.ts"() {
|
|
11606
11678
|
"use strict";
|
|
@@ -11612,6 +11684,8 @@ var init_sync = __esm({
|
|
|
11612
11684
|
init_blast();
|
|
11613
11685
|
init_dist();
|
|
11614
11686
|
init_scan_watermark();
|
|
11687
|
+
init_state2();
|
|
11688
|
+
init_audit();
|
|
11615
11689
|
FINDING_TO_SIGNAL3 = {
|
|
11616
11690
|
dlp: "dlpFindings",
|
|
11617
11691
|
pii: "piiFindings",
|
|
@@ -11628,6 +11702,9 @@ var init_sync = __esm({
|
|
|
11628
11702
|
DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
11629
11703
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
11630
11704
|
MIN_INTERVAL_HOURS = 1;
|
|
11705
|
+
FORENSIC_BROADCAST_INTERVAL_MS = 3e4;
|
|
11706
|
+
FORENSIC_INITIAL_DELAY_MS = 5e3;
|
|
11707
|
+
forensicBroadcastOffsets = /* @__PURE__ */ new Map();
|
|
11631
11708
|
}
|
|
11632
11709
|
});
|
|
11633
11710
|
|
|
@@ -11855,6 +11932,7 @@ var init_mcp_tools = __esm({
|
|
|
11855
11932
|
function startDaemon() {
|
|
11856
11933
|
startCostSync();
|
|
11857
11934
|
startCloudSync();
|
|
11935
|
+
startForensicBroadcast();
|
|
11858
11936
|
startDlpScanner();
|
|
11859
11937
|
loadInsightCounts();
|
|
11860
11938
|
const internalToken = (0, import_crypto7.randomUUID)();
|
|
@@ -20785,6 +20863,17 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
20785
20863
|
process.exit(1);
|
|
20786
20864
|
}
|
|
20787
20865
|
});
|
|
20866
|
+
program.command("monitor").description("Live interactive dashboard \u2014 activity feed, approvals, security signals").action(async () => {
|
|
20867
|
+
try {
|
|
20868
|
+
const dashboardPath = import_path49.default.join(__dirname, "dashboard.mjs");
|
|
20869
|
+
const dynamicImport = new Function("id", "return import(id)");
|
|
20870
|
+
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
20871
|
+
await mod.startMonitor();
|
|
20872
|
+
} catch (err2) {
|
|
20873
|
+
console.error(import_chalk31.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
20874
|
+
process.exit(1);
|
|
20875
|
+
}
|
|
20876
|
+
});
|
|
20788
20877
|
registerWatchCommand(program);
|
|
20789
20878
|
registerMcpGatewayCommand(program);
|
|
20790
20879
|
registerMcpServerCommand(program);
|
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;
|
|
@@ -4788,7 +4791,7 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
4788
4791
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
4789
4792
|
}
|
|
4790
4793
|
}
|
|
4791
|
-
async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
4794
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
|
|
4792
4795
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
4793
4796
|
const res = await fetch(`${base}/check`, {
|
|
4794
4797
|
method: "POST",
|
|
@@ -4799,7 +4802,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
4799
4802
|
slackDelegated: true,
|
|
4800
4803
|
agent: meta?.agent,
|
|
4801
4804
|
mcpServer: meta?.mcpServer,
|
|
4802
|
-
...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
|
|
4803
4811
|
}),
|
|
4804
4812
|
signal: AbortSignal.timeout(3e3)
|
|
4805
4813
|
});
|
|
@@ -5940,7 +5948,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
5940
5948
|
let daemonAllowCount = 1;
|
|
5941
5949
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
5942
5950
|
if (cloudEnforced && cloudRequestId) {
|
|
5943
|
-
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);
|
|
5944
5959
|
viewerId = viewer?.id ?? null;
|
|
5945
5960
|
daemonEntryId = viewerId;
|
|
5946
5961
|
if (viewer) daemonAllowCount = viewer.allowCount;
|
|
@@ -8030,6 +8045,7 @@ __export(scan_watermark_exports, {
|
|
|
8030
8045
|
markUploadComplete: () => markUploadComplete,
|
|
8031
8046
|
saveWatermark: () => saveWatermark,
|
|
8032
8047
|
scanDelta: () => scanDelta,
|
|
8048
|
+
tickForensicBroadcast: () => tickForensicBroadcast,
|
|
8033
8049
|
tickScanWatcher: () => tickScanWatcher
|
|
8034
8050
|
});
|
|
8035
8051
|
import fs17 from "fs";
|
|
@@ -8237,6 +8253,25 @@ function extractFindingsFromLine(line, sessionId, lineIndex) {
|
|
|
8237
8253
|
}
|
|
8238
8254
|
return findings;
|
|
8239
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
|
+
}
|
|
8240
8275
|
function markUploadComplete() {
|
|
8241
8276
|
const state = loadWatermark();
|
|
8242
8277
|
if (state.status === "schema-future") return;
|
|
@@ -11006,6 +11041,19 @@ data: ${JSON.stringify(data)}
|
|
|
11006
11041
|
}
|
|
11007
11042
|
});
|
|
11008
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
|
+
}
|
|
11009
11057
|
function abandonPending() {
|
|
11010
11058
|
setAbandonTimer(null);
|
|
11011
11059
|
pending.forEach((entry, id) => {
|
|
@@ -11189,7 +11237,7 @@ function bindActivitySocket() {
|
|
|
11189
11237
|
});
|
|
11190
11238
|
activitySocketServer = unixServer;
|
|
11191
11239
|
}
|
|
11192
|
-
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;
|
|
11193
11241
|
var init_state2 = __esm({
|
|
11194
11242
|
"src/daemon/state.ts"() {
|
|
11195
11243
|
"use strict";
|
|
@@ -11234,6 +11282,11 @@ var init_state2 = __esm({
|
|
|
11234
11282
|
INPUT_PRICE_PER_1M = 3;
|
|
11235
11283
|
OUTPUT_PRICE_PER_1M = 15;
|
|
11236
11284
|
BYTES_PER_TOKEN = 4;
|
|
11285
|
+
CRITICAL_FORENSIC_CATEGORIES = /* @__PURE__ */ new Set([
|
|
11286
|
+
"privilege-escalation",
|
|
11287
|
+
"destructive-op",
|
|
11288
|
+
"eval-of-remote"
|
|
11289
|
+
]);
|
|
11237
11290
|
WRITE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
11238
11291
|
"write",
|
|
11239
11292
|
"write_file",
|
|
@@ -11578,7 +11631,26 @@ function startCloudSync() {
|
|
|
11578
11631
|
const recurring = setInterval(() => void syncOnce(), intervalMs);
|
|
11579
11632
|
recurring.unref();
|
|
11580
11633
|
}
|
|
11581
|
-
|
|
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;
|
|
11582
11654
|
var init_sync = __esm({
|
|
11583
11655
|
"src/daemon/sync.ts"() {
|
|
11584
11656
|
"use strict";
|
|
@@ -11586,6 +11658,8 @@ var init_sync = __esm({
|
|
|
11586
11658
|
init_blast();
|
|
11587
11659
|
init_dist();
|
|
11588
11660
|
init_scan_watermark();
|
|
11661
|
+
init_state2();
|
|
11662
|
+
init_audit();
|
|
11589
11663
|
FINDING_TO_SIGNAL3 = {
|
|
11590
11664
|
dlp: "dlpFindings",
|
|
11591
11665
|
pii: "piiFindings",
|
|
@@ -11602,6 +11676,9 @@ var init_sync = __esm({
|
|
|
11602
11676
|
DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
11603
11677
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
11604
11678
|
MIN_INTERVAL_HOURS = 1;
|
|
11679
|
+
FORENSIC_BROADCAST_INTERVAL_MS = 3e4;
|
|
11680
|
+
FORENSIC_INITIAL_DELAY_MS = 5e3;
|
|
11681
|
+
forensicBroadcastOffsets = /* @__PURE__ */ new Map();
|
|
11605
11682
|
}
|
|
11606
11683
|
});
|
|
11607
11684
|
|
|
@@ -11835,6 +11912,7 @@ import chalk6 from "chalk";
|
|
|
11835
11912
|
function startDaemon() {
|
|
11836
11913
|
startCostSync();
|
|
11837
11914
|
startCloudSync();
|
|
11915
|
+
startForensicBroadcast();
|
|
11838
11916
|
startDlpScanner();
|
|
11839
11917
|
loadInsightCounts();
|
|
11840
11918
|
const internalToken = randomUUID4();
|
|
@@ -20757,6 +20835,17 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
20757
20835
|
process.exit(1);
|
|
20758
20836
|
}
|
|
20759
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
|
+
});
|
|
20760
20849
|
registerWatchCommand(program);
|
|
20761
20850
|
registerMcpGatewayCommand(program);
|
|
20762
20851
|
registerMcpServerCommand(program);
|