@node9/proxy 1.62.1 → 1.64.0
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 +926 -340
- package/dist/cli.mjs +924 -338
- package/dist/dashboard.mjs +82 -25
- package/dist/index.js +144 -39
- package/dist/index.mjs +144 -39
- package/dist/scan-ink.mjs +6 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -343,6 +343,7 @@ var init_config_schema = __esm({
|
|
|
343
343
|
// nudge-only (default), and the scan cadence in minutes.
|
|
344
344
|
mcpAutoWrap: import_zod.z.boolean().optional(),
|
|
345
345
|
mcpReconcileIntervalMinutes: import_zod.z.number().positive().optional(),
|
|
346
|
+
mcpStaleAfterDays: import_zod.z.number().min(0).optional(),
|
|
346
347
|
cloudSyncIntervalHours: import_zod.z.number().positive().optional(),
|
|
347
348
|
// Seconds-granular override for the cloud policy sync cadence. Wins over
|
|
348
349
|
// cloudSyncIntervalHours when set. Lets you opt into fast apply (e.g. 20)
|
|
@@ -3977,6 +3978,7 @@ var init_dist = __esm({
|
|
|
3977
3978
|
name: "redis",
|
|
3978
3979
|
description: "Protects Redis instances from destructive AI operations",
|
|
3979
3980
|
aliases: [],
|
|
3981
|
+
_scopeNote: "Every rule requires Redis to actually be involved: a client in the command (redis-cli / valkey-cli, in either order \u2014 `redis-cli FLUSHALL` and `echo FLUSHALL | redis-cli` are both real), OR the command field holding the bare keyword, which is what the MCP redis_command tool sends ({ command: 'FLUSHALL', args: [] }). Without the client requirement these rules matched the WORD anywhere \u2014 including prose in a heredoc writing a file \u2014 which produced two observed false positives: a test fixture describing the shield, and a commit message naming the rule. Every other builtin shield already scopes this way (aws s3, docker system prune, kubectl delete, gh repo delete). See redis-scope.spec.ts.",
|
|
3980
3982
|
smartRules: [
|
|
3981
3983
|
{
|
|
3982
3984
|
name: "shield:redis:block-flushall",
|
|
@@ -3985,7 +3987,7 @@ var init_dist = __esm({
|
|
|
3985
3987
|
{
|
|
3986
3988
|
field: "command",
|
|
3987
3989
|
op: "matches",
|
|
3988
|
-
value: "\\bFLUSHALL\\b",
|
|
3990
|
+
value: "(redis|valkey)-cli.*\\bFLUSHALL\\b|\\bFLUSHALL\\b.*(redis|valkey)-cli|^ ?FLUSHALL\\b|\\.flushall\\s*\\(",
|
|
3989
3991
|
flags: "i"
|
|
3990
3992
|
}
|
|
3991
3993
|
],
|
|
@@ -3999,7 +4001,7 @@ var init_dist = __esm({
|
|
|
3999
4001
|
{
|
|
4000
4002
|
field: "command",
|
|
4001
4003
|
op: "matches",
|
|
4002
|
-
value: "\\bFLUSHDB\\b",
|
|
4004
|
+
value: "(redis|valkey)-cli.*\\bFLUSHDB\\b|\\bFLUSHDB\\b.*(redis|valkey)-cli|^ ?FLUSHDB\\b|\\.flushdb\\s*\\(",
|
|
4003
4005
|
flags: "i"
|
|
4004
4006
|
}
|
|
4005
4007
|
],
|
|
@@ -4013,7 +4015,7 @@ var init_dist = __esm({
|
|
|
4013
4015
|
{
|
|
4014
4016
|
field: "command",
|
|
4015
4017
|
op: "matches",
|
|
4016
|
-
value: "\\
|
|
4018
|
+
value: "(redis|valkey)-cli.*CONFIG\\s+RESETSTAT|^ ?CONFIG\\s+RESETSTAT",
|
|
4017
4019
|
flags: "i"
|
|
4018
4020
|
}
|
|
4019
4021
|
],
|
|
@@ -4027,7 +4029,7 @@ var init_dist = __esm({
|
|
|
4027
4029
|
{
|
|
4028
4030
|
field: "command",
|
|
4029
4031
|
op: "matches",
|
|
4030
|
-
value: "\\bCONFIG\\s+SET\\b",
|
|
4032
|
+
value: "(redis|valkey)-cli.*\\bCONFIG\\s+SET\\b|\\bCONFIG\\s+SET\\b.*(redis|valkey)-cli|^ ?CONFIG\\s+SET\\b",
|
|
4031
4033
|
flags: "i"
|
|
4032
4034
|
}
|
|
4033
4035
|
],
|
|
@@ -4041,7 +4043,7 @@ var init_dist = __esm({
|
|
|
4041
4043
|
{
|
|
4042
4044
|
field: "command",
|
|
4043
4045
|
op: "matches",
|
|
4044
|
-
value: "
|
|
4046
|
+
value: "(redis|valkey)-cli.*\\bDEL\\b.*[*?\\[]|^ ?DEL\\b.*[*?\\[]|-cli.*--scan.*xargs.*del",
|
|
4045
4047
|
flags: "i"
|
|
4046
4048
|
}
|
|
4047
4049
|
],
|
|
@@ -4608,6 +4610,48 @@ function getActiveEnvironment(config) {
|
|
|
4608
4610
|
const env = config.settings.environment || process.env.NODE_ENV || "development";
|
|
4609
4611
|
return config.environments[env] ?? null;
|
|
4610
4612
|
}
|
|
4613
|
+
function readRulesCacheResilient(cacheFile) {
|
|
4614
|
+
let existed = false;
|
|
4615
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
4616
|
+
let content;
|
|
4617
|
+
try {
|
|
4618
|
+
content = import_fs4.default.readFileSync(cacheFile, "utf-8");
|
|
4619
|
+
existed = true;
|
|
4620
|
+
} catch (err2) {
|
|
4621
|
+
if (err2.code === "ENOENT") return {};
|
|
4622
|
+
continue;
|
|
4623
|
+
}
|
|
4624
|
+
try {
|
|
4625
|
+
return JSON.parse(content);
|
|
4626
|
+
} catch {
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
if (existed) {
|
|
4630
|
+
const backup = import_path4.default.join(import_path4.default.dirname(cacheFile), "rules-cache.last-good.json");
|
|
4631
|
+
if (backup !== cacheFile) {
|
|
4632
|
+
try {
|
|
4633
|
+
const raw = JSON.parse(import_fs4.default.readFileSync(backup, "utf-8"));
|
|
4634
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
|
|
4635
|
+
return raw;
|
|
4636
|
+
} catch {
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_UNREADABLE");
|
|
4640
|
+
}
|
|
4641
|
+
return {};
|
|
4642
|
+
}
|
|
4643
|
+
function logCacheReadIssue(cacheFile, kind) {
|
|
4644
|
+
if (cacheReadFailureLogged) return;
|
|
4645
|
+
cacheReadFailureLogged = true;
|
|
4646
|
+
try {
|
|
4647
|
+
import_fs4.default.appendFileSync(
|
|
4648
|
+
import_path4.default.join(import_os4.default.homedir(), ".node9", "hook-debug.log"),
|
|
4649
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
|
|
4650
|
+
`
|
|
4651
|
+
);
|
|
4652
|
+
} catch {
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4611
4655
|
function getConfig(cwd) {
|
|
4612
4656
|
if (!cwd && cachedConfig) return cachedConfig;
|
|
4613
4657
|
const globalPath = import_path4.default.join(import_os4.default.homedir(), ".node9", "config.json");
|
|
@@ -4675,6 +4719,7 @@ function getConfig(cwd) {
|
|
|
4675
4719
|
if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
|
|
4676
4720
|
if (s.mcpReconcileIntervalMinutes !== void 0)
|
|
4677
4721
|
mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
|
|
4722
|
+
if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
|
|
4678
4723
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
4679
4724
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
4680
4725
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -4684,12 +4729,13 @@ function getConfig(cwd) {
|
|
|
4684
4729
|
if (p.smartRules) {
|
|
4685
4730
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
4686
4731
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
4687
|
-
const
|
|
4732
|
+
const localRules = p.smartRules.map(({ pinned: _pinned, ...r }) => r);
|
|
4733
|
+
const userRuleNames = new Set(localRules.filter((r) => r.name).map((r) => r.name));
|
|
4688
4734
|
const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
|
|
4689
4735
|
const filteredNonBlocks = defaultNonBlocks.filter(
|
|
4690
4736
|
(r) => !r.name || !userRuleNames.has(r.name)
|
|
4691
4737
|
);
|
|
4692
|
-
mergedPolicy.smartRules = [...filteredBlocks, ...
|
|
4738
|
+
mergedPolicy.smartRules = [...filteredBlocks, ...localRules, ...filteredNonBlocks];
|
|
4693
4739
|
}
|
|
4694
4740
|
if (p.snapshot) {
|
|
4695
4741
|
const s2 = p.snapshot;
|
|
@@ -4754,10 +4800,12 @@ function getConfig(cwd) {
|
|
|
4754
4800
|
applyLayer(globalConfig);
|
|
4755
4801
|
applyLayer(projectConfig);
|
|
4756
4802
|
let cloudManagedShields = [];
|
|
4803
|
+
let modeCloudControlled = false;
|
|
4804
|
+
let modeCloudStaged = false;
|
|
4757
4805
|
{
|
|
4758
4806
|
const cacheFile = import_path4.default.join(import_os4.default.homedir(), ".node9", "rules-cache.json");
|
|
4759
4807
|
try {
|
|
4760
|
-
const raw =
|
|
4808
|
+
const raw = readRulesCacheResilient(cacheFile);
|
|
4761
4809
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
4762
4810
|
applyLayer({ policy: { smartRules: raw.rules } });
|
|
4763
4811
|
}
|
|
@@ -4774,6 +4822,9 @@ function getConfig(cwd) {
|
|
|
4774
4822
|
locked.includes("mode")
|
|
4775
4823
|
);
|
|
4776
4824
|
}
|
|
4825
|
+
if (typeof mc.mode === "string" || locked.includes("mode")) {
|
|
4826
|
+
modeCloudControlled = true;
|
|
4827
|
+
}
|
|
4777
4828
|
if (mc.egress && typeof mc.egress === "object") {
|
|
4778
4829
|
const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
|
|
4779
4830
|
mergedPolicy.egress = applyManagedEgress(
|
|
@@ -4872,19 +4923,28 @@ function getConfig(cwd) {
|
|
|
4872
4923
|
}
|
|
4873
4924
|
if (raw.shadowMode === true) {
|
|
4874
4925
|
mergedSettings.mode = "observe";
|
|
4926
|
+
modeCloudStaged = true;
|
|
4875
4927
|
}
|
|
4876
4928
|
} catch {
|
|
4877
4929
|
}
|
|
4878
4930
|
}
|
|
4879
4931
|
const shieldOverrides = readShieldOverrides();
|
|
4880
4932
|
const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
|
|
4933
|
+
const cloudManagedSet = new Set(cloudManagedShields);
|
|
4881
4934
|
for (const shieldName of activeShieldNames) {
|
|
4882
|
-
const
|
|
4935
|
+
const isCloudMandated = cloudManagedSet.has(shieldName);
|
|
4936
|
+
const shield = isCloudMandated ? BUILTIN_SHIELDS[shieldName] : getShield(shieldName);
|
|
4883
4937
|
if (!shield) continue;
|
|
4884
4938
|
const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
4885
|
-
const ruleOverrides = shieldOverrides[shieldName] ?? {};
|
|
4939
|
+
const ruleOverrides = isCloudMandated ? {} : shieldOverrides[shieldName] ?? {};
|
|
4886
4940
|
for (const rule of shield.smartRules) {
|
|
4887
|
-
|
|
4941
|
+
const collides = rule.name ? existingRuleNames.has(rule.name) : false;
|
|
4942
|
+
if (isCloudMandated) {
|
|
4943
|
+
if (collides) {
|
|
4944
|
+
mergedPolicy.smartRules = mergedPolicy.smartRules.filter((r) => r.name !== rule.name);
|
|
4945
|
+
}
|
|
4946
|
+
mergedPolicy.smartRules.push({ ...rule, pinned: true });
|
|
4947
|
+
} else if (!collides) {
|
|
4888
4948
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
4889
4949
|
mergedPolicy.smartRules.push(
|
|
4890
4950
|
overrideVerdict !== void 0 ? { ...rule, verdict: overrideVerdict } : rule
|
|
@@ -4900,7 +4960,27 @@ function getConfig(cwd) {
|
|
|
4900
4960
|
for (const rule of ADVISORY_SMART_RULES) {
|
|
4901
4961
|
if (!existingAdvisoryNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
4902
4962
|
}
|
|
4903
|
-
|
|
4963
|
+
const envMode = process.env.NODE9_MODE;
|
|
4964
|
+
if (envMode && !modeCloudControlled && ["observe", "audit", "standard", "strict"].includes(envMode)) {
|
|
4965
|
+
mergedSettings.mode = envMode;
|
|
4966
|
+
}
|
|
4967
|
+
if (cloudManagedShields.length > 0 && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
|
|
4968
|
+
mergedSettings.mode = "standard";
|
|
4969
|
+
}
|
|
4970
|
+
const managedFloorActive = cloudManagedShields.length > 0 || modeCloudControlled && mergedSettings.mode === "strict";
|
|
4971
|
+
if (modeCloudControlled && mergedSettings.mode === "strict") {
|
|
4972
|
+
for (const name of Object.keys(mergedEnvironments)) {
|
|
4973
|
+
if (mergedEnvironments[name]?.requireApproval === false) {
|
|
4974
|
+
const cleaned = { ...mergedEnvironments[name] };
|
|
4975
|
+
delete cleaned.requireApproval;
|
|
4976
|
+
mergedEnvironments[name] = cleaned;
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
if (managedFloorActive) {
|
|
4981
|
+
mergedPolicy.ignoredTools = [...DEFAULT_CONFIG.policy.ignoredTools];
|
|
4982
|
+
mergedPolicy.sandboxPaths = [...DEFAULT_CONFIG.policy.sandboxPaths];
|
|
4983
|
+
}
|
|
4904
4984
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
4905
4985
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
4906
4986
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
@@ -4969,7 +5049,7 @@ ${error.replace("Invalid config:\n", "")}
|
|
|
4969
5049
|
}
|
|
4970
5050
|
return sanitized;
|
|
4971
5051
|
}
|
|
4972
|
-
var import_fs4, import_path4, import_os4, DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig;
|
|
5052
|
+
var import_fs4, import_path4, import_os4, DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig, cacheReadFailureLogged;
|
|
4973
5053
|
var init_config = __esm({
|
|
4974
5054
|
"src/config/index.ts"() {
|
|
4975
5055
|
"use strict";
|
|
@@ -4981,6 +5061,7 @@ var init_config = __esm({
|
|
|
4981
5061
|
init_managed();
|
|
4982
5062
|
init_build();
|
|
4983
5063
|
init_trusted_hosts();
|
|
5064
|
+
init_dist();
|
|
4984
5065
|
DANGEROUS_WORDS = [
|
|
4985
5066
|
"mkfs",
|
|
4986
5067
|
// formats/wipes a filesystem partition
|
|
@@ -5258,6 +5339,7 @@ var init_config = __esm({
|
|
|
5258
5339
|
}
|
|
5259
5340
|
];
|
|
5260
5341
|
cachedConfig = null;
|
|
5342
|
+
cacheReadFailureLogged = false;
|
|
5261
5343
|
}
|
|
5262
5344
|
});
|
|
5263
5345
|
|
|
@@ -5975,6 +6057,18 @@ async function isDaemonReachable(timeoutMs = 500) {
|
|
|
5975
6057
|
return false;
|
|
5976
6058
|
}
|
|
5977
6059
|
}
|
|
6060
|
+
async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
6061
|
+
try {
|
|
6062
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/approver`, {
|
|
6063
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6064
|
+
});
|
|
6065
|
+
if (!res.ok) return false;
|
|
6066
|
+
const body = await res.json();
|
|
6067
|
+
return body.interactive === true;
|
|
6068
|
+
} catch {
|
|
6069
|
+
return false;
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
5978
6072
|
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent) {
|
|
5979
6073
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
5980
6074
|
const ctrl = new AbortController();
|
|
@@ -6859,6 +6953,12 @@ function isNetworkTool(toolName, args) {
|
|
|
6859
6953
|
function notifyActivity(data) {
|
|
6860
6954
|
return notifyActivitySocket(data);
|
|
6861
6955
|
}
|
|
6956
|
+
async function hasReachableHumanApprover(opts) {
|
|
6957
|
+
const hasDisplay = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
6958
|
+
const nativeReachable = !opts.calledFromDaemon && opts.approvers.native !== false && hasDisplay;
|
|
6959
|
+
if (nativeReachable) return true;
|
|
6960
|
+
return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
|
|
6961
|
+
}
|
|
6862
6962
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
6863
6963
|
if (!options?.calledFromDaemon) {
|
|
6864
6964
|
const actId = (0, import_crypto5.randomUUID)();
|
|
@@ -7173,6 +7273,35 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7173
7273
|
return { approved: true, checkedBy: "local-policy" };
|
|
7174
7274
|
}
|
|
7175
7275
|
if (policyResult.decision === "block") {
|
|
7276
|
+
const daemonUp = isDaemonRunning();
|
|
7277
|
+
let humanApproverReachable = false;
|
|
7278
|
+
if (!policyResult.dependsOnStatePredicates?.length && daemonUp && !isTestEnv2) {
|
|
7279
|
+
humanApproverReachable = await hasReachableHumanApprover({
|
|
7280
|
+
approvers,
|
|
7281
|
+
calledFromDaemon: options?.calledFromDaemon
|
|
7282
|
+
});
|
|
7283
|
+
}
|
|
7284
|
+
const mayDowngrade = daemonUp && !isTestEnv2 && humanApproverReachable;
|
|
7285
|
+
const hardBlock = () => {
|
|
7286
|
+
if (!isManual)
|
|
7287
|
+
appendLocalAudit(
|
|
7288
|
+
toolName,
|
|
7289
|
+
args,
|
|
7290
|
+
"deny",
|
|
7291
|
+
"smart-rule-block",
|
|
7292
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
7293
|
+
hashAuditArgs
|
|
7294
|
+
);
|
|
7295
|
+
return {
|
|
7296
|
+
approved: false,
|
|
7297
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7298
|
+
blockedBy: "local-config",
|
|
7299
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
7300
|
+
ruleHit: policyResult.ruleName,
|
|
7301
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7302
|
+
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7303
|
+
};
|
|
7304
|
+
};
|
|
7176
7305
|
if (policyResult.dependsOnStatePredicates?.length) {
|
|
7177
7306
|
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
7178
7307
|
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
@@ -7189,7 +7318,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7189
7318
|
if (predicatesMet && policyResult.recoveryCommand) {
|
|
7190
7319
|
statefulRecoveryCommand = policyResult.recoveryCommand;
|
|
7191
7320
|
}
|
|
7192
|
-
} else if (
|
|
7321
|
+
} else if (mayDowngrade) {
|
|
7193
7322
|
if (!isManual)
|
|
7194
7323
|
appendLocalAudit(
|
|
7195
7324
|
toolName,
|
|
@@ -7211,36 +7340,13 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7211
7340
|
};
|
|
7212
7341
|
}
|
|
7213
7342
|
} else {
|
|
7214
|
-
|
|
7215
|
-
appendLocalAudit(
|
|
7216
|
-
toolName,
|
|
7217
|
-
args,
|
|
7218
|
-
"deny",
|
|
7219
|
-
"smart-rule-block",
|
|
7220
|
-
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
7221
|
-
// panel can attribute this block to its specific shield
|
|
7222
|
-
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
7223
|
-
// rule→shield map. checkedBy stays as the generic
|
|
7224
|
-
// `smart-rule-block` for backward compat with existing
|
|
7225
|
-
// log readers.
|
|
7226
|
-
{ ...meta, ruleName: policyResult.ruleName },
|
|
7227
|
-
hashAuditArgs
|
|
7228
|
-
);
|
|
7229
|
-
return {
|
|
7230
|
-
approved: false,
|
|
7231
|
-
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7232
|
-
blockedBy: "local-config",
|
|
7233
|
-
blockedByLabel: policyResult.blockedByLabel,
|
|
7234
|
-
ruleHit: policyResult.ruleName,
|
|
7235
|
-
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7236
|
-
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7237
|
-
};
|
|
7343
|
+
return hardBlock();
|
|
7238
7344
|
}
|
|
7239
7345
|
}
|
|
7240
7346
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
7241
7347
|
policyMatchedField = policyResult.matchedField;
|
|
7242
7348
|
policyMatchedWord = policyResult.matchedWord;
|
|
7243
|
-
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
7349
|
+
if (policyResult.ruleName || policyResult.tier === 7) localSmartRuleMatched = true;
|
|
7244
7350
|
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
7245
7351
|
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
7246
7352
|
riskMetadata = computeRiskMetadata(
|
|
@@ -7252,7 +7358,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7252
7358
|
policyResult.ruleName
|
|
7253
7359
|
);
|
|
7254
7360
|
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
7255
|
-
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
7361
|
+
const persistent = policyResult.ruleName || policyResult.tier === 7 ? null : getPersistentDecision(toolName);
|
|
7256
7362
|
if (persistent === "allow" && !appPermReview) {
|
|
7257
7363
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
|
|
7258
7364
|
return { approved: true, checkedBy: "persistent" };
|
|
@@ -15919,6 +16025,9 @@ data: ${JSON.stringify(data)}
|
|
|
15919
16025
|
}
|
|
15920
16026
|
});
|
|
15921
16027
|
}
|
|
16028
|
+
function hasInteractiveClient() {
|
|
16029
|
+
return [...sseClients].some((c) => c.capabilities.includes("input"));
|
|
16030
|
+
}
|
|
15922
16031
|
function broadcastForensic(finding) {
|
|
15923
16032
|
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
15924
16033
|
const event = {
|
|
@@ -17156,18 +17265,27 @@ var init_score = __esm({
|
|
|
17156
17265
|
|
|
17157
17266
|
// src/posture/headline.ts
|
|
17158
17267
|
function worstFinding(findings) {
|
|
17159
|
-
|
|
17268
|
+
const cmp = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
17269
|
+
return [...findings].sort(
|
|
17270
|
+
(a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || cmp(a.category, b.category) || cmp(a.title, b.title)
|
|
17271
|
+
)[0];
|
|
17272
|
+
}
|
|
17273
|
+
function actionFromFinding(f) {
|
|
17274
|
+
if (!f?.fix) return null;
|
|
17275
|
+
const fix = f.fix.replace(/^fix it now:\s*/i, "");
|
|
17276
|
+
const where = f.detail.length === 0 ? "" : f.detail.length === 1 ? ` \u2014 found: ${f.detail[0]}` : ` \u2014 found: ${f.detail[0]} and ${f.detail.length - 1} more`;
|
|
17277
|
+
return fix + where;
|
|
17160
17278
|
}
|
|
17161
17279
|
function deriveHeadline(allFindings) {
|
|
17162
17280
|
const findings = allFindings.filter(
|
|
17163
17281
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
|
|
17164
17282
|
);
|
|
17165
17283
|
if (findings.length === 0 || findings.every((f) => f.severity === "advisory")) return null;
|
|
17166
|
-
const
|
|
17167
|
-
const secrets =
|
|
17168
|
-
const egressOpen =
|
|
17169
|
-
const noIsolation =
|
|
17170
|
-
const gateWeak =
|
|
17284
|
+
const has2 = (category) => findings.some((f) => f.category === category);
|
|
17285
|
+
const secrets = has2("Secrets");
|
|
17286
|
+
const egressOpen = has2("Egress");
|
|
17287
|
+
const noIsolation = has2("Isolation");
|
|
17288
|
+
const gateWeak = has2("Approval gate");
|
|
17171
17289
|
const notWired = findings.some((f) => f.category === "Coverage" && f.severity === "critical");
|
|
17172
17290
|
const observeOnly = findings.some((f) => f.category === "Coverage" && f.severity === "high");
|
|
17173
17291
|
let risk;
|
|
@@ -17190,13 +17308,16 @@ function deriveHeadline(allFindings) {
|
|
|
17190
17308
|
} else if (observeOnly) {
|
|
17191
17309
|
action = "Switch node9 to enforcing mode \u2014 right now it is only watching, not blocking.";
|
|
17192
17310
|
} else if (egressOpen) {
|
|
17193
|
-
|
|
17311
|
+
const egressFix = actionFromFinding(
|
|
17312
|
+
worstFinding(findings.filter((f) => f.category === "Egress"))
|
|
17313
|
+
);
|
|
17314
|
+
action = egressFix ? `${egressFix} Closing the exit breaks the exfiltration chain.` : "lock egress to an allowlist (node9 can enforce it) \u2014 it closes the exit the exfiltration needs.";
|
|
17194
17315
|
} else if (secrets) {
|
|
17195
|
-
action = "node9 can block reads of sensitive
|
|
17316
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Secrets"))) ?? "node9 can block reads of sensitive credential files in-path (`node9 shield enable project-jail`).";
|
|
17196
17317
|
} else if (gateWeak) {
|
|
17197
|
-
action = "node9 can enforce destructive-command blocking in-path.";
|
|
17318
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Approval gate"))) ?? "node9 can enforce destructive-command blocking in-path (`node9 shield enable bash-safe`).";
|
|
17198
17319
|
} else {
|
|
17199
|
-
action = worstFinding(findings)
|
|
17320
|
+
action = actionFromFinding(worstFinding(findings)) ?? "Review the findings below.";
|
|
17200
17321
|
}
|
|
17201
17322
|
return { risk, action };
|
|
17202
17323
|
}
|
|
@@ -17451,7 +17572,7 @@ var init_ship = __esm({
|
|
|
17451
17572
|
});
|
|
17452
17573
|
|
|
17453
17574
|
// src/policy-snapshot/build.ts
|
|
17454
|
-
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = []) {
|
|
17575
|
+
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = [], syncHealth) {
|
|
17455
17576
|
const p = config.policy;
|
|
17456
17577
|
const statusByKey = /* @__PURE__ */ new Map();
|
|
17457
17578
|
for (const s of statusEntries) {
|
|
@@ -17510,7 +17631,17 @@ function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, st
|
|
|
17510
17631
|
dlpEnabled: p.dlp.enabled,
|
|
17511
17632
|
engineVersion: ENGINE_VERSION,
|
|
17512
17633
|
// Connected rows first so they win the cap; non-connected SEE rows fill the rest.
|
|
17513
|
-
mcpServers: [...connectedRows, ...extraRows].slice(0, MAX_MCP_SERVERS)
|
|
17634
|
+
mcpServers: [...connectedRows, ...extraRows].slice(0, MAX_MCP_SERVERS),
|
|
17635
|
+
// fleet-ship Step 2: ship the proxy's own sync health so the dashboard can
|
|
17636
|
+
// derive a "sync failing" badge. Only included when the caller passes it.
|
|
17637
|
+
...syncHealth && {
|
|
17638
|
+
syncHealth: {
|
|
17639
|
+
lastCheckedAt: syncHealth.lastCheckedAt,
|
|
17640
|
+
lastChangedAt: syncHealth.lastChangedAt,
|
|
17641
|
+
lastError: syncHealth.lastError,
|
|
17642
|
+
consecutiveFailures: syncHealth.consecutiveFailures
|
|
17643
|
+
}
|
|
17644
|
+
}
|
|
17514
17645
|
};
|
|
17515
17646
|
}
|
|
17516
17647
|
var MAX_RULES, MAX_EGRESS, MAX_MCP_SERVERS, MAX_MCP_TOOLS;
|
|
@@ -17724,6 +17855,21 @@ function inventoryMcp(home = import_os31.default.homedir()) {
|
|
|
17724
17855
|
}
|
|
17725
17856
|
return out;
|
|
17726
17857
|
}
|
|
17858
|
+
function inventoryServerKeys(inv) {
|
|
17859
|
+
const keys = /* @__PURE__ */ new Set();
|
|
17860
|
+
for (const e of inv) {
|
|
17861
|
+
if (e.state === "gatewayed") {
|
|
17862
|
+
const i = e.args.indexOf("--upstream");
|
|
17863
|
+
if (i >= 0 && e.args[i + 1]) {
|
|
17864
|
+
keys.add(getServerKey(e.args[i + 1]));
|
|
17865
|
+
}
|
|
17866
|
+
} else if (e.state === "ungoverned") {
|
|
17867
|
+
const cmd = [e.command, ...e.args].map(quoteArg).join(" ");
|
|
17868
|
+
keys.add(getServerKey(cmd));
|
|
17869
|
+
}
|
|
17870
|
+
}
|
|
17871
|
+
return keys;
|
|
17872
|
+
}
|
|
17727
17873
|
function writeMcpEntry(mcpFile, format, name, entry) {
|
|
17728
17874
|
const key = format === "toml" ? "mcp_servers" : "mcpServers";
|
|
17729
17875
|
let root = {};
|
|
@@ -17751,6 +17897,7 @@ var init_mcp_wrap = __esm({
|
|
|
17751
17897
|
import_smol_toml4 = require("smol-toml");
|
|
17752
17898
|
init_agent_wiring();
|
|
17753
17899
|
init_mcp_cmd();
|
|
17900
|
+
init_mcp_pin();
|
|
17754
17901
|
init_mcp_cmd();
|
|
17755
17902
|
}
|
|
17756
17903
|
});
|
|
@@ -18179,9 +18326,12 @@ function extractManagedConfig(body) {
|
|
|
18179
18326
|
return out.mode !== void 0 || out.egress !== void 0 || out.dlp !== void 0 || out.approvers !== void 0 || out.reviewChannel !== void 0 || out.approvalTimeoutMs !== void 0 || out.injectionScan !== void 0 || out.loopDetection !== void 0 || out.skillPinning !== void 0 || out.jailPaths !== void 0 || out.trustedHosts !== void 0 || out.appPermissions !== void 0 ? out : void 0;
|
|
18180
18327
|
}
|
|
18181
18328
|
function writeCache2(cache) {
|
|
18182
|
-
const
|
|
18183
|
-
|
|
18184
|
-
|
|
18329
|
+
const data = JSON.stringify(cache, null, 2) + "\n";
|
|
18330
|
+
atomicWriteSync2(rulesCacheFile(), data, "utf-8");
|
|
18331
|
+
try {
|
|
18332
|
+
atomicWriteSync2(rulesCacheBackupFile(), data, "utf-8");
|
|
18333
|
+
} catch {
|
|
18334
|
+
}
|
|
18185
18335
|
}
|
|
18186
18336
|
async function syncOnce() {
|
|
18187
18337
|
const creds = readCredentials();
|
|
@@ -18285,7 +18435,10 @@ async function pushPolicySnapshot(creds) {
|
|
|
18285
18435
|
// Merged config-vs-connected status. resolveMcpStatus reads process.env to
|
|
18286
18436
|
// resolve ${VAR} placeholders — so this push reflects THIS process's env
|
|
18287
18437
|
// (daemon here, user shell in runPolicyPush); see the P2 design's two-env note.
|
|
18288
|
-
resolveMcpStatus()
|
|
18438
|
+
resolveMcpStatus(),
|
|
18439
|
+
// fleet-ship Step 2: ship this machine's sync health so the dashboard can
|
|
18440
|
+
// show "sync failing" badges. readSyncHealth is local (same module, no circular).
|
|
18441
|
+
readSyncHealth()
|
|
18289
18442
|
);
|
|
18290
18443
|
await shipPolicySnapshot(body, creds);
|
|
18291
18444
|
} catch {
|
|
@@ -18308,7 +18461,8 @@ async function runPolicyPush() {
|
|
|
18308
18461
|
// Merged config-vs-connected status. resolveMcpStatus reads process.env to
|
|
18309
18462
|
// resolve ${VAR} placeholders — so this push reflects THIS process's env
|
|
18310
18463
|
// (daemon here, user shell in runPolicyPush); see the P2 design's two-env note.
|
|
18311
|
-
resolveMcpStatus()
|
|
18464
|
+
resolveMcpStatus(),
|
|
18465
|
+
readSyncHealth()
|
|
18312
18466
|
);
|
|
18313
18467
|
const sent = await shipPolicySnapshot(body, creds);
|
|
18314
18468
|
return sent ? { ok: true } : { ok: false, reason: "Push failed (network or server error)" };
|
|
@@ -18471,7 +18625,7 @@ function startForensicBroadcast() {
|
|
|
18471
18625
|
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
18472
18626
|
recurring.unref();
|
|
18473
18627
|
}
|
|
18474
|
-
var import_fs35, import_https4, import_os32, import_path34, FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL2, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, syncHealthFile, STALE_MIN_MS, STALE_MAX_MS, STALE_FACTOR, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
18628
|
+
var import_fs35, import_https4, import_os32, import_path34, FINDING_TO_SIGNAL3, rulesCacheFile, rulesCacheBackupFile, DEFAULT_API_URL2, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, syncHealthFile, STALE_MIN_MS, STALE_MAX_MS, STALE_FACTOR, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
18475
18629
|
var init_sync = __esm({
|
|
18476
18630
|
"src/daemon/sync.ts"() {
|
|
18477
18631
|
"use strict";
|
|
@@ -18485,6 +18639,7 @@ var init_sync = __esm({
|
|
|
18485
18639
|
init_ship();
|
|
18486
18640
|
init_build2();
|
|
18487
18641
|
init_mcp_tools();
|
|
18642
|
+
init_state2();
|
|
18488
18643
|
init_mcp_status();
|
|
18489
18644
|
init_ship2();
|
|
18490
18645
|
init_shields();
|
|
@@ -18505,6 +18660,7 @@ var init_sync = __esm({
|
|
|
18505
18660
|
"long-output-redacted": "longOutputRedactions"
|
|
18506
18661
|
};
|
|
18507
18662
|
rulesCacheFile = () => import_path34.default.join(import_os32.default.homedir(), ".node9", "rules-cache.json");
|
|
18663
|
+
rulesCacheBackupFile = () => import_path34.default.join(import_os32.default.homedir(), ".node9", "rules-cache.last-good.json");
|
|
18508
18664
|
DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
18509
18665
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
18510
18666
|
MIN_INTERVAL_SECONDS = 15;
|
|
@@ -18746,6 +18902,53 @@ var init_audit_shipper = __esm({
|
|
|
18746
18902
|
}
|
|
18747
18903
|
});
|
|
18748
18904
|
|
|
18905
|
+
// src/audit/decision.ts
|
|
18906
|
+
function classifyDecision(a, b) {
|
|
18907
|
+
const isRow = !!a && typeof a === "object";
|
|
18908
|
+
const decision = isRow ? a.decision : a;
|
|
18909
|
+
const attribution = isRow ? a.checkedBy ?? a.source : b;
|
|
18910
|
+
const raw = typeof decision === "string" ? decision : String(decision ?? "");
|
|
18911
|
+
const src = typeof attribution === "string" ? attribution.toLowerCase() : "";
|
|
18912
|
+
const d = raw.toLowerCase();
|
|
18913
|
+
if (src && has(src, "observe-mode")) {
|
|
18914
|
+
return { outcome: "observe", label: "Would block", raw };
|
|
18915
|
+
}
|
|
18916
|
+
if (d === "dlp") return { outcome: "info", label: "Finding", raw };
|
|
18917
|
+
if (d === "mcp-discovered") return { outcome: "info", label: "Info", raw };
|
|
18918
|
+
if (d === "allow" || d === "allowed") {
|
|
18919
|
+
if (src === "post-hook") return { outcome: "allow", label: "Ran", raw };
|
|
18920
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18921
|
+
return { outcome: "allow", label: "Approved", raw };
|
|
18922
|
+
}
|
|
18923
|
+
return { outcome: "allow", label: "Auto-allowed", raw };
|
|
18924
|
+
}
|
|
18925
|
+
if (d === "deny" || d === "auto-deny" || d === "block") {
|
|
18926
|
+
if (TIMEOUT_SOURCES.has(src)) {
|
|
18927
|
+
return { outcome: "deny", label: "Timed out", raw };
|
|
18928
|
+
}
|
|
18929
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18930
|
+
return { outcome: "deny", label: "Denied", raw };
|
|
18931
|
+
}
|
|
18932
|
+
return { outcome: "deny", label: "Blocked", raw };
|
|
18933
|
+
}
|
|
18934
|
+
if (d === "review" || d === "pending") {
|
|
18935
|
+
return { outcome: "info", label: "Pending", raw };
|
|
18936
|
+
}
|
|
18937
|
+
return { outcome: "unknown", label: raw ? `? ${raw}` : "? (none)", raw };
|
|
18938
|
+
}
|
|
18939
|
+
function decisionTag(view) {
|
|
18940
|
+
return `[${view.label}]`.padEnd(14);
|
|
18941
|
+
}
|
|
18942
|
+
var HUMAN_SOURCES, TIMEOUT_SOURCES, has;
|
|
18943
|
+
var init_decision = __esm({
|
|
18944
|
+
"src/audit/decision.ts"() {
|
|
18945
|
+
"use strict";
|
|
18946
|
+
HUMAN_SOURCES = /* @__PURE__ */ new Set(["daemon", "cloud", "local-decision", "inline-review-approved"]);
|
|
18947
|
+
TIMEOUT_SOURCES = /* @__PURE__ */ new Set(["timeout"]);
|
|
18948
|
+
has = (s, needle) => s.includes(needle);
|
|
18949
|
+
}
|
|
18950
|
+
});
|
|
18951
|
+
|
|
18749
18952
|
// src/daemon/dlp-scanner.ts
|
|
18750
18953
|
function loadIndex() {
|
|
18751
18954
|
try {
|
|
@@ -18981,6 +19184,7 @@ function runMcpReconcile() {
|
|
|
18981
19184
|
}
|
|
18982
19185
|
const baseline = loadBaseline();
|
|
18983
19186
|
const creds = getCredentials();
|
|
19187
|
+
reconcileStale(inv, creds);
|
|
18984
19188
|
const fresh = inv.filter((e) => e.state === "ungoverned" && !baseline.has(idKey(e)));
|
|
18985
19189
|
if (fresh.length === 0) return;
|
|
18986
19190
|
const wrappedAgents = /* @__PURE__ */ new Set();
|
|
@@ -19020,6 +19224,77 @@ function runMcpReconcile() {
|
|
|
19020
19224
|
}
|
|
19021
19225
|
saveBaseline(baseline);
|
|
19022
19226
|
}
|
|
19227
|
+
function reconcileStale(inv, creds) {
|
|
19228
|
+
let pins;
|
|
19229
|
+
try {
|
|
19230
|
+
pins = readMcpPins();
|
|
19231
|
+
} catch {
|
|
19232
|
+
return;
|
|
19233
|
+
}
|
|
19234
|
+
const serverKeys = Object.keys(pins.servers);
|
|
19235
|
+
if (serverKeys.length === 0) return;
|
|
19236
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
19237
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19238
|
+
let dirty = false;
|
|
19239
|
+
for (const sk of serverKeys) {
|
|
19240
|
+
const pin = pins.servers[sk];
|
|
19241
|
+
if (liveKeys.has(sk)) {
|
|
19242
|
+
if (pin.lastSeen !== now) {
|
|
19243
|
+
pin.lastSeen = now;
|
|
19244
|
+
dirty = true;
|
|
19245
|
+
}
|
|
19246
|
+
} else if (!pin.lastSeen) {
|
|
19247
|
+
pin.lastSeen = pin.pinnedAt;
|
|
19248
|
+
dirty = true;
|
|
19249
|
+
}
|
|
19250
|
+
}
|
|
19251
|
+
const staleDays = getConfig().settings.mcpStaleAfterDays ?? DEFAULT_STALE_DAYS;
|
|
19252
|
+
if (staleDays > 0 && liveKeys.size > 0) {
|
|
19253
|
+
const staleMs = staleDays * 864e5;
|
|
19254
|
+
for (const sk of serverKeys) {
|
|
19255
|
+
const pin = pins.servers[sk];
|
|
19256
|
+
if (liveKeys.has(sk)) continue;
|
|
19257
|
+
const age = Date.now() - Date.parse(pin.lastSeen ?? pin.pinnedAt);
|
|
19258
|
+
if (age >= staleMs) {
|
|
19259
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
19260
|
+
event: "mcp-pin-auto-removed",
|
|
19261
|
+
serverKey: sk,
|
|
19262
|
+
label: pin.label,
|
|
19263
|
+
lastSeen: pin.lastSeen,
|
|
19264
|
+
pinnedAt: pin.pinnedAt
|
|
19265
|
+
});
|
|
19266
|
+
if (creds) {
|
|
19267
|
+
try {
|
|
19268
|
+
void auditLocalAllow(
|
|
19269
|
+
`mcp-server:${sk}`,
|
|
19270
|
+
{
|
|
19271
|
+
serverKey: sk,
|
|
19272
|
+
label: pin.label,
|
|
19273
|
+
lastSeen: pin.lastSeen,
|
|
19274
|
+
reason: "stale",
|
|
19275
|
+
staleDays
|
|
19276
|
+
},
|
|
19277
|
+
"mcp-server-removed",
|
|
19278
|
+
creds,
|
|
19279
|
+
{ mcpServer: pin.label },
|
|
19280
|
+
void 0,
|
|
19281
|
+
false
|
|
19282
|
+
);
|
|
19283
|
+
} catch {
|
|
19284
|
+
}
|
|
19285
|
+
}
|
|
19286
|
+
delete pins.servers[sk];
|
|
19287
|
+
dirty = true;
|
|
19288
|
+
}
|
|
19289
|
+
}
|
|
19290
|
+
}
|
|
19291
|
+
if (dirty) {
|
|
19292
|
+
try {
|
|
19293
|
+
writeMcpPins(pins);
|
|
19294
|
+
} catch {
|
|
19295
|
+
}
|
|
19296
|
+
}
|
|
19297
|
+
}
|
|
19023
19298
|
function startMcpReconciler() {
|
|
19024
19299
|
setImmediate(() => {
|
|
19025
19300
|
try {
|
|
@@ -19041,7 +19316,7 @@ function startMcpReconciler() {
|
|
|
19041
19316
|
};
|
|
19042
19317
|
schedule();
|
|
19043
19318
|
}
|
|
19044
|
-
var import_fs38, import_path37, import_os35, import_crypto10, BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN;
|
|
19319
|
+
var import_fs38, import_path37, import_os35, import_crypto10, BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN, DEFAULT_STALE_DAYS;
|
|
19045
19320
|
var init_mcp_reconciler = __esm({
|
|
19046
19321
|
"src/daemon/mcp-reconciler.ts"() {
|
|
19047
19322
|
"use strict";
|
|
@@ -19054,9 +19329,11 @@ var init_mcp_reconciler = __esm({
|
|
|
19054
19329
|
init_config();
|
|
19055
19330
|
init_cloud();
|
|
19056
19331
|
init_audit();
|
|
19332
|
+
init_mcp_pin();
|
|
19057
19333
|
BASELINE_FILE2 = import_path37.default.join(import_os35.default.homedir(), ".node9", "mcp-baseline.json");
|
|
19058
19334
|
BASELINE_CAP = 500;
|
|
19059
19335
|
DEFAULT_INTERVAL_MIN = 60;
|
|
19336
|
+
DEFAULT_STALE_DAYS = 7;
|
|
19060
19337
|
}
|
|
19061
19338
|
});
|
|
19062
19339
|
|
|
@@ -19128,20 +19405,87 @@ var init_hook_heal = __esm({
|
|
|
19128
19405
|
});
|
|
19129
19406
|
|
|
19130
19407
|
// src/daemon/startup-log.ts
|
|
19408
|
+
function capStartupLog(file) {
|
|
19409
|
+
try {
|
|
19410
|
+
if (import_fs39.default.statSync(file).size > MAX_STARTUP_LOG_BYTES) import_fs39.default.truncateSync(file);
|
|
19411
|
+
} catch {
|
|
19412
|
+
}
|
|
19413
|
+
}
|
|
19131
19414
|
function openStartupLogFd() {
|
|
19132
19415
|
try {
|
|
19133
19416
|
const file = DAEMON_STARTUP_LOG();
|
|
19134
19417
|
const dir = import_path38.default.dirname(file);
|
|
19135
19418
|
if (!import_fs39.default.existsSync(dir)) import_fs39.default.mkdirSync(dir, { recursive: true });
|
|
19136
|
-
|
|
19137
|
-
if (import_fs39.default.statSync(file).size > MAX_STARTUP_LOG_BYTES) import_fs39.default.truncateSync(file);
|
|
19138
|
-
} catch {
|
|
19139
|
-
}
|
|
19419
|
+
capStartupLog(file);
|
|
19140
19420
|
return import_fs39.default.openSync(file, "a");
|
|
19141
19421
|
} catch {
|
|
19142
19422
|
return void 0;
|
|
19143
19423
|
}
|
|
19144
19424
|
}
|
|
19425
|
+
function recordStartupState(outcome, kind, detail) {
|
|
19426
|
+
try {
|
|
19427
|
+
if (outcome === "starting") {
|
|
19428
|
+
const prev = readStartupState();
|
|
19429
|
+
if (prev?.outcome === "starting") {
|
|
19430
|
+
const prevAt = new Date(prev.at).getTime();
|
|
19431
|
+
const age = Math.abs(Date.now() - prevAt);
|
|
19432
|
+
if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
|
|
19433
|
+
}
|
|
19434
|
+
}
|
|
19435
|
+
const file = DAEMON_STARTUP_STATE();
|
|
19436
|
+
const dir = import_path38.default.dirname(file);
|
|
19437
|
+
if (!import_fs39.default.existsSync(dir)) import_fs39.default.mkdirSync(dir, { recursive: true });
|
|
19438
|
+
const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
19439
|
+
if (kind) state.kind = kind;
|
|
19440
|
+
if (detail) state.detail = detail.slice(0, MAX_DETAIL);
|
|
19441
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
19442
|
+
try {
|
|
19443
|
+
import_fs39.default.writeFileSync(tmp, JSON.stringify(state), "utf-8");
|
|
19444
|
+
import_fs39.default.renameSync(tmp, file);
|
|
19445
|
+
} catch (err2) {
|
|
19446
|
+
try {
|
|
19447
|
+
import_fs39.default.unlinkSync(tmp);
|
|
19448
|
+
} catch {
|
|
19449
|
+
}
|
|
19450
|
+
throw err2;
|
|
19451
|
+
}
|
|
19452
|
+
} catch {
|
|
19453
|
+
}
|
|
19454
|
+
}
|
|
19455
|
+
function readStartupState() {
|
|
19456
|
+
try {
|
|
19457
|
+
const raw = import_fs39.default.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
|
|
19458
|
+
const s = JSON.parse(raw);
|
|
19459
|
+
if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
|
|
19460
|
+
return s;
|
|
19461
|
+
} catch {
|
|
19462
|
+
return null;
|
|
19463
|
+
}
|
|
19464
|
+
}
|
|
19465
|
+
function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
19466
|
+
const s = readStartupState();
|
|
19467
|
+
if (!s) return null;
|
|
19468
|
+
const at = new Date(s.at);
|
|
19469
|
+
if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
|
|
19470
|
+
switch (s.outcome) {
|
|
19471
|
+
case "ok":
|
|
19472
|
+
case "ok-elsewhere":
|
|
19473
|
+
return null;
|
|
19474
|
+
case "starting":
|
|
19475
|
+
if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
|
|
19476
|
+
return {
|
|
19477
|
+
kind: "did-not-start",
|
|
19478
|
+
detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
|
|
19479
|
+
at,
|
|
19480
|
+
// `at` is the FIRST attempt of the streak, not the most recent one.
|
|
19481
|
+
label: "start attempts failing since"
|
|
19482
|
+
};
|
|
19483
|
+
case "failed":
|
|
19484
|
+
return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
|
|
19485
|
+
default:
|
|
19486
|
+
return null;
|
|
19487
|
+
}
|
|
19488
|
+
}
|
|
19145
19489
|
function logDaemonStartup(kind, detail) {
|
|
19146
19490
|
try {
|
|
19147
19491
|
const file = DAEMON_STARTUP_LOG();
|
|
@@ -19153,7 +19497,7 @@ function logDaemonStartup(kind, detail) {
|
|
|
19153
19497
|
} catch {
|
|
19154
19498
|
}
|
|
19155
19499
|
}
|
|
19156
|
-
var import_fs39, import_path38, import_os36, DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES;
|
|
19500
|
+
var import_fs39, import_path38, import_os36, DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
|
|
19157
19501
|
var init_startup_log = __esm({
|
|
19158
19502
|
"src/daemon/startup-log.ts"() {
|
|
19159
19503
|
"use strict";
|
|
@@ -19162,10 +19506,83 @@ var init_startup_log = __esm({
|
|
|
19162
19506
|
import_os36 = __toESM(require("os"));
|
|
19163
19507
|
DAEMON_STARTUP_LOG = () => import_path38.default.join(import_os36.default.homedir(), ".node9", "daemon-startup.log");
|
|
19164
19508
|
MAX_STARTUP_LOG_BYTES = 256 * 1024;
|
|
19509
|
+
DAEMON_STARTUP_STATE = () => import_path38.default.join(import_os36.default.homedir(), ".node9", "daemon-startup-state.json");
|
|
19510
|
+
MAX_DETAIL = 200;
|
|
19511
|
+
STARTING_GRACE_MS = 90 * 1e3;
|
|
19165
19512
|
}
|
|
19166
19513
|
});
|
|
19167
19514
|
|
|
19168
19515
|
// src/daemon/server.ts
|
|
19516
|
+
function buildDaemonReport(allEntries, period, now) {
|
|
19517
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19518
|
+
let start = new Date(todayStart);
|
|
19519
|
+
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19520
|
+
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19521
|
+
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19522
|
+
const entries = allEntries.filter((e) => {
|
|
19523
|
+
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19524
|
+
return new Date(e.ts) >= start;
|
|
19525
|
+
});
|
|
19526
|
+
const isBlocked = (e) => classifyDecision(e).outcome === "deny";
|
|
19527
|
+
const checkedBy = (e) => typeof e.checkedBy === "string" ? e.checkedBy : void 0;
|
|
19528
|
+
const summary = {
|
|
19529
|
+
total: entries.length,
|
|
19530
|
+
// The inline `startsWith('allow')` this replaces counted every non-allow
|
|
19531
|
+
// row as "blocked" — so DLP findings, MCP-discovery events and, worst, all
|
|
19532
|
+
// the observe-mode "would have blocked" rows inflated the blocked count
|
|
19533
|
+
// with things that were never refusals.
|
|
19534
|
+
allowed: entries.filter((e) => classifyDecision(e).outcome === "allow").length,
|
|
19535
|
+
blocked: entries.filter(isBlocked).length,
|
|
19536
|
+
dlp: entries.filter((e) => checkedBy(e)?.includes("dlp")).length,
|
|
19537
|
+
loops: entries.filter((e) => checkedBy(e) === "loop-detected").length
|
|
19538
|
+
};
|
|
19539
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
19540
|
+
if (period === "today") {
|
|
19541
|
+
for (let h = 0; h < 24; h++) {
|
|
19542
|
+
const key = String(h).padStart(2, "0") + ":00";
|
|
19543
|
+
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19544
|
+
}
|
|
19545
|
+
for (const e of entries) {
|
|
19546
|
+
const hour = new Date(e.ts).getHours();
|
|
19547
|
+
const key = String(hour).padStart(2, "0") + ":00";
|
|
19548
|
+
const d = dailyMap.get(key);
|
|
19549
|
+
d.calls++;
|
|
19550
|
+
if (isBlocked(e)) d.blocked++;
|
|
19551
|
+
}
|
|
19552
|
+
} else {
|
|
19553
|
+
for (const e of entries) {
|
|
19554
|
+
const date = e.ts.slice(0, 10);
|
|
19555
|
+
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19556
|
+
d.calls++;
|
|
19557
|
+
if (isBlocked(e)) d.blocked++;
|
|
19558
|
+
dailyMap.set(date, d);
|
|
19559
|
+
}
|
|
19560
|
+
}
|
|
19561
|
+
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19562
|
+
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19563
|
+
for (const e of entries) {
|
|
19564
|
+
const tool = String(e.tool ?? "");
|
|
19565
|
+
topToolsMap.set(tool, (topToolsMap.get(tool) || 0) + 1);
|
|
19566
|
+
if (isBlocked(e)) topBlockedMap.set(tool, (topBlockedMap.get(tool) || 0) + 1);
|
|
19567
|
+
}
|
|
19568
|
+
const top5 = (m) => [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19569
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
19570
|
+
for (const e of entries) {
|
|
19571
|
+
const key = e.agent || "unknown";
|
|
19572
|
+
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19573
|
+
a.total++;
|
|
19574
|
+
if (isBlocked(e)) a.blocked++;
|
|
19575
|
+
if (checkedBy(e)?.includes("dlp")) a.dlp++;
|
|
19576
|
+
agentMap.set(key, a);
|
|
19577
|
+
}
|
|
19578
|
+
return {
|
|
19579
|
+
summary,
|
|
19580
|
+
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19581
|
+
topTools: top5(topToolsMap),
|
|
19582
|
+
topBlockedTools: top5(topBlockedMap),
|
|
19583
|
+
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
19584
|
+
};
|
|
19585
|
+
}
|
|
19169
19586
|
function startDaemon() {
|
|
19170
19587
|
try {
|
|
19171
19588
|
startCostSync();
|
|
@@ -19180,6 +19597,7 @@ function startDaemon() {
|
|
|
19180
19597
|
const stack = err2 instanceof Error ? err2.stack ?? err2.message : String(err2);
|
|
19181
19598
|
console.error("\n\u{1F6D1} Node9 daemon startup failed:\n" + stack);
|
|
19182
19599
|
logDaemonStartup("startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19600
|
+
recordStartupState("failed", "startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19183
19601
|
process.exit(1);
|
|
19184
19602
|
}
|
|
19185
19603
|
const internalToken = (0, import_crypto11.randomUUID)();
|
|
@@ -19552,6 +19970,10 @@ data: ${JSON.stringify(item.data)}
|
|
|
19552
19970
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
19553
19971
|
}
|
|
19554
19972
|
}
|
|
19973
|
+
if (req.method === "GET" && pathname === "/approver") {
|
|
19974
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19975
|
+
return res.end(JSON.stringify({ interactive: hasInteractiveClient() }));
|
|
19976
|
+
}
|
|
19555
19977
|
if (req.method === "GET" && pathname === "/state/check") {
|
|
19556
19978
|
const predicatesParam = reqUrl.searchParams.get("predicates") ?? "";
|
|
19557
19979
|
const predicates = predicatesParam.split(",").filter(Boolean);
|
|
@@ -19653,75 +20075,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
19653
20075
|
return [];
|
|
19654
20076
|
}
|
|
19655
20077
|
});
|
|
19656
|
-
const now = /* @__PURE__ */ new Date();
|
|
19657
|
-
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19658
|
-
let start = new Date(todayStart);
|
|
19659
|
-
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19660
|
-
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19661
|
-
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19662
|
-
const entries = allEntries.filter((e) => {
|
|
19663
|
-
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19664
|
-
return new Date(e.ts) >= start;
|
|
19665
|
-
});
|
|
19666
|
-
const summary = {
|
|
19667
|
-
total: entries.length,
|
|
19668
|
-
allowed: entries.filter((e) => e.decision && e.decision.startsWith("allow")).length,
|
|
19669
|
-
blocked: entries.filter((e) => e.decision && !e.decision.startsWith("allow")).length,
|
|
19670
|
-
dlp: entries.filter((e) => e.checkedBy && e.checkedBy.includes("dlp")).length,
|
|
19671
|
-
loops: entries.filter((e) => e.checkedBy === "loop-detected").length
|
|
19672
|
-
};
|
|
19673
|
-
const dailyMap = /* @__PURE__ */ new Map();
|
|
19674
|
-
if (period === "today") {
|
|
19675
|
-
for (let h = 0; h < 24; h++) {
|
|
19676
|
-
const key = String(h).padStart(2, "0") + ":00";
|
|
19677
|
-
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19678
|
-
}
|
|
19679
|
-
for (const e of entries) {
|
|
19680
|
-
const hour = new Date(e.ts).getHours();
|
|
19681
|
-
const key = String(hour).padStart(2, "0") + ":00";
|
|
19682
|
-
const d = dailyMap.get(key);
|
|
19683
|
-
d.calls++;
|
|
19684
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19685
|
-
}
|
|
19686
|
-
} else {
|
|
19687
|
-
for (const e of entries) {
|
|
19688
|
-
const date = e.ts.slice(0, 10);
|
|
19689
|
-
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19690
|
-
d.calls++;
|
|
19691
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19692
|
-
dailyMap.set(date, d);
|
|
19693
|
-
}
|
|
19694
|
-
}
|
|
19695
|
-
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19696
|
-
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19697
|
-
for (const e of entries) {
|
|
19698
|
-
topToolsMap.set(e.tool, (topToolsMap.get(e.tool) || 0) + 1);
|
|
19699
|
-
if (e.decision && !e.decision.startsWith("allow")) {
|
|
19700
|
-
topBlockedMap.set(e.tool, (topBlockedMap.get(e.tool) || 0) + 1);
|
|
19701
|
-
}
|
|
19702
|
-
}
|
|
19703
|
-
const topTools = [...topToolsMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19704
|
-
const topBlockedTools = [...topBlockedMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19705
|
-
const agentMap = /* @__PURE__ */ new Map();
|
|
19706
|
-
for (const e of entries) {
|
|
19707
|
-
const key = e.agent || "unknown";
|
|
19708
|
-
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19709
|
-
a.total++;
|
|
19710
|
-
if (e.decision && !e.decision.startsWith("allow")) a.blocked++;
|
|
19711
|
-
if (e.checkedBy?.includes("dlp")) a.dlp++;
|
|
19712
|
-
agentMap.set(key, a);
|
|
19713
|
-
}
|
|
19714
|
-
const byAgent = [...agentMap.values()].sort((a, b) => b.total - a.total);
|
|
19715
20078
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19716
|
-
return res.end(
|
|
19717
|
-
JSON.stringify({
|
|
19718
|
-
summary,
|
|
19719
|
-
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19720
|
-
topTools,
|
|
19721
|
-
topBlockedTools,
|
|
19722
|
-
byAgent
|
|
19723
|
-
})
|
|
19724
|
-
);
|
|
20079
|
+
return res.end(JSON.stringify(buildDaemonReport(allEntries, period, /* @__PURE__ */ new Date())));
|
|
19725
20080
|
} catch {
|
|
19726
20081
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
19727
20082
|
return res.end(JSON.stringify({ error: "Failed to parse report" }));
|
|
@@ -20024,6 +20379,23 @@ data: ${JSON.stringify(item.data)}
|
|
|
20024
20379
|
res.writeHead(404).end();
|
|
20025
20380
|
});
|
|
20026
20381
|
setDaemonServer(server);
|
|
20382
|
+
let bindAttempts = 0;
|
|
20383
|
+
const MAX_BIND_ATTEMPTS = 3;
|
|
20384
|
+
function retryListen() {
|
|
20385
|
+
if (++bindAttempts >= MAX_BIND_ATTEMPTS) {
|
|
20386
|
+
logDaemonStartup(
|
|
20387
|
+
"port-unavailable",
|
|
20388
|
+
`:${DAEMON_PORT} is held by something that is not a node9 daemon`
|
|
20389
|
+
);
|
|
20390
|
+
recordStartupState(
|
|
20391
|
+
"failed",
|
|
20392
|
+
"port-unavailable",
|
|
20393
|
+
`:${DAEMON_PORT} is held by another process that is not a node9 daemon \u2014 free the port, then: node9 daemon --background`
|
|
20394
|
+
);
|
|
20395
|
+
return process.exit(0);
|
|
20396
|
+
}
|
|
20397
|
+
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
20398
|
+
}
|
|
20027
20399
|
server.on("error", (e) => {
|
|
20028
20400
|
if (e.code === "EADDRINUSE") {
|
|
20029
20401
|
try {
|
|
@@ -20031,6 +20403,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
20031
20403
|
const { pid } = JSON.parse(import_fs40.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
20032
20404
|
process.kill(pid, 0);
|
|
20033
20405
|
logDaemonStartup("port-in-use", `another daemon (pid ${pid}) owns :${DAEMON_PORT}`);
|
|
20406
|
+
recordStartupState("ok-elsewhere");
|
|
20034
20407
|
return process.exit(0);
|
|
20035
20408
|
}
|
|
20036
20409
|
} catch {
|
|
@@ -20038,13 +20411,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
20038
20411
|
import_fs40.default.unlinkSync(DAEMON_PID_FILE);
|
|
20039
20412
|
} catch {
|
|
20040
20413
|
}
|
|
20041
|
-
|
|
20414
|
+
retryListen();
|
|
20042
20415
|
return;
|
|
20043
20416
|
}
|
|
20044
20417
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/settings`, {
|
|
20045
20418
|
signal: AbortSignal.timeout(1e3)
|
|
20046
20419
|
}).then((res) => {
|
|
20047
20420
|
if (res.ok) {
|
|
20421
|
+
let adopted = false;
|
|
20048
20422
|
try {
|
|
20049
20423
|
let orphanPid = null;
|
|
20050
20424
|
const ss = (0, import_child_process2.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
@@ -20072,19 +20446,38 @@ data: ${JSON.stringify(item.data)}
|
|
|
20072
20446
|
JSON.stringify({ pid: orphanPid, port: DAEMON_PORT, internalToken, autoStarted }),
|
|
20073
20447
|
{ mode: 384 }
|
|
20074
20448
|
);
|
|
20449
|
+
adopted = true;
|
|
20075
20450
|
}
|
|
20076
20451
|
} catch {
|
|
20077
20452
|
}
|
|
20453
|
+
if (adopted) {
|
|
20454
|
+
logDaemonStartup(
|
|
20455
|
+
"port-in-use-orphan",
|
|
20456
|
+
`adopted the daemon already on :${DAEMON_PORT}`
|
|
20457
|
+
);
|
|
20458
|
+
recordStartupState("ok-elsewhere");
|
|
20459
|
+
} else {
|
|
20460
|
+
logDaemonStartup(
|
|
20461
|
+
"orphan-unidentified",
|
|
20462
|
+
`healthy daemon on :${DAEMON_PORT} could not be identified \u2014 no pid file written`
|
|
20463
|
+
);
|
|
20464
|
+
recordStartupState(
|
|
20465
|
+
"failed",
|
|
20466
|
+
"orphan-unidentified",
|
|
20467
|
+
`a healthy daemon is running on :${DAEMON_PORT} but its process could not be identified, so node9 cannot track it \u2014 install \`ss\` or \`lsof\`, or restart it with: node9 daemon --background`
|
|
20468
|
+
);
|
|
20469
|
+
}
|
|
20078
20470
|
process.exit(0);
|
|
20079
20471
|
} else {
|
|
20080
|
-
|
|
20472
|
+
retryListen();
|
|
20081
20473
|
}
|
|
20082
20474
|
}).catch(() => {
|
|
20083
|
-
|
|
20475
|
+
retryListen();
|
|
20084
20476
|
});
|
|
20085
20477
|
return;
|
|
20086
20478
|
}
|
|
20087
20479
|
logDaemonStartup("bind-failed", e.message);
|
|
20480
|
+
recordStartupState("failed", "bind-failed", e.message);
|
|
20088
20481
|
console.error(import_chalk6.default.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
|
|
20089
20482
|
process.exit(1);
|
|
20090
20483
|
});
|
|
@@ -20102,6 +20495,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
20102
20495
|
{ mode: 384 }
|
|
20103
20496
|
);
|
|
20104
20497
|
console.error(import_chalk6.default.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE on 127.0.0.1:${DAEMON_PORT}`));
|
|
20498
|
+
logDaemonStartup("ok", `listening on ${DAEMON_HOST}:${DAEMON_PORT}`);
|
|
20499
|
+
recordStartupState("ok");
|
|
20105
20500
|
});
|
|
20106
20501
|
if (watchMode) {
|
|
20107
20502
|
console.error(import_chalk6.default.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
@@ -20127,6 +20522,7 @@ var init_server = __esm({
|
|
|
20127
20522
|
init_costSync();
|
|
20128
20523
|
init_sync();
|
|
20129
20524
|
init_audit_shipper();
|
|
20525
|
+
init_decision();
|
|
20130
20526
|
init_dlp_scanner();
|
|
20131
20527
|
init_mcp_reconciler();
|
|
20132
20528
|
init_hook_heal();
|
|
@@ -45329,19 +45725,19 @@ function getModelContextLimit(model) {
|
|
|
45329
45725
|
}
|
|
45330
45726
|
function readSessionUsage() {
|
|
45331
45727
|
const projectsDir = import_path67.default.join(import_os60.default.homedir(), ".claude", "projects");
|
|
45332
|
-
if (!
|
|
45728
|
+
if (!import_fs71.default.existsSync(projectsDir)) return null;
|
|
45333
45729
|
let latestFile = null;
|
|
45334
45730
|
let latestMtime = 0;
|
|
45335
45731
|
try {
|
|
45336
|
-
for (const dir of
|
|
45732
|
+
for (const dir of import_fs71.default.readdirSync(projectsDir)) {
|
|
45337
45733
|
const dirPath = import_path67.default.join(projectsDir, dir);
|
|
45338
45734
|
try {
|
|
45339
|
-
if (!
|
|
45340
|
-
for (const file of
|
|
45735
|
+
if (!import_fs71.default.statSync(dirPath).isDirectory()) continue;
|
|
45736
|
+
for (const file of import_fs71.default.readdirSync(dirPath)) {
|
|
45341
45737
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
45342
45738
|
const filePath = import_path67.default.join(dirPath, file);
|
|
45343
45739
|
try {
|
|
45344
|
-
const mtime =
|
|
45740
|
+
const mtime = import_fs71.default.statSync(filePath).mtimeMs;
|
|
45345
45741
|
if (mtime > latestMtime) {
|
|
45346
45742
|
latestMtime = mtime;
|
|
45347
45743
|
latestFile = filePath;
|
|
@@ -45356,7 +45752,7 @@ function readSessionUsage() {
|
|
|
45356
45752
|
}
|
|
45357
45753
|
if (!latestFile) return null;
|
|
45358
45754
|
try {
|
|
45359
|
-
const lines =
|
|
45755
|
+
const lines = import_fs71.default.readFileSync(latestFile, "utf-8").split("\n");
|
|
45360
45756
|
let lastModel = "";
|
|
45361
45757
|
let lastInput = 0;
|
|
45362
45758
|
let lastOutput = 0;
|
|
@@ -45456,9 +45852,9 @@ function renderPending(activity) {
|
|
|
45456
45852
|
}
|
|
45457
45853
|
async function ensureDaemon() {
|
|
45458
45854
|
let pidPort = null;
|
|
45459
|
-
if (
|
|
45855
|
+
if (import_fs71.default.existsSync(PID_FILE)) {
|
|
45460
45856
|
try {
|
|
45461
|
-
const { port } = JSON.parse(
|
|
45857
|
+
const { port } = JSON.parse(import_fs71.default.readFileSync(PID_FILE, "utf-8"));
|
|
45462
45858
|
pidPort = port;
|
|
45463
45859
|
} catch {
|
|
45464
45860
|
console.error(import_chalk40.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -45473,12 +45869,21 @@ async function ensureDaemon() {
|
|
|
45473
45869
|
} catch {
|
|
45474
45870
|
}
|
|
45475
45871
|
console.log(import_chalk40.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
45872
|
+
const startupFd = openStartupLogFd();
|
|
45873
|
+
recordStartupState("starting");
|
|
45476
45874
|
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
45477
45875
|
detached: true,
|
|
45478
|
-
stdio: "ignore",
|
|
45876
|
+
stdio: ["ignore", "ignore", startupFd ?? "ignore"],
|
|
45479
45877
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
45480
45878
|
});
|
|
45879
|
+
child.on("error", (err2) => recordStartupState("failed", "spawn-failed", err2.message));
|
|
45481
45880
|
child.unref();
|
|
45881
|
+
if (startupFd !== void 0) {
|
|
45882
|
+
try {
|
|
45883
|
+
import_fs71.default.closeSync(startupFd);
|
|
45884
|
+
} catch {
|
|
45885
|
+
}
|
|
45886
|
+
}
|
|
45482
45887
|
for (let i = 0; i < 20; i++) {
|
|
45483
45888
|
await new Promise((r) => setTimeout(r, 250));
|
|
45484
45889
|
try {
|
|
@@ -45616,7 +46021,7 @@ function buildRecoveryCardLines(req) {
|
|
|
45616
46021
|
function readApproversFromDisk() {
|
|
45617
46022
|
const configPath = import_path67.default.join(import_os60.default.homedir(), ".node9", "config.json");
|
|
45618
46023
|
try {
|
|
45619
|
-
const raw = JSON.parse(
|
|
46024
|
+
const raw = JSON.parse(import_fs71.default.readFileSync(configPath, "utf-8"));
|
|
45620
46025
|
const settings = raw.settings ?? {};
|
|
45621
46026
|
return settings.approvers ?? {};
|
|
45622
46027
|
} catch {
|
|
@@ -45634,13 +46039,13 @@ function approverStatusLine() {
|
|
|
45634
46039
|
function toggleApprover(channel) {
|
|
45635
46040
|
const configPath = import_path67.default.join(import_os60.default.homedir(), ".node9", "config.json");
|
|
45636
46041
|
try {
|
|
45637
|
-
const raw = JSON.parse(
|
|
46042
|
+
const raw = JSON.parse(import_fs71.default.readFileSync(configPath, "utf-8"));
|
|
45638
46043
|
const settings = raw.settings ?? {};
|
|
45639
46044
|
const approvers = settings.approvers ?? {};
|
|
45640
46045
|
approvers[channel] = approvers[channel] === false;
|
|
45641
46046
|
settings.approvers = approvers;
|
|
45642
46047
|
raw.settings = settings;
|
|
45643
|
-
|
|
46048
|
+
import_fs71.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
45644
46049
|
} catch (err2) {
|
|
45645
46050
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
45646
46051
|
`);
|
|
@@ -45812,7 +46217,7 @@ async function startTail(options = {}) {
|
|
|
45812
46217
|
}
|
|
45813
46218
|
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
|
|
45814
46219
|
try {
|
|
45815
|
-
|
|
46220
|
+
import_fs71.default.appendFileSync(
|
|
45816
46221
|
import_path67.default.join(import_os60.default.homedir(), ".node9", "hook-debug.log"),
|
|
45817
46222
|
`[tail] POST /decision failed: ${String(err2)}
|
|
45818
46223
|
`
|
|
@@ -45879,7 +46284,7 @@ async function startTail(options = {}) {
|
|
|
45879
46284
|
}
|
|
45880
46285
|
const auditLog = import_path67.default.join(import_os60.default.homedir(), ".node9", "audit.log");
|
|
45881
46286
|
try {
|
|
45882
|
-
const unackedDlp =
|
|
46287
|
+
const unackedDlp = import_fs71.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
45883
46288
|
if (unackedDlp > 0) {
|
|
45884
46289
|
console.log("");
|
|
45885
46290
|
console.log(
|
|
@@ -45919,7 +46324,7 @@ async function startTail(options = {}) {
|
|
|
45919
46324
|
if (stallWarned) return;
|
|
45920
46325
|
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
45921
46326
|
try {
|
|
45922
|
-
const auditMtime =
|
|
46327
|
+
const auditMtime = import_fs71.default.statSync(auditLog).mtimeMs;
|
|
45923
46328
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
45924
46329
|
console.log("");
|
|
45925
46330
|
console.log(
|
|
@@ -46104,17 +46509,18 @@ async function startTail(options = {}) {
|
|
|
46104
46509
|
process.exit(1);
|
|
46105
46510
|
});
|
|
46106
46511
|
}
|
|
46107
|
-
var import_http5, import_chalk40,
|
|
46512
|
+
var import_http5, import_chalk40, import_fs71, import_os60, import_path67, import_readline6, import_child_process14, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
46108
46513
|
var init_tail = __esm({
|
|
46109
46514
|
"src/tui/tail.ts"() {
|
|
46110
46515
|
"use strict";
|
|
46111
46516
|
import_http5 = __toESM(require("http"));
|
|
46112
46517
|
import_chalk40 = __toESM(require("chalk"));
|
|
46113
|
-
|
|
46518
|
+
import_fs71 = __toESM(require("fs"));
|
|
46114
46519
|
import_os60 = __toESM(require("os"));
|
|
46115
46520
|
import_path67 = __toESM(require("path"));
|
|
46116
46521
|
import_readline6 = __toESM(require("readline"));
|
|
46117
46522
|
import_child_process14 = require("child_process");
|
|
46523
|
+
init_startup_log();
|
|
46118
46524
|
init_daemon2();
|
|
46119
46525
|
init_daemon();
|
|
46120
46526
|
PID_FILE = import_path67.default.join(import_os60.default.homedir(), ".node9", "daemon.pid");
|
|
@@ -46239,9 +46645,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
46239
46645
|
return ` (${m}m left)`;
|
|
46240
46646
|
}
|
|
46241
46647
|
function safeReadJson(filePath) {
|
|
46242
|
-
if (!
|
|
46648
|
+
if (!import_fs72.default.existsSync(filePath)) return null;
|
|
46243
46649
|
try {
|
|
46244
|
-
return JSON.parse(
|
|
46650
|
+
return JSON.parse(import_fs72.default.readFileSync(filePath, "utf-8"));
|
|
46245
46651
|
} catch {
|
|
46246
46652
|
return null;
|
|
46247
46653
|
}
|
|
@@ -46262,10 +46668,10 @@ function countHooksInFile(filePath) {
|
|
|
46262
46668
|
return Object.keys(cfg.hooks).length;
|
|
46263
46669
|
}
|
|
46264
46670
|
function countRulesInDir(rulesDir) {
|
|
46265
|
-
if (!
|
|
46671
|
+
if (!import_fs72.default.existsSync(rulesDir)) return 0;
|
|
46266
46672
|
let count = 0;
|
|
46267
46673
|
try {
|
|
46268
|
-
for (const entry of
|
|
46674
|
+
for (const entry of import_fs72.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
46269
46675
|
if (entry.isDirectory()) {
|
|
46270
46676
|
count += countRulesInDir(import_path68.default.join(rulesDir, entry.name));
|
|
46271
46677
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -46291,7 +46697,7 @@ function countConfigs(cwd) {
|
|
|
46291
46697
|
let hooksCount = 0;
|
|
46292
46698
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
46293
46699
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
46294
|
-
if (
|
|
46700
|
+
if (import_fs72.default.existsSync(import_path68.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46295
46701
|
rulesCount += countRulesInDir(import_path68.default.join(claudeDir, "rules"));
|
|
46296
46702
|
const userSettings = import_path68.default.join(claudeDir, "settings.json");
|
|
46297
46703
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
@@ -46302,18 +46708,18 @@ function countConfigs(cwd) {
|
|
|
46302
46708
|
userMcpServers.delete(name);
|
|
46303
46709
|
}
|
|
46304
46710
|
if (cwd) {
|
|
46305
|
-
if (
|
|
46306
|
-
if (
|
|
46711
|
+
if (import_fs72.default.existsSync(import_path68.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
46712
|
+
if (import_fs72.default.existsSync(import_path68.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46307
46713
|
const projectClaudeDir = import_path68.default.join(cwd, ".claude");
|
|
46308
46714
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
46309
46715
|
if (!overlapsUserScope) {
|
|
46310
|
-
if (
|
|
46716
|
+
if (import_fs72.default.existsSync(import_path68.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46311
46717
|
rulesCount += countRulesInDir(import_path68.default.join(projectClaudeDir, "rules"));
|
|
46312
46718
|
const projSettings = import_path68.default.join(projectClaudeDir, "settings.json");
|
|
46313
46719
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
46314
46720
|
hooksCount += countHooksInFile(projSettings);
|
|
46315
46721
|
}
|
|
46316
|
-
if (
|
|
46722
|
+
if (import_fs72.default.existsSync(import_path68.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46317
46723
|
const localSettings = import_path68.default.join(projectClaudeDir, "settings.local.json");
|
|
46318
46724
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
46319
46725
|
hooksCount += countHooksInFile(localSettings);
|
|
@@ -46351,11 +46757,11 @@ function readActiveShieldsHud() {
|
|
|
46351
46757
|
}
|
|
46352
46758
|
try {
|
|
46353
46759
|
const shieldsPath = import_path68.default.join(import_os61.default.homedir(), ".node9", "shields.json");
|
|
46354
|
-
if (!
|
|
46760
|
+
if (!import_fs72.default.existsSync(shieldsPath)) {
|
|
46355
46761
|
shieldsCache = { value: [], ts: now };
|
|
46356
46762
|
return [];
|
|
46357
46763
|
}
|
|
46358
|
-
const parsed = JSON.parse(
|
|
46764
|
+
const parsed = JSON.parse(import_fs72.default.readFileSync(shieldsPath, "utf-8"));
|
|
46359
46765
|
if (!Array.isArray(parsed.active)) {
|
|
46360
46766
|
shieldsCache = { value: [], ts: now };
|
|
46361
46767
|
return [];
|
|
@@ -46457,17 +46863,17 @@ function renderContextLine(stdin) {
|
|
|
46457
46863
|
async function main() {
|
|
46458
46864
|
try {
|
|
46459
46865
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
46460
|
-
if (
|
|
46866
|
+
if (import_fs72.default.existsSync(import_path68.default.join(import_os61.default.homedir(), ".node9", "hud-debug"))) {
|
|
46461
46867
|
try {
|
|
46462
46868
|
const logPath = import_path68.default.join(import_os61.default.homedir(), ".node9", "hud-debug.log");
|
|
46463
46869
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
46464
46870
|
let size = 0;
|
|
46465
46871
|
try {
|
|
46466
|
-
size =
|
|
46872
|
+
size = import_fs72.default.statSync(logPath).size;
|
|
46467
46873
|
} catch {
|
|
46468
46874
|
}
|
|
46469
46875
|
if (size < MAX_LOG_SIZE) {
|
|
46470
|
-
|
|
46876
|
+
import_fs72.default.appendFileSync(
|
|
46471
46877
|
logPath,
|
|
46472
46878
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
46473
46879
|
);
|
|
@@ -46491,8 +46897,8 @@ async function main() {
|
|
|
46491
46897
|
import_path68.default.join(cwd, "node9.config.json"),
|
|
46492
46898
|
import_path68.default.join(import_os61.default.homedir(), ".node9", "config.json")
|
|
46493
46899
|
]) {
|
|
46494
|
-
if (!
|
|
46495
|
-
const cfg = JSON.parse(
|
|
46900
|
+
if (!import_fs72.default.existsSync(configPath)) continue;
|
|
46901
|
+
const cfg = JSON.parse(import_fs72.default.readFileSync(configPath, "utf-8"));
|
|
46496
46902
|
const hud = cfg.settings?.hud;
|
|
46497
46903
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
46498
46904
|
}
|
|
@@ -46510,11 +46916,11 @@ async function main() {
|
|
|
46510
46916
|
renderOffline();
|
|
46511
46917
|
}
|
|
46512
46918
|
}
|
|
46513
|
-
var
|
|
46919
|
+
var import_fs72, import_path68, import_os61, import_http6, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
46514
46920
|
var init_hud = __esm({
|
|
46515
46921
|
"src/cli/hud.ts"() {
|
|
46516
46922
|
"use strict";
|
|
46517
|
-
|
|
46923
|
+
import_fs72 = __toESM(require("fs"));
|
|
46518
46924
|
import_path68 = __toESM(require("path"));
|
|
46519
46925
|
import_os61 = __toESM(require("os"));
|
|
46520
46926
|
import_http6 = __toESM(require("http"));
|
|
@@ -46638,7 +47044,7 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
|
|
|
46638
47044
|
// src/cli.ts
|
|
46639
47045
|
init_daemon2();
|
|
46640
47046
|
var import_chalk41 = __toESM(require("chalk"));
|
|
46641
|
-
var
|
|
47047
|
+
var import_fs73 = __toESM(require("fs"));
|
|
46642
47048
|
var import_path69 = __toESM(require("path"));
|
|
46643
47049
|
var import_os62 = __toESM(require("os"));
|
|
46644
47050
|
var import_child_process15 = require("child_process");
|
|
@@ -46844,9 +47250,11 @@ function logAutostartSkipThrottled(reason) {
|
|
|
46844
47250
|
if (Date.now() - import_fs43.default.statSync(stamp).mtimeMs < SKIP_THROTTLE_MS) return;
|
|
46845
47251
|
} catch {
|
|
46846
47252
|
}
|
|
47253
|
+
const dir = import_path41.default.join(import_os39.default.homedir(), ".node9");
|
|
47254
|
+
if (!import_fs43.default.existsSync(dir)) import_fs43.default.mkdirSync(dir, { recursive: true });
|
|
46847
47255
|
import_fs43.default.writeFileSync(stamp, "", "utf-8");
|
|
46848
47256
|
import_fs43.default.appendFileSync(
|
|
46849
|
-
import_path41.default.join(
|
|
47257
|
+
import_path41.default.join(dir, "hook-debug.log"),
|
|
46850
47258
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-skip: ${reason}
|
|
46851
47259
|
`,
|
|
46852
47260
|
"utf-8"
|
|
@@ -46865,6 +47273,8 @@ async function autoStartDaemonAndWait() {
|
|
|
46865
47273
|
}
|
|
46866
47274
|
if (!resolvedArgv1.endsWith(".js")) return false;
|
|
46867
47275
|
const startupFd = openStartupLogFd();
|
|
47276
|
+
recordStartupState("starting");
|
|
47277
|
+
let spawned = false;
|
|
46868
47278
|
try {
|
|
46869
47279
|
const child = (0, import_child_process5.spawn)(process.execPath, [resolvedArgv1, "daemon"], {
|
|
46870
47280
|
detached: true,
|
|
@@ -46874,13 +47284,24 @@ async function autoStartDaemonAndWait() {
|
|
|
46874
47284
|
NODE9_AUTO_STARTED: "1"
|
|
46875
47285
|
}
|
|
46876
47286
|
});
|
|
47287
|
+
child.on("error", (err2) => {
|
|
47288
|
+
if (readStartupState()?.outcome !== "starting") return;
|
|
47289
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
47290
|
+
});
|
|
46877
47291
|
child.unref();
|
|
47292
|
+
spawned = true;
|
|
46878
47293
|
for (let i = 0; i < 20; i++) {
|
|
46879
47294
|
await new Promise((r) => setTimeout(r, 250));
|
|
46880
47295
|
if (!isDaemonRunning()) continue;
|
|
46881
47296
|
if (await isDaemonReachable()) return true;
|
|
46882
47297
|
}
|
|
46883
|
-
} catch {
|
|
47298
|
+
} catch (err2) {
|
|
47299
|
+
if (!spawned)
|
|
47300
|
+
recordStartupState(
|
|
47301
|
+
"failed",
|
|
47302
|
+
"spawn-failed",
|
|
47303
|
+
err2 instanceof Error ? err2.message : String(err2)
|
|
47304
|
+
);
|
|
46884
47305
|
} finally {
|
|
46885
47306
|
if (startupFd !== void 0) {
|
|
46886
47307
|
try {
|
|
@@ -47693,12 +48114,16 @@ RAW: ${raw}
|
|
|
47693
48114
|
delete safeEnv[key];
|
|
47694
48115
|
}
|
|
47695
48116
|
const startupFd = openStartupLogFd();
|
|
48117
|
+
recordStartupState("starting");
|
|
47696
48118
|
try {
|
|
47697
48119
|
const d = (0, import_child_process7.spawn)(process.execPath, [scriptPath, "daemon"], {
|
|
47698
48120
|
detached: true,
|
|
47699
48121
|
stdio: ["ignore", "ignore", startupFd ?? "ignore"],
|
|
47700
48122
|
env: { ...safeEnv, NODE9_AUTO_STARTED: "1" }
|
|
47701
48123
|
});
|
|
48124
|
+
d.on("error", (err2) => {
|
|
48125
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
48126
|
+
});
|
|
47702
48127
|
d.unref();
|
|
47703
48128
|
} finally {
|
|
47704
48129
|
if (startupFd !== void 0) {
|
|
@@ -47711,6 +48136,7 @@ RAW: ${raw}
|
|
|
47711
48136
|
} catch (spawnErr) {
|
|
47712
48137
|
const logPath = import_path45.default.join(import_os43.default.homedir(), ".node9", "hook-debug.log");
|
|
47713
48138
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
48139
|
+
recordStartupState("failed", "spawn-aborted", msg);
|
|
47714
48140
|
try {
|
|
47715
48141
|
import_fs47.default.appendFileSync(
|
|
47716
48142
|
logPath,
|
|
@@ -48875,6 +49301,7 @@ function agoLabel(iso, now = Date.now()) {
|
|
|
48875
49301
|
}
|
|
48876
49302
|
|
|
48877
49303
|
// src/cli/commands/doctor.ts
|
|
49304
|
+
init_startup_log();
|
|
48878
49305
|
function registerDoctorCommand(program2, version2) {
|
|
48879
49306
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
|
|
48880
49307
|
const homeDir2 = import_os46.default.homedir();
|
|
@@ -48986,6 +49413,15 @@ function registerDoctorCommand(program2, version2) {
|
|
|
48986
49413
|
"Daemon not running \u2014 terminal & native approvals unavailable",
|
|
48987
49414
|
"Run: node9 daemon --background"
|
|
48988
49415
|
);
|
|
49416
|
+
const cause = readStartupCause();
|
|
49417
|
+
if (cause) {
|
|
49418
|
+
const suffix = cause.detail ? ` \u2014 ${cause.detail}` : "";
|
|
49419
|
+
console.log(
|
|
49420
|
+
import_chalk11.default.gray(
|
|
49421
|
+
` ${cause.label} ${agoLabel(cause.at.toISOString())}: ${cause.kind}${suffix}`
|
|
49422
|
+
)
|
|
49423
|
+
);
|
|
49424
|
+
}
|
|
48989
49425
|
}
|
|
48990
49426
|
const autostart = autostartAdvice({
|
|
48991
49427
|
installed: isDaemonServiceInstalled(),
|
|
@@ -49063,6 +49499,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
49063
49499
|
var import_chalk12 = __toESM(require("chalk"));
|
|
49064
49500
|
var import_fs52 = __toESM(require("fs"));
|
|
49065
49501
|
var import_path50 = __toESM(require("path"));
|
|
49502
|
+
init_decision();
|
|
49066
49503
|
var import_os47 = __toESM(require("os"));
|
|
49067
49504
|
function formatRelativeTime(timestamp) {
|
|
49068
49505
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
@@ -49094,10 +49531,16 @@ function registerAuditCommand(program2) {
|
|
|
49094
49531
|
});
|
|
49095
49532
|
entries = entries.map((e) => ({
|
|
49096
49533
|
...e,
|
|
49097
|
-
|
|
49534
|
+
// classifyDecision is the ONE mapper (audit/decision.ts). The inline
|
|
49535
|
+
// `startsWith('allow') ? allow : deny` this replaces bucketed `dlp` and
|
|
49536
|
+
// `mcp-discovered` — findings, not verdicts — as DENY, inventing
|
|
49537
|
+
// refusals that never happened.
|
|
49538
|
+
// Pass the ROW, never a field pair — the attribution key differs by
|
|
49539
|
+
// producer (`checkedBy` from the gate, `source` from the hook/daemon).
|
|
49540
|
+
view: classifyDecision(e)
|
|
49098
49541
|
}));
|
|
49099
49542
|
if (options.tool) entries = entries.filter((e) => String(e.tool).includes(options.tool));
|
|
49100
|
-
if (options.deny) entries = entries.filter((e) => e.
|
|
49543
|
+
if (options.deny) entries = entries.filter((e) => e.view.outcome === "deny");
|
|
49101
49544
|
const limit = Math.max(1, parseInt(options.tail, 10) || 20);
|
|
49102
49545
|
entries = entries.slice(-limit);
|
|
49103
49546
|
if (options.json) {
|
|
@@ -49120,13 +49563,13 @@ function registerAuditCommand(program2) {
|
|
|
49120
49563
|
for (const e of entries) {
|
|
49121
49564
|
const time = formatRelativeTime(String(e.ts)).padEnd(12);
|
|
49122
49565
|
const tool = String(e.tool).slice(0, 17).padEnd(18);
|
|
49123
|
-
const result = e.
|
|
49566
|
+
const result = e.view.outcome === "allow" ? import_chalk12.default.green(e.view.label.padEnd(14)) : e.view.outcome === "deny" ? import_chalk12.default.red(e.view.label.padEnd(14)) : e.view.outcome === "observe" ? import_chalk12.default.yellow(e.view.label.padEnd(14)) : import_chalk12.default.gray(e.view.label.padEnd(14));
|
|
49124
49567
|
const checker = String(e.checkedBy || "unknown").slice(0, 14).padEnd(15);
|
|
49125
49568
|
const agent = String(e.agent || "unknown");
|
|
49126
49569
|
console.log(` ${time} ${tool} ${result} ${checker} ${agent}`);
|
|
49127
49570
|
}
|
|
49128
|
-
const allowed = entries.filter((e) => e.
|
|
49129
|
-
const denied = entries.filter((e) => e.
|
|
49571
|
+
const allowed = entries.filter((e) => e.view.outcome === "allow").length;
|
|
49572
|
+
const denied = entries.filter((e) => e.view.outcome === "deny").length;
|
|
49130
49573
|
console.log(import_chalk12.default.dim(" " + "\u2500".repeat(65)));
|
|
49131
49574
|
console.log(
|
|
49132
49575
|
` ${entries.length} entries | ${import_chalk12.default.green(allowed + " allowed")} | ${import_chalk12.default.red(denied + " denied")}
|
|
@@ -49145,6 +49588,7 @@ var import_path51 = __toESM(require("path"));
|
|
|
49145
49588
|
init_costSync();
|
|
49146
49589
|
init_litellm();
|
|
49147
49590
|
init_cost_codex();
|
|
49591
|
+
init_decision();
|
|
49148
49592
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
49149
49593
|
function buildTestTimestamps(allEntries) {
|
|
49150
49594
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -49235,8 +49679,15 @@ function parseAuditLog(logPath) {
|
|
|
49235
49679
|
}
|
|
49236
49680
|
});
|
|
49237
49681
|
}
|
|
49238
|
-
function
|
|
49239
|
-
|
|
49682
|
+
function viewOf(e) {
|
|
49683
|
+
const outcome = classifyDecision(e).outcome;
|
|
49684
|
+
const observed = outcome === "observe";
|
|
49685
|
+
return {
|
|
49686
|
+
observed,
|
|
49687
|
+
ran: outcome === "allow" || observed,
|
|
49688
|
+
blocked: outcome === "deny" || outcome === "unknown",
|
|
49689
|
+
info: outcome === "info"
|
|
49690
|
+
};
|
|
49240
49691
|
}
|
|
49241
49692
|
function isDlp(checkedBy) {
|
|
49242
49693
|
return !!checkedBy?.includes("dlp");
|
|
@@ -49649,12 +50100,12 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49649
50100
|
const priorStart = new Date(start.getTime() - periodMs);
|
|
49650
50101
|
const priorEntries = allEntries.filter((e) => {
|
|
49651
50102
|
if (e.source === "post-hook") return false;
|
|
50103
|
+
if (e.source === "response-dlp") return false;
|
|
50104
|
+
if (typeof e.decision !== "string") return false;
|
|
49652
50105
|
const ts = new Date(e.ts);
|
|
49653
50106
|
return ts >= priorStart && ts <= priorEnd;
|
|
49654
50107
|
});
|
|
49655
|
-
const priorBlocked = priorEntries.filter(
|
|
49656
|
-
(e) => typeof e.decision === "string" && !isAllow(e.decision)
|
|
49657
|
-
).length;
|
|
50108
|
+
const priorBlocked = priorEntries.filter((e) => viewOf(e).blocked).length;
|
|
49658
50109
|
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
49659
50110
|
const excludeTests = opts.excludeTests === true;
|
|
49660
50111
|
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
@@ -49697,15 +50148,17 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49697
50148
|
let dimInjectionBlocked = 0;
|
|
49698
50149
|
for (const e of entries) {
|
|
49699
50150
|
if (superseded.has(supersedeKey(e))) continue;
|
|
49700
|
-
const
|
|
50151
|
+
const view = viewOf(e);
|
|
49701
50152
|
const dateKey = e.ts.slice(0, 10);
|
|
49702
50153
|
const userInteracted = e.source === "daemon";
|
|
49703
|
-
if (
|
|
49704
|
-
if (
|
|
50154
|
+
if (view.observed) {
|
|
50155
|
+
if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
50156
|
+
} else if (view.info) {
|
|
50157
|
+
} else if (userInteracted) {
|
|
50158
|
+
if (view.ran) userApproved++;
|
|
49705
50159
|
else userDenied++;
|
|
49706
|
-
} else if (
|
|
50160
|
+
} else if (view.blocked) {
|
|
49707
50161
|
if (e.checkedBy === "timeout") timedOut++;
|
|
49708
|
-
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
49709
50162
|
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
49710
50163
|
else if (e.checkedBy === "local-decision") userDenied++;
|
|
49711
50164
|
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
@@ -49717,7 +50170,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49717
50170
|
if (cb.includes("would-block") && (cb.includes("pii") || cb.includes("dlp"))) {
|
|
49718
50171
|
dimDataObserved++;
|
|
49719
50172
|
}
|
|
49720
|
-
if (
|
|
50173
|
+
if (view.blocked && !userInteracted) {
|
|
49721
50174
|
switch (dimensionOfBlock(cb, e.ruleName ?? "")) {
|
|
49722
50175
|
case "network":
|
|
49723
50176
|
dimNetworkBlocked++;
|
|
@@ -49735,15 +50188,15 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49735
50188
|
}
|
|
49736
50189
|
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
49737
50190
|
t.calls++;
|
|
49738
|
-
if (
|
|
50191
|
+
if (view.blocked) t.blocked++;
|
|
49739
50192
|
toolMap.set(e.tool, t);
|
|
49740
|
-
if (
|
|
50193
|
+
if (view.blocked) {
|
|
49741
50194
|
const key = e.checkedBy ?? (e.source === "daemon" ? "local-decision" : null);
|
|
49742
50195
|
if (key) {
|
|
49743
50196
|
blockMap.set(key, (blockMap.get(key) ?? 0) + 1);
|
|
49744
50197
|
}
|
|
49745
50198
|
}
|
|
49746
|
-
if (
|
|
50199
|
+
if ((view.blocked || view.observed) && e.ruleName) {
|
|
49747
50200
|
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
49748
50201
|
}
|
|
49749
50202
|
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
@@ -49752,7 +50205,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49752
50205
|
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
49753
50206
|
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
49754
50207
|
d.calls++;
|
|
49755
|
-
if (
|
|
50208
|
+
if (view.blocked) d.blocked++;
|
|
49756
50209
|
dailyMap.set(dateKey, d);
|
|
49757
50210
|
}
|
|
49758
50211
|
for (const e of allEntries) {
|
|
@@ -50306,6 +50759,8 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
50306
50759
|
// src/cli/commands/daemon-cmd.ts
|
|
50307
50760
|
var import_chalk14 = __toESM(require("chalk"));
|
|
50308
50761
|
var import_child_process9 = require("child_process");
|
|
50762
|
+
var import_fs54 = __toESM(require("fs"));
|
|
50763
|
+
init_startup_log();
|
|
50309
50764
|
init_daemon2();
|
|
50310
50765
|
var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
|
|
50311
50766
|
function registerDaemonCommand(program2) {
|
|
@@ -50344,14 +50799,27 @@ function registerDaemonCommand(program2) {
|
|
|
50344
50799
|
if (cmd === "restart") {
|
|
50345
50800
|
stopDaemon();
|
|
50346
50801
|
await new Promise((r) => setTimeout(r, 500));
|
|
50802
|
+
const restartFd = openStartupLogFd();
|
|
50803
|
+
recordStartupState("starting");
|
|
50347
50804
|
const child = (0, import_child_process9.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
50348
50805
|
detached: true,
|
|
50349
|
-
stdio: "ignore",
|
|
50806
|
+
stdio: ["ignore", "ignore", restartFd ?? "ignore"],
|
|
50350
50807
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
50351
50808
|
});
|
|
50809
|
+
child.on(
|
|
50810
|
+
"error",
|
|
50811
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50812
|
+
);
|
|
50352
50813
|
child.unref();
|
|
50814
|
+
if (restartFd !== void 0) {
|
|
50815
|
+
try {
|
|
50816
|
+
import_fs54.default.closeSync(restartFd);
|
|
50817
|
+
} catch {
|
|
50818
|
+
}
|
|
50819
|
+
}
|
|
50353
50820
|
if (child.pid) {
|
|
50354
|
-
console.log(import_chalk14.default.green(`\u2713 Daemon
|
|
50821
|
+
console.log(import_chalk14.default.green(`\u2713 Daemon relaunching (PID ${child.pid})`));
|
|
50822
|
+
console.log(import_chalk14.default.gray(" Confirm with: node9 status"));
|
|
50355
50823
|
} else {
|
|
50356
50824
|
console.error(import_chalk14.default.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
|
|
50357
50825
|
process.exit(1);
|
|
@@ -50374,13 +50842,33 @@ function registerDaemonCommand(program2) {
|
|
|
50374
50842
|
return;
|
|
50375
50843
|
}
|
|
50376
50844
|
if (options.background) {
|
|
50377
|
-
const
|
|
50378
|
-
|
|
50379
|
-
|
|
50380
|
-
|
|
50381
|
-
|
|
50382
|
-
|
|
50383
|
-
|
|
50845
|
+
const startupFd = openStartupLogFd();
|
|
50846
|
+
try {
|
|
50847
|
+
recordStartupState("starting");
|
|
50848
|
+
const child = (0, import_child_process9.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
50849
|
+
detached: true,
|
|
50850
|
+
// Capture the child's stderr: a module-load crash prints its stack there
|
|
50851
|
+
// and dies before it can record anything itself.
|
|
50852
|
+
stdio: ["ignore", "ignore", startupFd ?? "ignore"]
|
|
50853
|
+
});
|
|
50854
|
+
child.on(
|
|
50855
|
+
"error",
|
|
50856
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50857
|
+
);
|
|
50858
|
+
child.unref();
|
|
50859
|
+
console.log(
|
|
50860
|
+
import_chalk14.default.green(`
|
|
50861
|
+
\u{1F6E1}\uFE0F Node9 daemon launching in background (PID ${child.pid})`)
|
|
50862
|
+
);
|
|
50863
|
+
console.log(import_chalk14.default.gray(" Confirm with: node9 status"));
|
|
50864
|
+
} finally {
|
|
50865
|
+
if (startupFd !== void 0) {
|
|
50866
|
+
try {
|
|
50867
|
+
import_fs54.default.closeSync(startupFd);
|
|
50868
|
+
} catch {
|
|
50869
|
+
}
|
|
50870
|
+
}
|
|
50871
|
+
}
|
|
50384
50872
|
process.exit(0);
|
|
50385
50873
|
}
|
|
50386
50874
|
startDaemon();
|
|
@@ -50390,7 +50878,7 @@ function registerDaemonCommand(program2) {
|
|
|
50390
50878
|
|
|
50391
50879
|
// src/cli/commands/status.ts
|
|
50392
50880
|
var import_chalk15 = __toESM(require("chalk"));
|
|
50393
|
-
var
|
|
50881
|
+
var import_fs55 = __toESM(require("fs"));
|
|
50394
50882
|
var import_path52 = __toESM(require("path"));
|
|
50395
50883
|
var import_os49 = __toESM(require("os"));
|
|
50396
50884
|
init_core();
|
|
@@ -50473,10 +50961,10 @@ function registerStatusCommand(program2) {
|
|
|
50473
50961
|
const projectConfig = import_path52.default.join(process.cwd(), "node9.config.json");
|
|
50474
50962
|
const globalConfig = import_path52.default.join(import_os49.default.homedir(), ".node9", "config.json");
|
|
50475
50963
|
console.log(
|
|
50476
|
-
` Local: ${
|
|
50964
|
+
` Local: ${import_fs55.default.existsSync(projectConfig) ? import_chalk15.default.green("Active (node9.config.json)") : import_chalk15.default.gray("Not present")}`
|
|
50477
50965
|
);
|
|
50478
50966
|
console.log(
|
|
50479
|
-
` Global: ${
|
|
50967
|
+
` Global: ${import_fs55.default.existsSync(globalConfig) ? import_chalk15.default.green("Active (~/.node9/config.json)") : import_chalk15.default.gray("Not present")}`
|
|
50480
50968
|
);
|
|
50481
50969
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
50482
50970
|
console.log(
|
|
@@ -50518,7 +51006,7 @@ function registerStatusCommand(program2) {
|
|
|
50518
51006
|
|
|
50519
51007
|
// src/cli/commands/init.ts
|
|
50520
51008
|
var import_chalk16 = __toESM(require("chalk"));
|
|
50521
|
-
var
|
|
51009
|
+
var import_fs56 = __toESM(require("fs"));
|
|
50522
51010
|
var import_path53 = __toESM(require("path"));
|
|
50523
51011
|
var import_os50 = __toESM(require("os"));
|
|
50524
51012
|
var import_https6 = __toESM(require("https"));
|
|
@@ -50612,15 +51100,15 @@ function registerInitCommand(program2) {
|
|
|
50612
51100
|
console.log("");
|
|
50613
51101
|
}
|
|
50614
51102
|
const configPath = import_path53.default.join(import_os50.default.homedir(), ".node9", "config.json");
|
|
50615
|
-
const isFirstInstall = !
|
|
50616
|
-
if (
|
|
51103
|
+
const isFirstInstall = !import_fs56.default.existsSync(configPath);
|
|
51104
|
+
if (import_fs56.default.existsSync(configPath) && !options.force) {
|
|
50617
51105
|
try {
|
|
50618
|
-
const existing = JSON.parse(
|
|
51106
|
+
const existing = JSON.parse(import_fs56.default.readFileSync(configPath, "utf-8"));
|
|
50619
51107
|
const settings = existing.settings ?? {};
|
|
50620
51108
|
if (settings.mode !== chosenMode) {
|
|
50621
51109
|
settings.mode = chosenMode;
|
|
50622
51110
|
existing.settings = settings;
|
|
50623
|
-
|
|
51111
|
+
import_fs56.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
50624
51112
|
console.log(import_chalk16.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
50625
51113
|
} else {
|
|
50626
51114
|
console.log(import_chalk16.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -50634,8 +51122,8 @@ function registerInitCommand(program2) {
|
|
|
50634
51122
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
50635
51123
|
};
|
|
50636
51124
|
const dir = import_path53.default.dirname(configPath);
|
|
50637
|
-
if (!
|
|
50638
|
-
|
|
51125
|
+
if (!import_fs56.default.existsSync(dir)) import_fs56.default.mkdirSync(dir, { recursive: true });
|
|
51126
|
+
import_fs56.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
50639
51127
|
console.log(import_chalk16.default.green(`\u2705 Config created: ${configPath}`));
|
|
50640
51128
|
console.log(import_chalk16.default.gray(` Mode: ${chosenMode}`));
|
|
50641
51129
|
}
|
|
@@ -50735,14 +51223,14 @@ function registerInitCommand(program2) {
|
|
|
50735
51223
|
|
|
50736
51224
|
// src/cli/commands/heal.ts
|
|
50737
51225
|
var import_chalk17 = __toESM(require("chalk"));
|
|
50738
|
-
var
|
|
51226
|
+
var import_fs57 = __toESM(require("fs"));
|
|
50739
51227
|
init_agent_wiring();
|
|
50740
51228
|
init_setup();
|
|
50741
51229
|
init_hook_baseline();
|
|
50742
51230
|
var hasHookSurface = (a) => a.hooks.length > 0;
|
|
50743
51231
|
function backupForHeal(file) {
|
|
50744
51232
|
try {
|
|
50745
|
-
if (file &&
|
|
51233
|
+
if (file && import_fs57.default.existsSync(file)) import_fs57.default.copyFileSync(file, `${file}.node9-heal-bak`);
|
|
50746
51234
|
} catch {
|
|
50747
51235
|
}
|
|
50748
51236
|
}
|
|
@@ -51702,16 +52190,17 @@ function registerMcpGatewayCommand(program2) {
|
|
|
51702
52190
|
|
|
51703
52191
|
// src/mcp-server/index.ts
|
|
51704
52192
|
var import_readline5 = __toESM(require("readline"));
|
|
51705
|
-
var
|
|
52193
|
+
var import_fs59 = __toESM(require("fs"));
|
|
51706
52194
|
var import_os52 = __toESM(require("os"));
|
|
51707
52195
|
var import_path56 = __toESM(require("path"));
|
|
51708
52196
|
var import_child_process11 = require("child_process");
|
|
52197
|
+
init_decision();
|
|
51709
52198
|
init_core();
|
|
51710
52199
|
init_daemon();
|
|
51711
52200
|
init_shields();
|
|
51712
52201
|
|
|
51713
52202
|
// src/auth/egress-config.ts
|
|
51714
|
-
var
|
|
52203
|
+
var import_fs58 = __toESM(require("fs"));
|
|
51715
52204
|
var import_os51 = __toESM(require("os"));
|
|
51716
52205
|
var import_path55 = __toESM(require("path"));
|
|
51717
52206
|
var DEFAULT_EGRESS = {
|
|
@@ -51727,7 +52216,7 @@ function egressConfigPath() {
|
|
|
51727
52216
|
function readEgressRawConfig() {
|
|
51728
52217
|
let text;
|
|
51729
52218
|
try {
|
|
51730
|
-
text =
|
|
52219
|
+
text = import_fs58.default.readFileSync(egressConfigPath(), "utf8");
|
|
51731
52220
|
} catch (err2) {
|
|
51732
52221
|
if (err2.code === "ENOENT") return {};
|
|
51733
52222
|
throw err2;
|
|
@@ -51742,8 +52231,8 @@ function readEgressRawConfig() {
|
|
|
51742
52231
|
}
|
|
51743
52232
|
function writeEgressRawConfig(config) {
|
|
51744
52233
|
const p = egressConfigPath();
|
|
51745
|
-
|
|
51746
|
-
|
|
52234
|
+
import_fs58.default.mkdirSync(import_path55.default.dirname(p), { recursive: true });
|
|
52235
|
+
import_fs58.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
51747
52236
|
}
|
|
51748
52237
|
function applyEgress(config, change) {
|
|
51749
52238
|
const policy = config.policy = config.policy ?? {};
|
|
@@ -51938,7 +52427,7 @@ var TOOLS = [
|
|
|
51938
52427
|
},
|
|
51939
52428
|
{
|
|
51940
52429
|
name: "node9_audit_get",
|
|
51941
|
-
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, tool name,
|
|
52430
|
+
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, outcome, tool name, the command, and the rule that fired. Outcomes use the same words as the dashboard: Auto-allowed, Approved, Ran, Blocked, Denied (a human refused), Timed out (nobody answered), Would block (shadow mode let it through), Finding, Info. Use this to review what AI actions were taken, especially refused ones.",
|
|
51942
52431
|
inputSchema: {
|
|
51943
52432
|
type: "object",
|
|
51944
52433
|
properties: {
|
|
@@ -51948,8 +52437,8 @@ var TOOLS = [
|
|
|
51948
52437
|
},
|
|
51949
52438
|
filter: {
|
|
51950
52439
|
type: "string",
|
|
51951
|
-
enum: ["all", "
|
|
51952
|
-
description: 'Filter by
|
|
52440
|
+
enum: ["all", "allow", "deny", "observe", "info", "block"],
|
|
52441
|
+
description: 'Filter by outcome. "deny" covers every refusal (rule block, human denial, timeout); "observe" is shadow-mode would-blocks; "block" is an alias for "deny". Omit or use "all" for every entry.'
|
|
51953
52442
|
}
|
|
51954
52443
|
},
|
|
51955
52444
|
required: []
|
|
@@ -52131,10 +52620,10 @@ function handleStatus() {
|
|
|
52131
52620
|
const projectConfig = import_path56.default.join(process.cwd(), "node9.config.json");
|
|
52132
52621
|
const globalConfig = import_path56.default.join(import_os52.default.homedir(), ".node9", "config.json");
|
|
52133
52622
|
lines.push(
|
|
52134
|
-
`Project config (node9.config.json): ${
|
|
52623
|
+
`Project config (node9.config.json): ${import_fs59.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
52135
52624
|
);
|
|
52136
52625
|
lines.push(
|
|
52137
|
-
`Global config (~/.node9/config.json): ${
|
|
52626
|
+
`Global config (~/.node9/config.json): ${import_fs59.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
52138
52627
|
);
|
|
52139
52628
|
return lines.join("\n");
|
|
52140
52629
|
}
|
|
@@ -52244,8 +52733,8 @@ var GLOBAL_CONFIG_PATH = import_path56.default.join(import_os52.default.homedir(
|
|
|
52244
52733
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
52245
52734
|
function readGlobalConfigRaw() {
|
|
52246
52735
|
try {
|
|
52247
|
-
if (
|
|
52248
|
-
return JSON.parse(
|
|
52736
|
+
if (import_fs59.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
52737
|
+
return JSON.parse(import_fs59.default.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
52249
52738
|
}
|
|
52250
52739
|
} catch {
|
|
52251
52740
|
}
|
|
@@ -52253,8 +52742,8 @@ function readGlobalConfigRaw() {
|
|
|
52253
52742
|
}
|
|
52254
52743
|
function writeGlobalConfigRaw(data) {
|
|
52255
52744
|
const dir = import_path56.default.dirname(GLOBAL_CONFIG_PATH);
|
|
52256
|
-
if (!
|
|
52257
|
-
|
|
52745
|
+
if (!import_fs59.default.existsSync(dir)) import_fs59.default.mkdirSync(dir, { recursive: true });
|
|
52746
|
+
import_fs59.default.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
|
|
52258
52747
|
}
|
|
52259
52748
|
function handleApproverList() {
|
|
52260
52749
|
const config = getConfig();
|
|
@@ -52299,35 +52788,37 @@ function handleAuditGet(args) {
|
|
|
52299
52788
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
52300
52789
|
const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
|
|
52301
52790
|
const auditPath = import_path56.default.join(import_os52.default.homedir(), ".node9", "audit.log");
|
|
52302
|
-
if (!
|
|
52303
|
-
const rawLines =
|
|
52791
|
+
if (!import_fs59.default.existsSync(auditPath)) return "No audit log found.";
|
|
52792
|
+
const rawLines = import_fs59.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
52793
|
+
const wanted = filter === "block" ? "deny" : filter;
|
|
52304
52794
|
const parsed = [];
|
|
52305
52795
|
for (const line of rawLines) {
|
|
52306
52796
|
try {
|
|
52307
52797
|
const e = JSON.parse(line);
|
|
52308
|
-
const
|
|
52309
|
-
if (
|
|
52798
|
+
const view = classifyDecision(e);
|
|
52799
|
+
if (wanted && view.outcome !== wanted) continue;
|
|
52310
52800
|
const argsObj = e.args;
|
|
52311
52801
|
let detail = "";
|
|
52312
52802
|
if (argsObj) {
|
|
52313
52803
|
const cmd = argsObj.command ?? argsObj.file_path ?? argsObj.path ?? argsObj.sql;
|
|
52314
|
-
if (typeof cmd === "string" && cmd)
|
|
52315
|
-
detail = cmd.length > 80 ? cmd.slice(0, 80) + "\u2026" : cmd;
|
|
52316
|
-
}
|
|
52804
|
+
if (typeof cmd === "string" && cmd) detail = cmd;
|
|
52317
52805
|
}
|
|
52318
|
-
|
|
52806
|
+
if (!detail && typeof e.argsPreview === "string") detail = e.argsPreview;
|
|
52807
|
+
detail = detail.replace(/\s+/g, " ").trim();
|
|
52808
|
+
if (detail.length > 80) detail = detail.slice(0, 80) + "\u2026";
|
|
52809
|
+
const why = typeof e.ruleName === "string" && e.ruleName ? ` (${e.ruleName})` : "";
|
|
52319
52810
|
const toolPad = String(e.tool ?? "").padEnd(20);
|
|
52320
|
-
const line2 = `${e.ts} ${
|
|
52321
|
-
parsed.push({ raw: line,
|
|
52811
|
+
const line2 = `${e.ts} ${decisionTag(view)} ${toolPad} ${detail}${why}`;
|
|
52812
|
+
parsed.push({ raw: line, outcome: view.outcome, formatted: line2 });
|
|
52322
52813
|
} catch {
|
|
52323
|
-
parsed.push({ raw: line,
|
|
52814
|
+
parsed.push({ raw: line, outcome: "unknown", formatted: `[? unparseable] ${line}` });
|
|
52324
52815
|
}
|
|
52325
52816
|
}
|
|
52326
52817
|
const recent = parsed.slice(-limit);
|
|
52327
52818
|
if (recent.length === 0) {
|
|
52328
|
-
return filter ? `No ${
|
|
52819
|
+
return filter ? `No ${wanted} entries found in audit log.` : "Audit log is empty.";
|
|
52329
52820
|
}
|
|
52330
|
-
const header = filter ? `Last ${recent.length} ${
|
|
52821
|
+
const header = filter ? `Last ${recent.length} ${String(wanted).toUpperCase()} entries:` : `Last ${recent.length} audit entries:`;
|
|
52331
52822
|
return `${header}
|
|
52332
52823
|
|
|
52333
52824
|
${recent.map((e) => e.formatted).join("\n")}`;
|
|
@@ -52674,7 +53165,7 @@ function registerTrustCommand(program2) {
|
|
|
52674
53165
|
// src/cli/commands/mcp-pin.ts
|
|
52675
53166
|
var import_chalk24 = __toESM(require("chalk"));
|
|
52676
53167
|
init_mcp_pin();
|
|
52677
|
-
var
|
|
53168
|
+
var import_fs60 = __toESM(require("fs"));
|
|
52678
53169
|
|
|
52679
53170
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
52680
53171
|
var import_chalk23 = __toESM(require("chalk"));
|
|
@@ -52870,6 +53361,7 @@ Restart ${[...agents].join(", ")}`) + import_chalk23.default.gray(" to activate.
|
|
|
52870
53361
|
}
|
|
52871
53362
|
|
|
52872
53363
|
// src/cli/commands/mcp-pin.ts
|
|
53364
|
+
init_mcp_wrap();
|
|
52873
53365
|
function registerMcpPinCommand(program2) {
|
|
52874
53366
|
const pinCmd = program2.command("mcp").description("Manage MCP servers \u2014 governance (gateway) + tool-definition pinning");
|
|
52875
53367
|
registerMcpGatewayCommand2(pinCmd);
|
|
@@ -52881,7 +53373,7 @@ function registerMcpPinCommand(program2) {
|
|
|
52881
53373
|
let repoCorrupt = false;
|
|
52882
53374
|
if (found.source === "repo") {
|
|
52883
53375
|
try {
|
|
52884
|
-
const raw =
|
|
53376
|
+
const raw = import_fs60.default.readFileSync(found.path, "utf-8");
|
|
52885
53377
|
const parsed = JSON.parse(raw);
|
|
52886
53378
|
repoEntries = parsed.servers ?? {};
|
|
52887
53379
|
} catch {
|
|
@@ -52995,6 +53487,88 @@ function registerMcpPinCommand(program2) {
|
|
|
52995
53487
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
52996
53488
|
console.log(import_chalk24.default.gray(" Next connection to each server will re-pin.\n"));
|
|
52997
53489
|
});
|
|
53490
|
+
pinCmd.command("forget [serverKey]").option("--stale", "Remove all stale (orphaned) servers at once").description("Remove a server pin that is no longer configured in any agent").action((serverKey, opts) => {
|
|
53491
|
+
if (opts.stale) {
|
|
53492
|
+
forgetAllStale();
|
|
53493
|
+
return;
|
|
53494
|
+
}
|
|
53495
|
+
if (!serverKey) {
|
|
53496
|
+
console.error(
|
|
53497
|
+
import_chalk24.default.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
53498
|
+
);
|
|
53499
|
+
process.exit(1);
|
|
53500
|
+
}
|
|
53501
|
+
let pins;
|
|
53502
|
+
try {
|
|
53503
|
+
pins = readMcpPins();
|
|
53504
|
+
} catch {
|
|
53505
|
+
console.error(import_chalk24.default.red("\n\u274C Pin file is corrupt."));
|
|
53506
|
+
console.error(import_chalk24.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
53507
|
+
process.exit(1);
|
|
53508
|
+
}
|
|
53509
|
+
if (!pins.servers[serverKey]) {
|
|
53510
|
+
console.error(import_chalk24.default.red(`
|
|
53511
|
+
\u274C No pin found for server key "${serverKey}"
|
|
53512
|
+
`));
|
|
53513
|
+
console.error(`Run ${import_chalk24.default.cyan("node9 mcp pin list")} to see pinned servers.
|
|
53514
|
+
`);
|
|
53515
|
+
process.exit(1);
|
|
53516
|
+
}
|
|
53517
|
+
const inv = inventoryMcp();
|
|
53518
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53519
|
+
if (liveKeys.has(serverKey)) {
|
|
53520
|
+
const agent = "an agent config";
|
|
53521
|
+
console.error(import_chalk24.default.red(`
|
|
53522
|
+
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
53523
|
+
console.error(
|
|
53524
|
+
import_chalk24.default.yellow(
|
|
53525
|
+
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
53526
|
+
`
|
|
53527
|
+
)
|
|
53528
|
+
);
|
|
53529
|
+
process.exit(1);
|
|
53530
|
+
}
|
|
53531
|
+
const label2 = pins.servers[serverKey].label;
|
|
53532
|
+
removePin(serverKey);
|
|
53533
|
+
console.log(import_chalk24.default.green(`
|
|
53534
|
+
\u2713 Forgot server ${import_chalk24.default.cyan(serverKey)}`));
|
|
53535
|
+
console.log(import_chalk24.default.gray(` Was: ${label2}`));
|
|
53536
|
+
console.log(import_chalk24.default.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
53537
|
+
});
|
|
53538
|
+
}
|
|
53539
|
+
function forgetAllStale() {
|
|
53540
|
+
let pins;
|
|
53541
|
+
try {
|
|
53542
|
+
pins = readMcpPins();
|
|
53543
|
+
} catch {
|
|
53544
|
+
console.error(import_chalk24.default.red("\n\u274C Pin file is corrupt."));
|
|
53545
|
+
console.error(import_chalk24.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
53546
|
+
process.exit(1);
|
|
53547
|
+
}
|
|
53548
|
+
const inv = inventoryMcp();
|
|
53549
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53550
|
+
if (liveKeys.size === 0) {
|
|
53551
|
+
console.error(import_chalk24.default.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
53552
|
+
console.error(
|
|
53553
|
+
import_chalk24.default.yellow(
|
|
53554
|
+
" This usually means an agent config is missing or unreadable, not that\n every server is gone. Check your agent configs, or remove one at a\n time: node9 mcp forget <key>\n"
|
|
53555
|
+
)
|
|
53556
|
+
);
|
|
53557
|
+
process.exit(1);
|
|
53558
|
+
}
|
|
53559
|
+
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
53560
|
+
if (stale.length === 0) {
|
|
53561
|
+
console.log(import_chalk24.default.gray("\nNo stale servers to remove.\n"));
|
|
53562
|
+
return;
|
|
53563
|
+
}
|
|
53564
|
+
for (const [sk, pin] of stale) {
|
|
53565
|
+
delete pins.servers[sk];
|
|
53566
|
+
console.log(import_chalk24.default.green(` \u2713 ${import_chalk24.default.cyan(sk)} ${import_chalk24.default.gray(pin.label)}`));
|
|
53567
|
+
}
|
|
53568
|
+
writeMcpPins(pins);
|
|
53569
|
+
console.log(import_chalk24.default.green(`
|
|
53570
|
+
Removed ${stale.length} stale server(s).
|
|
53571
|
+
`));
|
|
52998
53572
|
}
|
|
52999
53573
|
|
|
53000
53574
|
// src/cli/commands/sync.ts
|
|
@@ -53239,10 +53813,16 @@ var LABEL_WIDTH = 14;
|
|
|
53239
53813
|
function label(category) {
|
|
53240
53814
|
return import_chalk27.default.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
53241
53815
|
}
|
|
53242
|
-
function
|
|
53816
|
+
function guardedWhat(f) {
|
|
53817
|
+
if (f.detail.length === 0) return "this";
|
|
53818
|
+
const reads = f.coverageProbe?.kind === "fileRead" ? "reads of " : "";
|
|
53819
|
+
const more = f.detail.length > 1 ? ` and ${f.detail.length - 1} more` : "";
|
|
53820
|
+
return `${reads}${f.detail[0]}${more}`;
|
|
53821
|
+
}
|
|
53822
|
+
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
53243
53823
|
const lines = [];
|
|
53244
53824
|
const wt = showWeight && f.scoreWeight ? import_chalk27.default.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
53245
|
-
lines.push(` ${ICON[f.severity]} ${label(
|
|
53825
|
+
lines.push(` ${ICON[f.severity]} ${label(displayLabel)}${wt}${f.title}`);
|
|
53246
53826
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
53247
53827
|
const width = 80 - indent.length;
|
|
53248
53828
|
for (const s of [f.what, f.why, f.who]) {
|
|
@@ -53279,11 +53859,11 @@ function renderPosture(result) {
|
|
|
53279
53859
|
);
|
|
53280
53860
|
const headroom = openHeadroom(result.findings);
|
|
53281
53861
|
if (headroom > 0) {
|
|
53282
|
-
|
|
53283
|
-
"
|
|
53284
|
-
|
|
53285
|
-
|
|
53286
|
-
);
|
|
53862
|
+
const openExposures = result.findings.filter(
|
|
53863
|
+
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
53864
|
+
).length;
|
|
53865
|
+
const note = openExposures > 0 ? `Of the gap to 100: ${headroom} pts is optional hardening you can choose to turn on (the \u{1F512} tier below, each at some cost to flexibility); the rest is the open findings \u2014 the flagged rows below. Fix those first.` : `${headroom} pts of headroom \u2014 optional hardening you can choose to turn on (the \u{1F512} tier below), each at some cost to flexibility.`;
|
|
53866
|
+
for (const l of wrap(note, 76)) lines.push(" " + import_chalk27.default.gray(l));
|
|
53287
53867
|
}
|
|
53288
53868
|
lines.push("");
|
|
53289
53869
|
if (result.headline) {
|
|
@@ -53296,13 +53876,18 @@ function renderPosture(result) {
|
|
|
53296
53876
|
}
|
|
53297
53877
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
53298
53878
|
const open = result.findings.filter((f) => f.coverage?.state !== "covered");
|
|
53879
|
+
const collision = new Set(
|
|
53880
|
+
covered.map((f) => f.category).filter((c) => open.some((o) => o.category === c))
|
|
53881
|
+
);
|
|
53882
|
+
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
53299
53883
|
if (covered.length > 0) {
|
|
53300
53884
|
lines.push(" " + import_chalk27.default.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
53301
53885
|
for (const f of covered) {
|
|
53302
53886
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
53303
53887
|
const via = f.coverage?.via ?? "node9";
|
|
53888
|
+
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
53304
53889
|
lines.push(
|
|
53305
|
-
` ${import_chalk27.default.green("\u2705")} ${label(
|
|
53890
|
+
` ${import_chalk27.default.green("\u2705")} ${label(lbl)}${import_chalk27.default.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
53306
53891
|
);
|
|
53307
53892
|
}
|
|
53308
53893
|
lines.push("");
|
|
@@ -53312,17 +53897,17 @@ function renderPosture(result) {
|
|
|
53312
53897
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
53313
53898
|
if (node9Open.length > 0) {
|
|
53314
53899
|
lines.push(" " + import_chalk27.default.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
53315
|
-
for (const f of node9Open) lines.push(...renderFinding(f, true));
|
|
53900
|
+
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53316
53901
|
}
|
|
53317
53902
|
if (reduceOpen.length > 0) {
|
|
53318
53903
|
if (node9Open.length > 0) lines.push("");
|
|
53319
53904
|
lines.push(" " + import_chalk27.default.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
53320
|
-
for (const f of reduceOpen) lines.push(...renderFinding(f, true));
|
|
53905
|
+
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53321
53906
|
}
|
|
53322
53907
|
if (osOpen.length > 0) {
|
|
53323
53908
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
53324
53909
|
lines.push(" " + import_chalk27.default.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
53325
|
-
for (const f of osOpen) lines.push(...renderFinding(f));
|
|
53910
|
+
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
53326
53911
|
}
|
|
53327
53912
|
for (const cat of result.passedCategories) {
|
|
53328
53913
|
lines.push(` ${import_chalk27.default.green("\u2705")} ${label(cat)}${import_chalk27.default.gray("no issues found")}`);
|
|
@@ -53381,7 +53966,7 @@ function registerPostureCommand(program2) {
|
|
|
53381
53966
|
var import_chalk30 = __toESM(require("chalk"));
|
|
53382
53967
|
|
|
53383
53968
|
// src/ci-check/fetch.ts
|
|
53384
|
-
var
|
|
53969
|
+
var import_fs61 = __toESM(require("fs"));
|
|
53385
53970
|
var import_path57 = __toESM(require("path"));
|
|
53386
53971
|
var import_node_child_process = require("child_process");
|
|
53387
53972
|
var import_undici = __toESM(require_undici());
|
|
@@ -53466,7 +54051,7 @@ function parseRepoUrl(input) {
|
|
|
53466
54051
|
function isLocalPath(input) {
|
|
53467
54052
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) return true;
|
|
53468
54053
|
try {
|
|
53469
|
-
return
|
|
54054
|
+
return import_fs61.default.existsSync(input) && import_fs61.default.statSync(input).isDirectory();
|
|
53470
54055
|
} catch {
|
|
53471
54056
|
return false;
|
|
53472
54057
|
}
|
|
@@ -53583,8 +54168,8 @@ function readLocalTree(dir) {
|
|
|
53583
54168
|
const add = (rel) => {
|
|
53584
54169
|
const abs = import_path57.default.join(root, rel);
|
|
53585
54170
|
try {
|
|
53586
|
-
if (
|
|
53587
|
-
files.push({ path: rel, content:
|
|
54171
|
+
if (import_fs61.default.existsSync(abs) && import_fs61.default.statSync(abs).isFile()) {
|
|
54172
|
+
files.push({ path: rel, content: import_fs61.default.readFileSync(abs, "utf8") });
|
|
53588
54173
|
}
|
|
53589
54174
|
} catch {
|
|
53590
54175
|
}
|
|
@@ -53604,7 +54189,7 @@ function readLocalTree(dir) {
|
|
|
53604
54189
|
dirsVisited++;
|
|
53605
54190
|
let entries;
|
|
53606
54191
|
try {
|
|
53607
|
-
entries =
|
|
54192
|
+
entries = import_fs61.default.readdirSync(import_path57.default.join(root, relDir), { withFileTypes: true });
|
|
53608
54193
|
} catch {
|
|
53609
54194
|
return;
|
|
53610
54195
|
}
|
|
@@ -53627,8 +54212,8 @@ function readLocalTree(dir) {
|
|
|
53627
54212
|
for (const rel of matches) collect(rel);
|
|
53628
54213
|
const wfDir = import_path57.default.join(root, WORKFLOW_DIR);
|
|
53629
54214
|
try {
|
|
53630
|
-
if (
|
|
53631
|
-
for (const name of
|
|
54215
|
+
if (import_fs61.default.existsSync(wfDir)) {
|
|
54216
|
+
for (const name of import_fs61.default.readdirSync(wfDir)) {
|
|
53632
54217
|
if (/\.ya?ml$/.test(name)) add(import_path57.default.join(WORKFLOW_DIR, name));
|
|
53633
54218
|
}
|
|
53634
54219
|
}
|
|
@@ -54767,7 +55352,7 @@ function registerEgressCommand(program2) {
|
|
|
54767
55352
|
var import_chalk32 = __toESM(require("chalk"));
|
|
54768
55353
|
|
|
54769
55354
|
// src/shields/jail.ts
|
|
54770
|
-
var
|
|
55355
|
+
var import_fs62 = __toESM(require("fs"));
|
|
54771
55356
|
var import_os53 = __toESM(require("os"));
|
|
54772
55357
|
var import_path58 = __toESM(require("path"));
|
|
54773
55358
|
init_build();
|
|
@@ -54779,7 +55364,7 @@ function jailStorePath() {
|
|
|
54779
55364
|
function readJailPaths() {
|
|
54780
55365
|
let text;
|
|
54781
55366
|
try {
|
|
54782
|
-
text =
|
|
55367
|
+
text = import_fs62.default.readFileSync(jailStorePath(), "utf8");
|
|
54783
55368
|
} catch (err2) {
|
|
54784
55369
|
if (err2.code === "ENOENT") return [];
|
|
54785
55370
|
throw err2;
|
|
@@ -54797,8 +55382,8 @@ function readJailPaths() {
|
|
|
54797
55382
|
}
|
|
54798
55383
|
function writeJailPaths(paths) {
|
|
54799
55384
|
const p = jailStorePath();
|
|
54800
|
-
|
|
54801
|
-
|
|
55385
|
+
import_fs62.default.mkdirSync(import_path58.default.dirname(p), { recursive: true });
|
|
55386
|
+
import_fs62.default.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
|
|
54802
55387
|
}
|
|
54803
55388
|
function addJailPath(rawPath, verdict) {
|
|
54804
55389
|
const norm = rawPath.trim();
|
|
@@ -54827,7 +55412,7 @@ function regenerateUserJail(paths) {
|
|
|
54827
55412
|
writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
|
|
54828
55413
|
}
|
|
54829
55414
|
try {
|
|
54830
|
-
|
|
55415
|
+
import_fs62.default.rmSync(file, { force: true });
|
|
54831
55416
|
} catch {
|
|
54832
55417
|
}
|
|
54833
55418
|
return;
|
|
@@ -54941,13 +55526,13 @@ function registerJailCommand(program2) {
|
|
|
54941
55526
|
|
|
54942
55527
|
// src/cli/commands/sandbox.ts
|
|
54943
55528
|
var import_chalk33 = __toESM(require("chalk"));
|
|
54944
|
-
var
|
|
55529
|
+
var import_fs65 = __toESM(require("fs"));
|
|
54945
55530
|
var import_path61 = __toESM(require("path"));
|
|
54946
55531
|
var import_child_process13 = require("child_process");
|
|
54947
55532
|
init_config();
|
|
54948
55533
|
|
|
54949
55534
|
// src/sandbox/config.ts
|
|
54950
|
-
var
|
|
55535
|
+
var import_fs63 = __toESM(require("fs"));
|
|
54951
55536
|
var import_path59 = __toESM(require("path"));
|
|
54952
55537
|
var import_yaml2 = require("yaml");
|
|
54953
55538
|
var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
|
|
@@ -55025,12 +55610,12 @@ function sandboxConfigPath(cwd = process.cwd()) {
|
|
|
55025
55610
|
}
|
|
55026
55611
|
function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
|
|
55027
55612
|
const p = sandboxConfigPath(cwd);
|
|
55028
|
-
if (!
|
|
55613
|
+
if (!import_fs63.default.existsSync(p)) {
|
|
55029
55614
|
throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
|
|
55030
55615
|
}
|
|
55031
55616
|
let raw;
|
|
55032
55617
|
try {
|
|
55033
|
-
raw = (0, import_yaml2.parse)(
|
|
55618
|
+
raw = (0, import_yaml2.parse)(import_fs63.default.readFileSync(p, "utf-8"));
|
|
55034
55619
|
} catch (err2) {
|
|
55035
55620
|
throw new Error(
|
|
55036
55621
|
`sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
|
|
@@ -55088,7 +55673,7 @@ function compileAllowlist(input) {
|
|
|
55088
55673
|
init_templates();
|
|
55089
55674
|
|
|
55090
55675
|
// src/sandbox/runtime.ts
|
|
55091
|
-
var
|
|
55676
|
+
var import_fs64 = __toESM(require("fs"));
|
|
55092
55677
|
var import_os54 = __toESM(require("os"));
|
|
55093
55678
|
var import_path60 = __toESM(require("path"));
|
|
55094
55679
|
var import_crypto14 = __toESM(require("crypto"));
|
|
@@ -55116,7 +55701,7 @@ function buildRunArgs(opts) {
|
|
|
55116
55701
|
args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
|
|
55117
55702
|
if (config.node9.mountAgentCredentials) {
|
|
55118
55703
|
const creds = agentCredentialsMount(config.agent);
|
|
55119
|
-
if (
|
|
55704
|
+
if (import_fs64.default.existsSync(creds.hostPath)) {
|
|
55120
55705
|
args.push("-v", `${creds.hostPath}:${creds.target}`);
|
|
55121
55706
|
}
|
|
55122
55707
|
}
|
|
@@ -55138,16 +55723,16 @@ function sandboxBuildDir(cwd = process.cwd()) {
|
|
|
55138
55723
|
}
|
|
55139
55724
|
function writeBuildContext(cwd, dockerfile, entrypoint) {
|
|
55140
55725
|
const dir = sandboxBuildDir(cwd);
|
|
55141
|
-
|
|
55142
|
-
|
|
55143
|
-
|
|
55726
|
+
import_fs64.default.mkdirSync(dir, { recursive: true });
|
|
55727
|
+
import_fs64.default.writeFileSync(import_path60.default.join(dir, "Dockerfile"), dockerfile);
|
|
55728
|
+
import_fs64.default.writeFileSync(import_path60.default.join(dir, "entrypoint.sh"), entrypoint);
|
|
55144
55729
|
return dir;
|
|
55145
55730
|
}
|
|
55146
55731
|
function writeAllowlist(cwd, hosts) {
|
|
55147
55732
|
const dir = import_path60.default.join(cwd, ".node9", "sandbox");
|
|
55148
|
-
|
|
55733
|
+
import_fs64.default.mkdirSync(dir, { recursive: true });
|
|
55149
55734
|
const p = import_path60.default.join(dir, "allowed-domains.txt");
|
|
55150
|
-
|
|
55735
|
+
import_fs64.default.writeFileSync(p, hosts.join("\n") + "\n");
|
|
55151
55736
|
return p;
|
|
55152
55737
|
}
|
|
55153
55738
|
function resolveHomePath(p) {
|
|
@@ -55156,7 +55741,7 @@ function resolveHomePath(p) {
|
|
|
55156
55741
|
|
|
55157
55742
|
// src/cli/commands/sandbox.ts
|
|
55158
55743
|
function seedDataDirConfig(dataDir, sandbox) {
|
|
55159
|
-
|
|
55744
|
+
import_fs65.default.mkdirSync(dataDir, { recursive: true });
|
|
55160
55745
|
const configPath = import_path61.default.join(dataDir, "config.json");
|
|
55161
55746
|
const seed = {
|
|
55162
55747
|
settings: {
|
|
@@ -55168,7 +55753,7 @@ function seedDataDirConfig(dataDir, sandbox) {
|
|
|
55168
55753
|
}
|
|
55169
55754
|
}
|
|
55170
55755
|
};
|
|
55171
|
-
|
|
55756
|
+
import_fs65.default.writeFileSync(configPath, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
55172
55757
|
}
|
|
55173
55758
|
function registerSandboxCommand(program2, version2) {
|
|
55174
55759
|
const node9Version2 = pinnedNode9Version(version2);
|
|
@@ -55176,13 +55761,13 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55176
55761
|
cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
|
|
55177
55762
|
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
55178
55763
|
const p = sandboxConfigPath();
|
|
55179
|
-
if (
|
|
55764
|
+
if (import_fs65.default.existsSync(p)) {
|
|
55180
55765
|
console.log(
|
|
55181
55766
|
import_chalk33.default.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
55182
55767
|
);
|
|
55183
55768
|
return;
|
|
55184
55769
|
}
|
|
55185
|
-
|
|
55770
|
+
import_fs65.default.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
55186
55771
|
console.log(
|
|
55187
55772
|
import_chalk33.default.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + import_chalk33.default.dim(` (agent: ${agent})`)
|
|
55188
55773
|
);
|
|
@@ -55223,7 +55808,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55223
55808
|
const hash = imageContentHash(dockerfile, entrypoint);
|
|
55224
55809
|
const image = sandbox.runtime.image;
|
|
55225
55810
|
const hashFile = import_path61.default.join(sandboxBuildDir(cwd), ".image-hash");
|
|
55226
|
-
const lastHash =
|
|
55811
|
+
const lastHash = import_fs65.default.existsSync(hashFile) ? import_fs65.default.readFileSync(hashFile, "utf-8").trim() : "";
|
|
55227
55812
|
const imageExists = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
55228
55813
|
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
55229
55814
|
if (needBuild) {
|
|
@@ -55235,7 +55820,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55235
55820
|
console.error(import_chalk33.default.red(" build failed."));
|
|
55236
55821
|
process.exit(b.status ?? 1);
|
|
55237
55822
|
}
|
|
55238
|
-
|
|
55823
|
+
import_fs65.default.writeFileSync(hashFile, hash);
|
|
55239
55824
|
}
|
|
55240
55825
|
const dataDir = sandboxDataDir(cwd);
|
|
55241
55826
|
seedDataDirConfig(dataDir, sandbox);
|
|
@@ -55249,7 +55834,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55249
55834
|
});
|
|
55250
55835
|
if (sandbox.node9.mountAgentCredentials) {
|
|
55251
55836
|
const creds = agentCredentialsMount(sandbox.agent);
|
|
55252
|
-
if (
|
|
55837
|
+
if (import_fs65.default.existsSync(creds.hostPath)) {
|
|
55253
55838
|
console.log(import_chalk33.default.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
55254
55839
|
} else {
|
|
55255
55840
|
console.log(
|
|
@@ -55266,7 +55851,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55266
55851
|
});
|
|
55267
55852
|
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
55268
55853
|
const auditPath = import_path61.default.join(sandboxDataDir(), "audit.log");
|
|
55269
|
-
if (!
|
|
55854
|
+
if (!import_fs65.default.existsSync(auditPath)) {
|
|
55270
55855
|
console.log(import_chalk33.default.dim(" no sandbox audit yet."));
|
|
55271
55856
|
return;
|
|
55272
55857
|
}
|
|
@@ -55274,11 +55859,11 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55274
55859
|
});
|
|
55275
55860
|
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
55276
55861
|
const auditPath = import_path61.default.join(sandboxDataDir(), "audit.log");
|
|
55277
|
-
if (!
|
|
55862
|
+
if (!import_fs65.default.existsSync(auditPath)) {
|
|
55278
55863
|
console.log(import_chalk33.default.dim(" no sandbox audit yet."));
|
|
55279
55864
|
return;
|
|
55280
55865
|
}
|
|
55281
|
-
process.stdout.write(
|
|
55866
|
+
process.stdout.write(import_fs65.default.readFileSync(auditPath, "utf-8"));
|
|
55282
55867
|
});
|
|
55283
55868
|
cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
|
|
55284
55869
|
const cwd = process.cwd();
|
|
@@ -55292,15 +55877,16 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55292
55877
|
stdio: "ignore"
|
|
55293
55878
|
});
|
|
55294
55879
|
}
|
|
55295
|
-
|
|
55880
|
+
import_fs65.default.rmSync(import_path61.default.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
55296
55881
|
console.log(import_chalk33.default.green(" \u2713 sandbox image + build + data removed."));
|
|
55297
55882
|
});
|
|
55298
55883
|
}
|
|
55299
55884
|
|
|
55300
55885
|
// src/cli/commands/sessions.ts
|
|
55301
55886
|
var import_chalk34 = __toESM(require("chalk"));
|
|
55302
|
-
var
|
|
55887
|
+
var import_fs66 = __toESM(require("fs"));
|
|
55303
55888
|
var import_path62 = __toESM(require("path"));
|
|
55889
|
+
init_decision();
|
|
55304
55890
|
var import_os55 = __toESM(require("os"));
|
|
55305
55891
|
init_scan_summary();
|
|
55306
55892
|
init_litellm();
|
|
@@ -55397,7 +55983,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55397
55983
|
const aPath = auditPath ?? import_path62.default.join(import_os55.default.homedir(), ".node9", "audit.log");
|
|
55398
55984
|
let raw;
|
|
55399
55985
|
try {
|
|
55400
|
-
raw =
|
|
55986
|
+
raw = import_fs66.default.readFileSync(aPath, "utf-8");
|
|
55401
55987
|
} catch {
|
|
55402
55988
|
return [];
|
|
55403
55989
|
}
|
|
@@ -55407,7 +55993,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55407
55993
|
try {
|
|
55408
55994
|
const e = JSON.parse(line);
|
|
55409
55995
|
if (!e.ts || !e.tool || !e.decision) continue;
|
|
55410
|
-
if (e.
|
|
55996
|
+
if (classifyDecision(e).outcome === "allow") continue;
|
|
55411
55997
|
entries.push(e);
|
|
55412
55998
|
} catch {
|
|
55413
55999
|
}
|
|
@@ -55434,7 +56020,7 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
55434
56020
|
}
|
|
55435
56021
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
55436
56022
|
const tmpDir = import_path62.default.join(import_os55.default.homedir(), ".gemini", "tmp");
|
|
55437
|
-
if (!
|
|
56023
|
+
if (!import_fs66.default.existsSync(tmpDir)) return [];
|
|
55438
56024
|
const cutoff = days !== null ? (() => {
|
|
55439
56025
|
const d = /* @__PURE__ */ new Date();
|
|
55440
56026
|
d.setDate(d.getDate() - days);
|
|
@@ -55443,7 +56029,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55443
56029
|
})() : null;
|
|
55444
56030
|
let slugDirs;
|
|
55445
56031
|
try {
|
|
55446
|
-
slugDirs =
|
|
56032
|
+
slugDirs = import_fs66.default.readdirSync(tmpDir);
|
|
55447
56033
|
} catch {
|
|
55448
56034
|
return [];
|
|
55449
56035
|
}
|
|
@@ -55451,27 +56037,27 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55451
56037
|
for (const slug2 of slugDirs) {
|
|
55452
56038
|
const slugPath = import_path62.default.join(tmpDir, slug2);
|
|
55453
56039
|
try {
|
|
55454
|
-
if (!
|
|
56040
|
+
if (!import_fs66.default.statSync(slugPath).isDirectory()) continue;
|
|
55455
56041
|
} catch {
|
|
55456
56042
|
continue;
|
|
55457
56043
|
}
|
|
55458
56044
|
let projectRoot = import_path62.default.join(import_os55.default.homedir(), slug2);
|
|
55459
56045
|
try {
|
|
55460
|
-
projectRoot =
|
|
56046
|
+
projectRoot = import_fs66.default.readFileSync(import_path62.default.join(slugPath, ".project_root"), "utf-8").trim();
|
|
55461
56047
|
} catch {
|
|
55462
56048
|
}
|
|
55463
56049
|
const chatsDir = import_path62.default.join(slugPath, "chats");
|
|
55464
|
-
if (!
|
|
56050
|
+
if (!import_fs66.default.existsSync(chatsDir)) continue;
|
|
55465
56051
|
let chatFiles;
|
|
55466
56052
|
try {
|
|
55467
|
-
chatFiles =
|
|
56053
|
+
chatFiles = import_fs66.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
55468
56054
|
} catch {
|
|
55469
56055
|
continue;
|
|
55470
56056
|
}
|
|
55471
56057
|
for (const chatFile of chatFiles) {
|
|
55472
56058
|
let raw;
|
|
55473
56059
|
try {
|
|
55474
|
-
raw =
|
|
56060
|
+
raw = import_fs66.default.readFileSync(import_path62.default.join(chatsDir, chatFile), "utf-8");
|
|
55475
56061
|
} catch {
|
|
55476
56062
|
continue;
|
|
55477
56063
|
}
|
|
@@ -55552,7 +56138,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55552
56138
|
}
|
|
55553
56139
|
function buildCodexSessions(days, allAuditEntries) {
|
|
55554
56140
|
const sessionsBase = import_path62.default.join(import_os55.default.homedir(), ".codex", "sessions");
|
|
55555
|
-
if (!
|
|
56141
|
+
if (!import_fs66.default.existsSync(sessionsBase)) return [];
|
|
55556
56142
|
const cutoff = days !== null ? (() => {
|
|
55557
56143
|
const d = /* @__PURE__ */ new Date();
|
|
55558
56144
|
d.setDate(d.getDate() - days);
|
|
@@ -55561,28 +56147,28 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55561
56147
|
})() : null;
|
|
55562
56148
|
const jsonlFiles = [];
|
|
55563
56149
|
try {
|
|
55564
|
-
for (const year of
|
|
56150
|
+
for (const year of import_fs66.default.readdirSync(sessionsBase)) {
|
|
55565
56151
|
const yearPath = import_path62.default.join(sessionsBase, year);
|
|
55566
56152
|
try {
|
|
55567
|
-
if (!
|
|
56153
|
+
if (!import_fs66.default.statSync(yearPath).isDirectory()) continue;
|
|
55568
56154
|
} catch {
|
|
55569
56155
|
continue;
|
|
55570
56156
|
}
|
|
55571
|
-
for (const month of
|
|
56157
|
+
for (const month of import_fs66.default.readdirSync(yearPath)) {
|
|
55572
56158
|
const monthPath = import_path62.default.join(yearPath, month);
|
|
55573
56159
|
try {
|
|
55574
|
-
if (!
|
|
56160
|
+
if (!import_fs66.default.statSync(monthPath).isDirectory()) continue;
|
|
55575
56161
|
} catch {
|
|
55576
56162
|
continue;
|
|
55577
56163
|
}
|
|
55578
|
-
for (const day of
|
|
56164
|
+
for (const day of import_fs66.default.readdirSync(monthPath)) {
|
|
55579
56165
|
const dayPath = import_path62.default.join(monthPath, day);
|
|
55580
56166
|
try {
|
|
55581
|
-
if (!
|
|
56167
|
+
if (!import_fs66.default.statSync(dayPath).isDirectory()) continue;
|
|
55582
56168
|
} catch {
|
|
55583
56169
|
continue;
|
|
55584
56170
|
}
|
|
55585
|
-
for (const file of
|
|
56171
|
+
for (const file of import_fs66.default.readdirSync(dayPath)) {
|
|
55586
56172
|
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path62.default.join(dayPath, file));
|
|
55587
56173
|
}
|
|
55588
56174
|
}
|
|
@@ -55595,7 +56181,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55595
56181
|
for (const filePath of jsonlFiles) {
|
|
55596
56182
|
let lines;
|
|
55597
56183
|
try {
|
|
55598
|
-
lines =
|
|
56184
|
+
lines = import_fs66.default.readFileSync(filePath, "utf-8").split("\n");
|
|
55599
56185
|
} catch {
|
|
55600
56186
|
continue;
|
|
55601
56187
|
}
|
|
@@ -55684,7 +56270,7 @@ function buildSessions(days, historyPath) {
|
|
|
55684
56270
|
const hPath = historyPath ?? import_path62.default.join(import_os55.default.homedir(), ".claude", "history.jsonl");
|
|
55685
56271
|
let historyRaw = "";
|
|
55686
56272
|
try {
|
|
55687
|
-
historyRaw =
|
|
56273
|
+
historyRaw = import_fs66.default.readFileSync(hPath, "utf-8");
|
|
55688
56274
|
} catch {
|
|
55689
56275
|
}
|
|
55690
56276
|
const cutoff = days !== null ? (() => {
|
|
@@ -55708,7 +56294,7 @@ function buildSessions(days, historyPath) {
|
|
|
55708
56294
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
55709
56295
|
let sessionLines = [];
|
|
55710
56296
|
try {
|
|
55711
|
-
sessionLines =
|
|
56297
|
+
sessionLines = import_fs66.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
55712
56298
|
} catch {
|
|
55713
56299
|
}
|
|
55714
56300
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -56102,12 +56688,12 @@ function registerSessionTaintCommand(program2) {
|
|
|
56102
56688
|
|
|
56103
56689
|
// src/cli/commands/skill-pin.ts
|
|
56104
56690
|
var import_chalk36 = __toESM(require("chalk"));
|
|
56105
|
-
var
|
|
56691
|
+
var import_fs67 = __toESM(require("fs"));
|
|
56106
56692
|
var import_os56 = __toESM(require("os"));
|
|
56107
56693
|
var import_path63 = __toESM(require("path"));
|
|
56108
56694
|
function wipeSkillSessions() {
|
|
56109
56695
|
try {
|
|
56110
|
-
|
|
56696
|
+
import_fs67.default.rmSync(import_path63.default.join(import_os56.default.homedir(), ".node9", "skill-sessions"), {
|
|
56111
56697
|
recursive: true,
|
|
56112
56698
|
force: true
|
|
56113
56699
|
});
|
|
@@ -56189,15 +56775,15 @@ function registerSkillPinCommand(program2) {
|
|
|
56189
56775
|
}
|
|
56190
56776
|
|
|
56191
56777
|
// src/cli/commands/decisions.ts
|
|
56192
|
-
var
|
|
56778
|
+
var import_fs68 = __toESM(require("fs"));
|
|
56193
56779
|
var import_os57 = __toESM(require("os"));
|
|
56194
56780
|
var import_path64 = __toESM(require("path"));
|
|
56195
56781
|
var import_chalk37 = __toESM(require("chalk"));
|
|
56196
56782
|
var DECISIONS_FILE2 = import_path64.default.join(import_os57.default.homedir(), ".node9", "decisions.json");
|
|
56197
56783
|
function readDecisions() {
|
|
56198
56784
|
try {
|
|
56199
|
-
if (!
|
|
56200
|
-
const raw =
|
|
56785
|
+
if (!import_fs68.default.existsSync(DECISIONS_FILE2)) return {};
|
|
56786
|
+
const raw = import_fs68.default.readFileSync(DECISIONS_FILE2, "utf-8");
|
|
56201
56787
|
const parsed = JSON.parse(raw);
|
|
56202
56788
|
const out = {};
|
|
56203
56789
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -56210,10 +56796,10 @@ function readDecisions() {
|
|
|
56210
56796
|
}
|
|
56211
56797
|
function writeDecisions(d) {
|
|
56212
56798
|
const dir = import_path64.default.dirname(DECISIONS_FILE2);
|
|
56213
|
-
if (!
|
|
56799
|
+
if (!import_fs68.default.existsSync(dir)) import_fs68.default.mkdirSync(dir, { recursive: true });
|
|
56214
56800
|
const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
|
|
56215
|
-
|
|
56216
|
-
|
|
56801
|
+
import_fs68.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
|
|
56802
|
+
import_fs68.default.renameSync(tmp, DECISIONS_FILE2);
|
|
56217
56803
|
}
|
|
56218
56804
|
function registerDecisionsCommand(program2) {
|
|
56219
56805
|
const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
|
|
@@ -56270,7 +56856,7 @@ Persistent decisions (${entries.length})
|
|
|
56270
56856
|
|
|
56271
56857
|
// src/cli/commands/dlp.ts
|
|
56272
56858
|
var import_chalk38 = __toESM(require("chalk"));
|
|
56273
|
-
var
|
|
56859
|
+
var import_fs69 = __toESM(require("fs"));
|
|
56274
56860
|
var import_path65 = __toESM(require("path"));
|
|
56275
56861
|
var import_os58 = __toESM(require("os"));
|
|
56276
56862
|
var AUDIT_LOG = import_path65.default.join(import_os58.default.homedir(), ".node9", "audit.log");
|
|
@@ -56281,7 +56867,7 @@ function stripAnsi(s) {
|
|
|
56281
56867
|
}
|
|
56282
56868
|
function loadResolved() {
|
|
56283
56869
|
try {
|
|
56284
|
-
const raw = JSON.parse(
|
|
56870
|
+
const raw = JSON.parse(import_fs69.default.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
56285
56871
|
return new Set(raw);
|
|
56286
56872
|
} catch {
|
|
56287
56873
|
return /* @__PURE__ */ new Set();
|
|
@@ -56289,13 +56875,13 @@ function loadResolved() {
|
|
|
56289
56875
|
}
|
|
56290
56876
|
function saveResolved(resolved) {
|
|
56291
56877
|
try {
|
|
56292
|
-
|
|
56878
|
+
import_fs69.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
56293
56879
|
} catch {
|
|
56294
56880
|
}
|
|
56295
56881
|
}
|
|
56296
56882
|
function loadDlpFindings() {
|
|
56297
|
-
if (!
|
|
56298
|
-
return
|
|
56883
|
+
if (!import_fs69.default.existsSync(AUDIT_LOG)) return [];
|
|
56884
|
+
return import_fs69.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
56299
56885
|
if (!line.trim()) return [];
|
|
56300
56886
|
try {
|
|
56301
56887
|
const e = JSON.parse(line);
|
|
@@ -56393,14 +56979,14 @@ function registerDlpCommand(program2) {
|
|
|
56393
56979
|
|
|
56394
56980
|
// src/cli/commands/mask.ts
|
|
56395
56981
|
var import_chalk39 = __toESM(require("chalk"));
|
|
56396
|
-
var
|
|
56982
|
+
var import_fs70 = __toESM(require("fs"));
|
|
56397
56983
|
var import_path66 = __toESM(require("path"));
|
|
56398
56984
|
var import_os59 = __toESM(require("os"));
|
|
56399
56985
|
init_dlp();
|
|
56400
56986
|
function findJsonlFiles(dir) {
|
|
56401
56987
|
const results = [];
|
|
56402
|
-
if (!
|
|
56403
|
-
for (const entry of
|
|
56988
|
+
if (!import_fs70.default.existsSync(dir)) return results;
|
|
56989
|
+
for (const entry of import_fs70.default.readdirSync(dir, { withFileTypes: true })) {
|
|
56404
56990
|
const full = import_path66.default.join(dir, entry.name);
|
|
56405
56991
|
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
56406
56992
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
@@ -56444,7 +57030,7 @@ function redactJson(obj) {
|
|
|
56444
57030
|
function processFile(filePath, dryRun) {
|
|
56445
57031
|
let raw;
|
|
56446
57032
|
try {
|
|
56447
|
-
raw =
|
|
57033
|
+
raw = import_fs70.default.readFileSync(filePath, "utf-8");
|
|
56448
57034
|
} catch {
|
|
56449
57035
|
return { redactedLines: 0, patterns: [] };
|
|
56450
57036
|
}
|
|
@@ -56476,14 +57062,14 @@ function processFile(filePath, dryRun) {
|
|
|
56476
57062
|
}
|
|
56477
57063
|
}
|
|
56478
57064
|
if (!dryRun && redactedLines > 0) {
|
|
56479
|
-
|
|
57065
|
+
import_fs70.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
56480
57066
|
}
|
|
56481
57067
|
return { redactedLines, patterns };
|
|
56482
57068
|
}
|
|
56483
57069
|
function processJsonFile(filePath, dryRun) {
|
|
56484
57070
|
let raw;
|
|
56485
57071
|
try {
|
|
56486
|
-
raw =
|
|
57072
|
+
raw = import_fs70.default.readFileSync(filePath, "utf-8");
|
|
56487
57073
|
} catch {
|
|
56488
57074
|
return { redactedLines: 0, patterns: [] };
|
|
56489
57075
|
}
|
|
@@ -56496,14 +57082,14 @@ function processJsonFile(filePath, dryRun) {
|
|
|
56496
57082
|
const { value, modified, found } = redactJson(parsed);
|
|
56497
57083
|
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
56498
57084
|
if (!dryRun) {
|
|
56499
|
-
|
|
57085
|
+
import_fs70.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
56500
57086
|
}
|
|
56501
57087
|
return { redactedLines: 1, patterns: found };
|
|
56502
57088
|
}
|
|
56503
57089
|
function findJsonFiles(dir) {
|
|
56504
57090
|
const results = [];
|
|
56505
|
-
if (!
|
|
56506
|
-
for (const entry of
|
|
57091
|
+
if (!import_fs70.default.existsSync(dir)) return results;
|
|
57092
|
+
for (const entry of import_fs70.default.readdirSync(dir, { withFileTypes: true })) {
|
|
56507
57093
|
const full = import_path66.default.join(dir, entry.name);
|
|
56508
57094
|
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
56509
57095
|
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
@@ -56523,7 +57109,7 @@ function registerMaskCommand(program2) {
|
|
|
56523
57109
|
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
56524
57110
|
const filtered = cutoff ? allFiles.filter((f) => {
|
|
56525
57111
|
try {
|
|
56526
|
-
return
|
|
57112
|
+
return import_fs70.default.statSync(f.path).mtime >= cutoff;
|
|
56527
57113
|
} catch {
|
|
56528
57114
|
return false;
|
|
56529
57115
|
}
|
|
@@ -56579,7 +57165,7 @@ function registerMaskCommand(program2) {
|
|
|
56579
57165
|
// src/cli.ts
|
|
56580
57166
|
init_blast();
|
|
56581
57167
|
var { version } = JSON.parse(
|
|
56582
|
-
|
|
57168
|
+
import_fs73.default.readFileSync(import_path69.default.join(__dirname, "../package.json"), "utf-8")
|
|
56583
57169
|
);
|
|
56584
57170
|
var program = new import_commander.Command();
|
|
56585
57171
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
@@ -56759,14 +57345,14 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
56759
57345
|
}
|
|
56760
57346
|
if (options.purge) {
|
|
56761
57347
|
const node9Dir = import_path69.default.join(import_os62.default.homedir(), ".node9");
|
|
56762
|
-
if (
|
|
57348
|
+
if (import_fs73.default.existsSync(node9Dir)) {
|
|
56763
57349
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
56764
57350
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
56765
57351
|
default: false
|
|
56766
57352
|
});
|
|
56767
57353
|
if (confirmed) {
|
|
56768
|
-
|
|
56769
|
-
if (
|
|
57354
|
+
import_fs73.default.rmSync(node9Dir, { recursive: true });
|
|
57355
|
+
if (import_fs73.default.existsSync(node9Dir)) {
|
|
56770
57356
|
console.error(
|
|
56771
57357
|
import_chalk41.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
56772
57358
|
);
|
|
@@ -56931,12 +57517,12 @@ Run "node9 addto claude" to register it as the statusLine.`
|
|
|
56931
57517
|
if (subcommand === "debug") {
|
|
56932
57518
|
const flagFile = import_path69.default.join(import_os62.default.homedir(), ".node9", "hud-debug");
|
|
56933
57519
|
if (state === "on") {
|
|
56934
|
-
|
|
56935
|
-
|
|
57520
|
+
import_fs73.default.mkdirSync(import_path69.default.dirname(flagFile), { recursive: true });
|
|
57521
|
+
import_fs73.default.writeFileSync(flagFile, "");
|
|
56936
57522
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
56937
57523
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
56938
57524
|
} else if (state === "off") {
|
|
56939
|
-
if (
|
|
57525
|
+
if (import_fs73.default.existsSync(flagFile)) import_fs73.default.unlinkSync(flagFile);
|
|
56940
57526
|
console.log("HUD debug logging disabled.");
|
|
56941
57527
|
} else {
|
|
56942
57528
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -57061,7 +57647,7 @@ if (process.argv[2] !== "daemon") {
|
|
|
57061
57647
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
57062
57648
|
const logPath = import_path69.default.join(import_os62.default.homedir(), ".node9", "hook-debug.log");
|
|
57063
57649
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
57064
|
-
|
|
57650
|
+
import_fs73.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
57065
57651
|
`);
|
|
57066
57652
|
}
|
|
57067
57653
|
process.exit(0);
|