@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.mjs
CHANGED
|
@@ -347,6 +347,7 @@ var init_config_schema = __esm({
|
|
|
347
347
|
// nudge-only (default), and the scan cadence in minutes.
|
|
348
348
|
mcpAutoWrap: z.boolean().optional(),
|
|
349
349
|
mcpReconcileIntervalMinutes: z.number().positive().optional(),
|
|
350
|
+
mcpStaleAfterDays: z.number().min(0).optional(),
|
|
350
351
|
cloudSyncIntervalHours: z.number().positive().optional(),
|
|
351
352
|
// Seconds-granular override for the cloud policy sync cadence. Wins over
|
|
352
353
|
// cloudSyncIntervalHours when set. Lets you opt into fast apply (e.g. 20)
|
|
@@ -3981,6 +3982,7 @@ var init_dist = __esm({
|
|
|
3981
3982
|
name: "redis",
|
|
3982
3983
|
description: "Protects Redis instances from destructive AI operations",
|
|
3983
3984
|
aliases: [],
|
|
3985
|
+
_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.",
|
|
3984
3986
|
smartRules: [
|
|
3985
3987
|
{
|
|
3986
3988
|
name: "shield:redis:block-flushall",
|
|
@@ -3989,7 +3991,7 @@ var init_dist = __esm({
|
|
|
3989
3991
|
{
|
|
3990
3992
|
field: "command",
|
|
3991
3993
|
op: "matches",
|
|
3992
|
-
value: "\\bFLUSHALL\\b",
|
|
3994
|
+
value: "(redis|valkey)-cli.*\\bFLUSHALL\\b|\\bFLUSHALL\\b.*(redis|valkey)-cli|^ ?FLUSHALL\\b|\\.flushall\\s*\\(",
|
|
3993
3995
|
flags: "i"
|
|
3994
3996
|
}
|
|
3995
3997
|
],
|
|
@@ -4003,7 +4005,7 @@ var init_dist = __esm({
|
|
|
4003
4005
|
{
|
|
4004
4006
|
field: "command",
|
|
4005
4007
|
op: "matches",
|
|
4006
|
-
value: "\\bFLUSHDB\\b",
|
|
4008
|
+
value: "(redis|valkey)-cli.*\\bFLUSHDB\\b|\\bFLUSHDB\\b.*(redis|valkey)-cli|^ ?FLUSHDB\\b|\\.flushdb\\s*\\(",
|
|
4007
4009
|
flags: "i"
|
|
4008
4010
|
}
|
|
4009
4011
|
],
|
|
@@ -4017,7 +4019,7 @@ var init_dist = __esm({
|
|
|
4017
4019
|
{
|
|
4018
4020
|
field: "command",
|
|
4019
4021
|
op: "matches",
|
|
4020
|
-
value: "\\
|
|
4022
|
+
value: "(redis|valkey)-cli.*CONFIG\\s+RESETSTAT|^ ?CONFIG\\s+RESETSTAT",
|
|
4021
4023
|
flags: "i"
|
|
4022
4024
|
}
|
|
4023
4025
|
],
|
|
@@ -4031,7 +4033,7 @@ var init_dist = __esm({
|
|
|
4031
4033
|
{
|
|
4032
4034
|
field: "command",
|
|
4033
4035
|
op: "matches",
|
|
4034
|
-
value: "\\bCONFIG\\s+SET\\b",
|
|
4036
|
+
value: "(redis|valkey)-cli.*\\bCONFIG\\s+SET\\b|\\bCONFIG\\s+SET\\b.*(redis|valkey)-cli|^ ?CONFIG\\s+SET\\b",
|
|
4035
4037
|
flags: "i"
|
|
4036
4038
|
}
|
|
4037
4039
|
],
|
|
@@ -4045,7 +4047,7 @@ var init_dist = __esm({
|
|
|
4045
4047
|
{
|
|
4046
4048
|
field: "command",
|
|
4047
4049
|
op: "matches",
|
|
4048
|
-
value: "
|
|
4050
|
+
value: "(redis|valkey)-cli.*\\bDEL\\b.*[*?\\[]|^ ?DEL\\b.*[*?\\[]|-cli.*--scan.*xargs.*del",
|
|
4049
4051
|
flags: "i"
|
|
4050
4052
|
}
|
|
4051
4053
|
],
|
|
@@ -4615,6 +4617,48 @@ function getActiveEnvironment(config) {
|
|
|
4615
4617
|
const env = config.settings.environment || process.env.NODE_ENV || "development";
|
|
4616
4618
|
return config.environments[env] ?? null;
|
|
4617
4619
|
}
|
|
4620
|
+
function readRulesCacheResilient(cacheFile) {
|
|
4621
|
+
let existed = false;
|
|
4622
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
4623
|
+
let content;
|
|
4624
|
+
try {
|
|
4625
|
+
content = fs4.readFileSync(cacheFile, "utf-8");
|
|
4626
|
+
existed = true;
|
|
4627
|
+
} catch (err2) {
|
|
4628
|
+
if (err2.code === "ENOENT") return {};
|
|
4629
|
+
continue;
|
|
4630
|
+
}
|
|
4631
|
+
try {
|
|
4632
|
+
return JSON.parse(content);
|
|
4633
|
+
} catch {
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
if (existed) {
|
|
4637
|
+
const backup = path4.join(path4.dirname(cacheFile), "rules-cache.last-good.json");
|
|
4638
|
+
if (backup !== cacheFile) {
|
|
4639
|
+
try {
|
|
4640
|
+
const raw = JSON.parse(fs4.readFileSync(backup, "utf-8"));
|
|
4641
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
|
|
4642
|
+
return raw;
|
|
4643
|
+
} catch {
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_UNREADABLE");
|
|
4647
|
+
}
|
|
4648
|
+
return {};
|
|
4649
|
+
}
|
|
4650
|
+
function logCacheReadIssue(cacheFile, kind) {
|
|
4651
|
+
if (cacheReadFailureLogged) return;
|
|
4652
|
+
cacheReadFailureLogged = true;
|
|
4653
|
+
try {
|
|
4654
|
+
fs4.appendFileSync(
|
|
4655
|
+
path4.join(os4.homedir(), ".node9", "hook-debug.log"),
|
|
4656
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
|
|
4657
|
+
`
|
|
4658
|
+
);
|
|
4659
|
+
} catch {
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4618
4662
|
function getConfig(cwd) {
|
|
4619
4663
|
if (!cwd && cachedConfig) return cachedConfig;
|
|
4620
4664
|
const globalPath = path4.join(os4.homedir(), ".node9", "config.json");
|
|
@@ -4682,6 +4726,7 @@ function getConfig(cwd) {
|
|
|
4682
4726
|
if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
|
|
4683
4727
|
if (s.mcpReconcileIntervalMinutes !== void 0)
|
|
4684
4728
|
mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
|
|
4729
|
+
if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
|
|
4685
4730
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
4686
4731
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
4687
4732
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -4691,12 +4736,13 @@ function getConfig(cwd) {
|
|
|
4691
4736
|
if (p.smartRules) {
|
|
4692
4737
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
4693
4738
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
4694
|
-
const
|
|
4739
|
+
const localRules = p.smartRules.map(({ pinned: _pinned, ...r }) => r);
|
|
4740
|
+
const userRuleNames = new Set(localRules.filter((r) => r.name).map((r) => r.name));
|
|
4695
4741
|
const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
|
|
4696
4742
|
const filteredNonBlocks = defaultNonBlocks.filter(
|
|
4697
4743
|
(r) => !r.name || !userRuleNames.has(r.name)
|
|
4698
4744
|
);
|
|
4699
|
-
mergedPolicy.smartRules = [...filteredBlocks, ...
|
|
4745
|
+
mergedPolicy.smartRules = [...filteredBlocks, ...localRules, ...filteredNonBlocks];
|
|
4700
4746
|
}
|
|
4701
4747
|
if (p.snapshot) {
|
|
4702
4748
|
const s2 = p.snapshot;
|
|
@@ -4761,10 +4807,12 @@ function getConfig(cwd) {
|
|
|
4761
4807
|
applyLayer(globalConfig);
|
|
4762
4808
|
applyLayer(projectConfig);
|
|
4763
4809
|
let cloudManagedShields = [];
|
|
4810
|
+
let modeCloudControlled = false;
|
|
4811
|
+
let modeCloudStaged = false;
|
|
4764
4812
|
{
|
|
4765
4813
|
const cacheFile = path4.join(os4.homedir(), ".node9", "rules-cache.json");
|
|
4766
4814
|
try {
|
|
4767
|
-
const raw =
|
|
4815
|
+
const raw = readRulesCacheResilient(cacheFile);
|
|
4768
4816
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
4769
4817
|
applyLayer({ policy: { smartRules: raw.rules } });
|
|
4770
4818
|
}
|
|
@@ -4781,6 +4829,9 @@ function getConfig(cwd) {
|
|
|
4781
4829
|
locked.includes("mode")
|
|
4782
4830
|
);
|
|
4783
4831
|
}
|
|
4832
|
+
if (typeof mc.mode === "string" || locked.includes("mode")) {
|
|
4833
|
+
modeCloudControlled = true;
|
|
4834
|
+
}
|
|
4784
4835
|
if (mc.egress && typeof mc.egress === "object") {
|
|
4785
4836
|
const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
|
|
4786
4837
|
mergedPolicy.egress = applyManagedEgress(
|
|
@@ -4879,19 +4930,28 @@ function getConfig(cwd) {
|
|
|
4879
4930
|
}
|
|
4880
4931
|
if (raw.shadowMode === true) {
|
|
4881
4932
|
mergedSettings.mode = "observe";
|
|
4933
|
+
modeCloudStaged = true;
|
|
4882
4934
|
}
|
|
4883
4935
|
} catch {
|
|
4884
4936
|
}
|
|
4885
4937
|
}
|
|
4886
4938
|
const shieldOverrides = readShieldOverrides();
|
|
4887
4939
|
const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
|
|
4940
|
+
const cloudManagedSet = new Set(cloudManagedShields);
|
|
4888
4941
|
for (const shieldName of activeShieldNames) {
|
|
4889
|
-
const
|
|
4942
|
+
const isCloudMandated = cloudManagedSet.has(shieldName);
|
|
4943
|
+
const shield = isCloudMandated ? BUILTIN_SHIELDS[shieldName] : getShield(shieldName);
|
|
4890
4944
|
if (!shield) continue;
|
|
4891
4945
|
const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
4892
|
-
const ruleOverrides = shieldOverrides[shieldName] ?? {};
|
|
4946
|
+
const ruleOverrides = isCloudMandated ? {} : shieldOverrides[shieldName] ?? {};
|
|
4893
4947
|
for (const rule of shield.smartRules) {
|
|
4894
|
-
|
|
4948
|
+
const collides = rule.name ? existingRuleNames.has(rule.name) : false;
|
|
4949
|
+
if (isCloudMandated) {
|
|
4950
|
+
if (collides) {
|
|
4951
|
+
mergedPolicy.smartRules = mergedPolicy.smartRules.filter((r) => r.name !== rule.name);
|
|
4952
|
+
}
|
|
4953
|
+
mergedPolicy.smartRules.push({ ...rule, pinned: true });
|
|
4954
|
+
} else if (!collides) {
|
|
4895
4955
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
4896
4956
|
mergedPolicy.smartRules.push(
|
|
4897
4957
|
overrideVerdict !== void 0 ? { ...rule, verdict: overrideVerdict } : rule
|
|
@@ -4907,7 +4967,27 @@ function getConfig(cwd) {
|
|
|
4907
4967
|
for (const rule of ADVISORY_SMART_RULES) {
|
|
4908
4968
|
if (!existingAdvisoryNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
4909
4969
|
}
|
|
4910
|
-
|
|
4970
|
+
const envMode = process.env.NODE9_MODE;
|
|
4971
|
+
if (envMode && !modeCloudControlled && ["observe", "audit", "standard", "strict"].includes(envMode)) {
|
|
4972
|
+
mergedSettings.mode = envMode;
|
|
4973
|
+
}
|
|
4974
|
+
if (cloudManagedShields.length > 0 && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
|
|
4975
|
+
mergedSettings.mode = "standard";
|
|
4976
|
+
}
|
|
4977
|
+
const managedFloorActive = cloudManagedShields.length > 0 || modeCloudControlled && mergedSettings.mode === "strict";
|
|
4978
|
+
if (modeCloudControlled && mergedSettings.mode === "strict") {
|
|
4979
|
+
for (const name of Object.keys(mergedEnvironments)) {
|
|
4980
|
+
if (mergedEnvironments[name]?.requireApproval === false) {
|
|
4981
|
+
const cleaned = { ...mergedEnvironments[name] };
|
|
4982
|
+
delete cleaned.requireApproval;
|
|
4983
|
+
mergedEnvironments[name] = cleaned;
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
if (managedFloorActive) {
|
|
4988
|
+
mergedPolicy.ignoredTools = [...DEFAULT_CONFIG.policy.ignoredTools];
|
|
4989
|
+
mergedPolicy.sandboxPaths = [...DEFAULT_CONFIG.policy.sandboxPaths];
|
|
4990
|
+
}
|
|
4911
4991
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
4912
4992
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
4913
4993
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
@@ -4976,7 +5056,7 @@ ${error.replace("Invalid config:\n", "")}
|
|
|
4976
5056
|
}
|
|
4977
5057
|
return sanitized;
|
|
4978
5058
|
}
|
|
4979
|
-
var DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig;
|
|
5059
|
+
var DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig, cacheReadFailureLogged;
|
|
4980
5060
|
var init_config = __esm({
|
|
4981
5061
|
"src/config/index.ts"() {
|
|
4982
5062
|
"use strict";
|
|
@@ -4985,6 +5065,7 @@ var init_config = __esm({
|
|
|
4985
5065
|
init_managed();
|
|
4986
5066
|
init_build();
|
|
4987
5067
|
init_trusted_hosts();
|
|
5068
|
+
init_dist();
|
|
4988
5069
|
DANGEROUS_WORDS = [
|
|
4989
5070
|
"mkfs",
|
|
4990
5071
|
// formats/wipes a filesystem partition
|
|
@@ -5262,6 +5343,7 @@ var init_config = __esm({
|
|
|
5262
5343
|
}
|
|
5263
5344
|
];
|
|
5264
5345
|
cachedConfig = null;
|
|
5346
|
+
cacheReadFailureLogged = false;
|
|
5265
5347
|
}
|
|
5266
5348
|
});
|
|
5267
5349
|
|
|
@@ -5982,6 +6064,18 @@ async function isDaemonReachable(timeoutMs = 500) {
|
|
|
5982
6064
|
return false;
|
|
5983
6065
|
}
|
|
5984
6066
|
}
|
|
6067
|
+
async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
6068
|
+
try {
|
|
6069
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/approver`, {
|
|
6070
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6071
|
+
});
|
|
6072
|
+
if (!res.ok) return false;
|
|
6073
|
+
const body = await res.json();
|
|
6074
|
+
return body.interactive === true;
|
|
6075
|
+
} catch {
|
|
6076
|
+
return false;
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
5985
6079
|
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent) {
|
|
5986
6080
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
5987
6081
|
const ctrl = new AbortController();
|
|
@@ -6862,6 +6956,12 @@ function isNetworkTool(toolName, args) {
|
|
|
6862
6956
|
function notifyActivity(data) {
|
|
6863
6957
|
return notifyActivitySocket(data);
|
|
6864
6958
|
}
|
|
6959
|
+
async function hasReachableHumanApprover(opts) {
|
|
6960
|
+
const hasDisplay = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
6961
|
+
const nativeReachable = !opts.calledFromDaemon && opts.approvers.native !== false && hasDisplay;
|
|
6962
|
+
if (nativeReachable) return true;
|
|
6963
|
+
return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
|
|
6964
|
+
}
|
|
6865
6965
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
6866
6966
|
if (!options?.calledFromDaemon) {
|
|
6867
6967
|
const actId = randomUUID();
|
|
@@ -7176,6 +7276,35 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7176
7276
|
return { approved: true, checkedBy: "local-policy" };
|
|
7177
7277
|
}
|
|
7178
7278
|
if (policyResult.decision === "block") {
|
|
7279
|
+
const daemonUp = isDaemonRunning();
|
|
7280
|
+
let humanApproverReachable = false;
|
|
7281
|
+
if (!policyResult.dependsOnStatePredicates?.length && daemonUp && !isTestEnv2) {
|
|
7282
|
+
humanApproverReachable = await hasReachableHumanApprover({
|
|
7283
|
+
approvers,
|
|
7284
|
+
calledFromDaemon: options?.calledFromDaemon
|
|
7285
|
+
});
|
|
7286
|
+
}
|
|
7287
|
+
const mayDowngrade = daemonUp && !isTestEnv2 && humanApproverReachable;
|
|
7288
|
+
const hardBlock = () => {
|
|
7289
|
+
if (!isManual)
|
|
7290
|
+
appendLocalAudit(
|
|
7291
|
+
toolName,
|
|
7292
|
+
args,
|
|
7293
|
+
"deny",
|
|
7294
|
+
"smart-rule-block",
|
|
7295
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
7296
|
+
hashAuditArgs
|
|
7297
|
+
);
|
|
7298
|
+
return {
|
|
7299
|
+
approved: false,
|
|
7300
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7301
|
+
blockedBy: "local-config",
|
|
7302
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
7303
|
+
ruleHit: policyResult.ruleName,
|
|
7304
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7305
|
+
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7306
|
+
};
|
|
7307
|
+
};
|
|
7179
7308
|
if (policyResult.dependsOnStatePredicates?.length) {
|
|
7180
7309
|
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
7181
7310
|
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
@@ -7192,7 +7321,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7192
7321
|
if (predicatesMet && policyResult.recoveryCommand) {
|
|
7193
7322
|
statefulRecoveryCommand = policyResult.recoveryCommand;
|
|
7194
7323
|
}
|
|
7195
|
-
} else if (
|
|
7324
|
+
} else if (mayDowngrade) {
|
|
7196
7325
|
if (!isManual)
|
|
7197
7326
|
appendLocalAudit(
|
|
7198
7327
|
toolName,
|
|
@@ -7214,36 +7343,13 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7214
7343
|
};
|
|
7215
7344
|
}
|
|
7216
7345
|
} else {
|
|
7217
|
-
|
|
7218
|
-
appendLocalAudit(
|
|
7219
|
-
toolName,
|
|
7220
|
-
args,
|
|
7221
|
-
"deny",
|
|
7222
|
-
"smart-rule-block",
|
|
7223
|
-
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
7224
|
-
// panel can attribute this block to its specific shield
|
|
7225
|
-
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
7226
|
-
// rule→shield map. checkedBy stays as the generic
|
|
7227
|
-
// `smart-rule-block` for backward compat with existing
|
|
7228
|
-
// log readers.
|
|
7229
|
-
{ ...meta, ruleName: policyResult.ruleName },
|
|
7230
|
-
hashAuditArgs
|
|
7231
|
-
);
|
|
7232
|
-
return {
|
|
7233
|
-
approved: false,
|
|
7234
|
-
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7235
|
-
blockedBy: "local-config",
|
|
7236
|
-
blockedByLabel: policyResult.blockedByLabel,
|
|
7237
|
-
ruleHit: policyResult.ruleName,
|
|
7238
|
-
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7239
|
-
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7240
|
-
};
|
|
7346
|
+
return hardBlock();
|
|
7241
7347
|
}
|
|
7242
7348
|
}
|
|
7243
7349
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
7244
7350
|
policyMatchedField = policyResult.matchedField;
|
|
7245
7351
|
policyMatchedWord = policyResult.matchedWord;
|
|
7246
|
-
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
7352
|
+
if (policyResult.ruleName || policyResult.tier === 7) localSmartRuleMatched = true;
|
|
7247
7353
|
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
7248
7354
|
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
7249
7355
|
riskMetadata = computeRiskMetadata(
|
|
@@ -7255,7 +7361,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7255
7361
|
policyResult.ruleName
|
|
7256
7362
|
);
|
|
7257
7363
|
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
7258
|
-
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
7364
|
+
const persistent = policyResult.ruleName || policyResult.tier === 7 ? null : getPersistentDecision(toolName);
|
|
7259
7365
|
if (persistent === "allow" && !appPermReview) {
|
|
7260
7366
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
|
|
7261
7367
|
return { approved: true, checkedBy: "persistent" };
|
|
@@ -15924,6 +16030,9 @@ data: ${JSON.stringify(data)}
|
|
|
15924
16030
|
}
|
|
15925
16031
|
});
|
|
15926
16032
|
}
|
|
16033
|
+
function hasInteractiveClient() {
|
|
16034
|
+
return [...sseClients].some((c) => c.capabilities.includes("input"));
|
|
16035
|
+
}
|
|
15927
16036
|
function broadcastForensic(finding) {
|
|
15928
16037
|
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
15929
16038
|
const event = {
|
|
@@ -17154,18 +17263,27 @@ var init_score = __esm({
|
|
|
17154
17263
|
|
|
17155
17264
|
// src/posture/headline.ts
|
|
17156
17265
|
function worstFinding(findings) {
|
|
17157
|
-
|
|
17266
|
+
const cmp = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
17267
|
+
return [...findings].sort(
|
|
17268
|
+
(a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || cmp(a.category, b.category) || cmp(a.title, b.title)
|
|
17269
|
+
)[0];
|
|
17270
|
+
}
|
|
17271
|
+
function actionFromFinding(f) {
|
|
17272
|
+
if (!f?.fix) return null;
|
|
17273
|
+
const fix = f.fix.replace(/^fix it now:\s*/i, "");
|
|
17274
|
+
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`;
|
|
17275
|
+
return fix + where;
|
|
17158
17276
|
}
|
|
17159
17277
|
function deriveHeadline(allFindings) {
|
|
17160
17278
|
const findings = allFindings.filter(
|
|
17161
17279
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
|
|
17162
17280
|
);
|
|
17163
17281
|
if (findings.length === 0 || findings.every((f) => f.severity === "advisory")) return null;
|
|
17164
|
-
const
|
|
17165
|
-
const secrets =
|
|
17166
|
-
const egressOpen =
|
|
17167
|
-
const noIsolation =
|
|
17168
|
-
const gateWeak =
|
|
17282
|
+
const has2 = (category) => findings.some((f) => f.category === category);
|
|
17283
|
+
const secrets = has2("Secrets");
|
|
17284
|
+
const egressOpen = has2("Egress");
|
|
17285
|
+
const noIsolation = has2("Isolation");
|
|
17286
|
+
const gateWeak = has2("Approval gate");
|
|
17169
17287
|
const notWired = findings.some((f) => f.category === "Coverage" && f.severity === "critical");
|
|
17170
17288
|
const observeOnly = findings.some((f) => f.category === "Coverage" && f.severity === "high");
|
|
17171
17289
|
let risk;
|
|
@@ -17188,13 +17306,16 @@ function deriveHeadline(allFindings) {
|
|
|
17188
17306
|
} else if (observeOnly) {
|
|
17189
17307
|
action = "Switch node9 to enforcing mode \u2014 right now it is only watching, not blocking.";
|
|
17190
17308
|
} else if (egressOpen) {
|
|
17191
|
-
|
|
17309
|
+
const egressFix = actionFromFinding(
|
|
17310
|
+
worstFinding(findings.filter((f) => f.category === "Egress"))
|
|
17311
|
+
);
|
|
17312
|
+
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.";
|
|
17192
17313
|
} else if (secrets) {
|
|
17193
|
-
action = "node9 can block reads of sensitive
|
|
17314
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Secrets"))) ?? "node9 can block reads of sensitive credential files in-path (`node9 shield enable project-jail`).";
|
|
17194
17315
|
} else if (gateWeak) {
|
|
17195
|
-
action = "node9 can enforce destructive-command blocking in-path.";
|
|
17316
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Approval gate"))) ?? "node9 can enforce destructive-command blocking in-path (`node9 shield enable bash-safe`).";
|
|
17196
17317
|
} else {
|
|
17197
|
-
action = worstFinding(findings)
|
|
17318
|
+
action = actionFromFinding(worstFinding(findings)) ?? "Review the findings below.";
|
|
17198
17319
|
}
|
|
17199
17320
|
return { risk, action };
|
|
17200
17321
|
}
|
|
@@ -17448,7 +17569,7 @@ var init_ship = __esm({
|
|
|
17448
17569
|
});
|
|
17449
17570
|
|
|
17450
17571
|
// src/policy-snapshot/build.ts
|
|
17451
|
-
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = []) {
|
|
17572
|
+
function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, statusEntries = [], syncHealth) {
|
|
17452
17573
|
const p = config.policy;
|
|
17453
17574
|
const statusByKey = /* @__PURE__ */ new Map();
|
|
17454
17575
|
for (const s of statusEntries) {
|
|
@@ -17507,7 +17628,17 @@ function buildPolicySnapshot(config, activeShields, overrides, mcpTools = {}, st
|
|
|
17507
17628
|
dlpEnabled: p.dlp.enabled,
|
|
17508
17629
|
engineVersion: ENGINE_VERSION,
|
|
17509
17630
|
// Connected rows first so they win the cap; non-connected SEE rows fill the rest.
|
|
17510
|
-
mcpServers: [...connectedRows, ...extraRows].slice(0, MAX_MCP_SERVERS)
|
|
17631
|
+
mcpServers: [...connectedRows, ...extraRows].slice(0, MAX_MCP_SERVERS),
|
|
17632
|
+
// fleet-ship Step 2: ship the proxy's own sync health so the dashboard can
|
|
17633
|
+
// derive a "sync failing" badge. Only included when the caller passes it.
|
|
17634
|
+
...syncHealth && {
|
|
17635
|
+
syncHealth: {
|
|
17636
|
+
lastCheckedAt: syncHealth.lastCheckedAt,
|
|
17637
|
+
lastChangedAt: syncHealth.lastChangedAt,
|
|
17638
|
+
lastError: syncHealth.lastError,
|
|
17639
|
+
consecutiveFailures: syncHealth.consecutiveFailures
|
|
17640
|
+
}
|
|
17641
|
+
}
|
|
17511
17642
|
};
|
|
17512
17643
|
}
|
|
17513
17644
|
var MAX_RULES, MAX_EGRESS, MAX_MCP_SERVERS, MAX_MCP_TOOLS;
|
|
@@ -17723,6 +17854,21 @@ function inventoryMcp(home = os32.homedir()) {
|
|
|
17723
17854
|
}
|
|
17724
17855
|
return out;
|
|
17725
17856
|
}
|
|
17857
|
+
function inventoryServerKeys(inv) {
|
|
17858
|
+
const keys = /* @__PURE__ */ new Set();
|
|
17859
|
+
for (const e of inv) {
|
|
17860
|
+
if (e.state === "gatewayed") {
|
|
17861
|
+
const i = e.args.indexOf("--upstream");
|
|
17862
|
+
if (i >= 0 && e.args[i + 1]) {
|
|
17863
|
+
keys.add(getServerKey(e.args[i + 1]));
|
|
17864
|
+
}
|
|
17865
|
+
} else if (e.state === "ungoverned") {
|
|
17866
|
+
const cmd = [e.command, ...e.args].map(quoteArg).join(" ");
|
|
17867
|
+
keys.add(getServerKey(cmd));
|
|
17868
|
+
}
|
|
17869
|
+
}
|
|
17870
|
+
return keys;
|
|
17871
|
+
}
|
|
17726
17872
|
function writeMcpEntry(mcpFile, format, name, entry) {
|
|
17727
17873
|
const key = format === "toml" ? "mcp_servers" : "mcpServers";
|
|
17728
17874
|
let root = {};
|
|
@@ -17746,6 +17892,7 @@ var init_mcp_wrap = __esm({
|
|
|
17746
17892
|
"use strict";
|
|
17747
17893
|
init_agent_wiring();
|
|
17748
17894
|
init_mcp_cmd();
|
|
17895
|
+
init_mcp_pin();
|
|
17749
17896
|
init_mcp_cmd();
|
|
17750
17897
|
}
|
|
17751
17898
|
});
|
|
@@ -18177,9 +18324,12 @@ function extractManagedConfig(body) {
|
|
|
18177
18324
|
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;
|
|
18178
18325
|
}
|
|
18179
18326
|
function writeCache2(cache) {
|
|
18180
|
-
const
|
|
18181
|
-
|
|
18182
|
-
|
|
18327
|
+
const data = JSON.stringify(cache, null, 2) + "\n";
|
|
18328
|
+
atomicWriteSync2(rulesCacheFile(), data, "utf-8");
|
|
18329
|
+
try {
|
|
18330
|
+
atomicWriteSync2(rulesCacheBackupFile(), data, "utf-8");
|
|
18331
|
+
} catch {
|
|
18332
|
+
}
|
|
18183
18333
|
}
|
|
18184
18334
|
async function syncOnce() {
|
|
18185
18335
|
const creds = readCredentials();
|
|
@@ -18283,7 +18433,10 @@ async function pushPolicySnapshot(creds) {
|
|
|
18283
18433
|
// Merged config-vs-connected status. resolveMcpStatus reads process.env to
|
|
18284
18434
|
// resolve ${VAR} placeholders — so this push reflects THIS process's env
|
|
18285
18435
|
// (daemon here, user shell in runPolicyPush); see the P2 design's two-env note.
|
|
18286
|
-
resolveMcpStatus()
|
|
18436
|
+
resolveMcpStatus(),
|
|
18437
|
+
// fleet-ship Step 2: ship this machine's sync health so the dashboard can
|
|
18438
|
+
// show "sync failing" badges. readSyncHealth is local (same module, no circular).
|
|
18439
|
+
readSyncHealth()
|
|
18287
18440
|
);
|
|
18288
18441
|
await shipPolicySnapshot(body, creds);
|
|
18289
18442
|
} catch {
|
|
@@ -18306,7 +18459,8 @@ async function runPolicyPush() {
|
|
|
18306
18459
|
// Merged config-vs-connected status. resolveMcpStatus reads process.env to
|
|
18307
18460
|
// resolve ${VAR} placeholders — so this push reflects THIS process's env
|
|
18308
18461
|
// (daemon here, user shell in runPolicyPush); see the P2 design's two-env note.
|
|
18309
|
-
resolveMcpStatus()
|
|
18462
|
+
resolveMcpStatus(),
|
|
18463
|
+
readSyncHealth()
|
|
18310
18464
|
);
|
|
18311
18465
|
const sent = await shipPolicySnapshot(body, creds);
|
|
18312
18466
|
return sent ? { ok: true } : { ok: false, reason: "Push failed (network or server error)" };
|
|
@@ -18469,7 +18623,7 @@ function startForensicBroadcast() {
|
|
|
18469
18623
|
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
18470
18624
|
recurring.unref();
|
|
18471
18625
|
}
|
|
18472
|
-
var 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;
|
|
18626
|
+
var 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;
|
|
18473
18627
|
var init_sync = __esm({
|
|
18474
18628
|
"src/daemon/sync.ts"() {
|
|
18475
18629
|
"use strict";
|
|
@@ -18479,6 +18633,7 @@ var init_sync = __esm({
|
|
|
18479
18633
|
init_ship();
|
|
18480
18634
|
init_build2();
|
|
18481
18635
|
init_mcp_tools();
|
|
18636
|
+
init_state2();
|
|
18482
18637
|
init_mcp_status();
|
|
18483
18638
|
init_ship2();
|
|
18484
18639
|
init_shields();
|
|
@@ -18499,6 +18654,7 @@ var init_sync = __esm({
|
|
|
18499
18654
|
"long-output-redacted": "longOutputRedactions"
|
|
18500
18655
|
};
|
|
18501
18656
|
rulesCacheFile = () => path35.join(os33.homedir(), ".node9", "rules-cache.json");
|
|
18657
|
+
rulesCacheBackupFile = () => path35.join(os33.homedir(), ".node9", "rules-cache.last-good.json");
|
|
18502
18658
|
DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
18503
18659
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
18504
18660
|
MIN_INTERVAL_SECONDS = 15;
|
|
@@ -18740,6 +18896,53 @@ var init_audit_shipper = __esm({
|
|
|
18740
18896
|
}
|
|
18741
18897
|
});
|
|
18742
18898
|
|
|
18899
|
+
// src/audit/decision.ts
|
|
18900
|
+
function classifyDecision(a, b) {
|
|
18901
|
+
const isRow = !!a && typeof a === "object";
|
|
18902
|
+
const decision = isRow ? a.decision : a;
|
|
18903
|
+
const attribution = isRow ? a.checkedBy ?? a.source : b;
|
|
18904
|
+
const raw = typeof decision === "string" ? decision : String(decision ?? "");
|
|
18905
|
+
const src = typeof attribution === "string" ? attribution.toLowerCase() : "";
|
|
18906
|
+
const d = raw.toLowerCase();
|
|
18907
|
+
if (src && has(src, "observe-mode")) {
|
|
18908
|
+
return { outcome: "observe", label: "Would block", raw };
|
|
18909
|
+
}
|
|
18910
|
+
if (d === "dlp") return { outcome: "info", label: "Finding", raw };
|
|
18911
|
+
if (d === "mcp-discovered") return { outcome: "info", label: "Info", raw };
|
|
18912
|
+
if (d === "allow" || d === "allowed") {
|
|
18913
|
+
if (src === "post-hook") return { outcome: "allow", label: "Ran", raw };
|
|
18914
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18915
|
+
return { outcome: "allow", label: "Approved", raw };
|
|
18916
|
+
}
|
|
18917
|
+
return { outcome: "allow", label: "Auto-allowed", raw };
|
|
18918
|
+
}
|
|
18919
|
+
if (d === "deny" || d === "auto-deny" || d === "block") {
|
|
18920
|
+
if (TIMEOUT_SOURCES.has(src)) {
|
|
18921
|
+
return { outcome: "deny", label: "Timed out", raw };
|
|
18922
|
+
}
|
|
18923
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18924
|
+
return { outcome: "deny", label: "Denied", raw };
|
|
18925
|
+
}
|
|
18926
|
+
return { outcome: "deny", label: "Blocked", raw };
|
|
18927
|
+
}
|
|
18928
|
+
if (d === "review" || d === "pending") {
|
|
18929
|
+
return { outcome: "info", label: "Pending", raw };
|
|
18930
|
+
}
|
|
18931
|
+
return { outcome: "unknown", label: raw ? `? ${raw}` : "? (none)", raw };
|
|
18932
|
+
}
|
|
18933
|
+
function decisionTag(view) {
|
|
18934
|
+
return `[${view.label}]`.padEnd(14);
|
|
18935
|
+
}
|
|
18936
|
+
var HUMAN_SOURCES, TIMEOUT_SOURCES, has;
|
|
18937
|
+
var init_decision = __esm({
|
|
18938
|
+
"src/audit/decision.ts"() {
|
|
18939
|
+
"use strict";
|
|
18940
|
+
HUMAN_SOURCES = /* @__PURE__ */ new Set(["daemon", "cloud", "local-decision", "inline-review-approved"]);
|
|
18941
|
+
TIMEOUT_SOURCES = /* @__PURE__ */ new Set(["timeout"]);
|
|
18942
|
+
has = (s, needle) => s.includes(needle);
|
|
18943
|
+
}
|
|
18944
|
+
});
|
|
18945
|
+
|
|
18743
18946
|
// src/daemon/dlp-scanner.ts
|
|
18744
18947
|
import fs38 from "fs";
|
|
18745
18948
|
import path37 from "path";
|
|
@@ -18979,6 +19182,7 @@ function runMcpReconcile() {
|
|
|
18979
19182
|
}
|
|
18980
19183
|
const baseline = loadBaseline();
|
|
18981
19184
|
const creds = getCredentials();
|
|
19185
|
+
reconcileStale(inv, creds);
|
|
18982
19186
|
const fresh = inv.filter((e) => e.state === "ungoverned" && !baseline.has(idKey(e)));
|
|
18983
19187
|
if (fresh.length === 0) return;
|
|
18984
19188
|
const wrappedAgents = /* @__PURE__ */ new Set();
|
|
@@ -19018,6 +19222,77 @@ function runMcpReconcile() {
|
|
|
19018
19222
|
}
|
|
19019
19223
|
saveBaseline(baseline);
|
|
19020
19224
|
}
|
|
19225
|
+
function reconcileStale(inv, creds) {
|
|
19226
|
+
let pins;
|
|
19227
|
+
try {
|
|
19228
|
+
pins = readMcpPins();
|
|
19229
|
+
} catch {
|
|
19230
|
+
return;
|
|
19231
|
+
}
|
|
19232
|
+
const serverKeys = Object.keys(pins.servers);
|
|
19233
|
+
if (serverKeys.length === 0) return;
|
|
19234
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
19235
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19236
|
+
let dirty = false;
|
|
19237
|
+
for (const sk of serverKeys) {
|
|
19238
|
+
const pin = pins.servers[sk];
|
|
19239
|
+
if (liveKeys.has(sk)) {
|
|
19240
|
+
if (pin.lastSeen !== now) {
|
|
19241
|
+
pin.lastSeen = now;
|
|
19242
|
+
dirty = true;
|
|
19243
|
+
}
|
|
19244
|
+
} else if (!pin.lastSeen) {
|
|
19245
|
+
pin.lastSeen = pin.pinnedAt;
|
|
19246
|
+
dirty = true;
|
|
19247
|
+
}
|
|
19248
|
+
}
|
|
19249
|
+
const staleDays = getConfig().settings.mcpStaleAfterDays ?? DEFAULT_STALE_DAYS;
|
|
19250
|
+
if (staleDays > 0 && liveKeys.size > 0) {
|
|
19251
|
+
const staleMs = staleDays * 864e5;
|
|
19252
|
+
for (const sk of serverKeys) {
|
|
19253
|
+
const pin = pins.servers[sk];
|
|
19254
|
+
if (liveKeys.has(sk)) continue;
|
|
19255
|
+
const age = Date.now() - Date.parse(pin.lastSeen ?? pin.pinnedAt);
|
|
19256
|
+
if (age >= staleMs) {
|
|
19257
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
19258
|
+
event: "mcp-pin-auto-removed",
|
|
19259
|
+
serverKey: sk,
|
|
19260
|
+
label: pin.label,
|
|
19261
|
+
lastSeen: pin.lastSeen,
|
|
19262
|
+
pinnedAt: pin.pinnedAt
|
|
19263
|
+
});
|
|
19264
|
+
if (creds) {
|
|
19265
|
+
try {
|
|
19266
|
+
void auditLocalAllow(
|
|
19267
|
+
`mcp-server:${sk}`,
|
|
19268
|
+
{
|
|
19269
|
+
serverKey: sk,
|
|
19270
|
+
label: pin.label,
|
|
19271
|
+
lastSeen: pin.lastSeen,
|
|
19272
|
+
reason: "stale",
|
|
19273
|
+
staleDays
|
|
19274
|
+
},
|
|
19275
|
+
"mcp-server-removed",
|
|
19276
|
+
creds,
|
|
19277
|
+
{ mcpServer: pin.label },
|
|
19278
|
+
void 0,
|
|
19279
|
+
false
|
|
19280
|
+
);
|
|
19281
|
+
} catch {
|
|
19282
|
+
}
|
|
19283
|
+
}
|
|
19284
|
+
delete pins.servers[sk];
|
|
19285
|
+
dirty = true;
|
|
19286
|
+
}
|
|
19287
|
+
}
|
|
19288
|
+
}
|
|
19289
|
+
if (dirty) {
|
|
19290
|
+
try {
|
|
19291
|
+
writeMcpPins(pins);
|
|
19292
|
+
} catch {
|
|
19293
|
+
}
|
|
19294
|
+
}
|
|
19295
|
+
}
|
|
19021
19296
|
function startMcpReconciler() {
|
|
19022
19297
|
setImmediate(() => {
|
|
19023
19298
|
try {
|
|
@@ -19039,7 +19314,7 @@ function startMcpReconciler() {
|
|
|
19039
19314
|
};
|
|
19040
19315
|
schedule();
|
|
19041
19316
|
}
|
|
19042
|
-
var BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN;
|
|
19317
|
+
var BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN, DEFAULT_STALE_DAYS;
|
|
19043
19318
|
var init_mcp_reconciler = __esm({
|
|
19044
19319
|
"src/daemon/mcp-reconciler.ts"() {
|
|
19045
19320
|
"use strict";
|
|
@@ -19048,9 +19323,11 @@ var init_mcp_reconciler = __esm({
|
|
|
19048
19323
|
init_config();
|
|
19049
19324
|
init_cloud();
|
|
19050
19325
|
init_audit();
|
|
19326
|
+
init_mcp_pin();
|
|
19051
19327
|
BASELINE_FILE2 = path38.join(os36.homedir(), ".node9", "mcp-baseline.json");
|
|
19052
19328
|
BASELINE_CAP = 500;
|
|
19053
19329
|
DEFAULT_INTERVAL_MIN = 60;
|
|
19330
|
+
DEFAULT_STALE_DAYS = 7;
|
|
19054
19331
|
}
|
|
19055
19332
|
});
|
|
19056
19333
|
|
|
@@ -19125,20 +19402,87 @@ var init_hook_heal = __esm({
|
|
|
19125
19402
|
import fs40 from "fs";
|
|
19126
19403
|
import path39 from "path";
|
|
19127
19404
|
import os37 from "os";
|
|
19405
|
+
function capStartupLog(file) {
|
|
19406
|
+
try {
|
|
19407
|
+
if (fs40.statSync(file).size > MAX_STARTUP_LOG_BYTES) fs40.truncateSync(file);
|
|
19408
|
+
} catch {
|
|
19409
|
+
}
|
|
19410
|
+
}
|
|
19128
19411
|
function openStartupLogFd() {
|
|
19129
19412
|
try {
|
|
19130
19413
|
const file = DAEMON_STARTUP_LOG();
|
|
19131
19414
|
const dir = path39.dirname(file);
|
|
19132
19415
|
if (!fs40.existsSync(dir)) fs40.mkdirSync(dir, { recursive: true });
|
|
19133
|
-
|
|
19134
|
-
if (fs40.statSync(file).size > MAX_STARTUP_LOG_BYTES) fs40.truncateSync(file);
|
|
19135
|
-
} catch {
|
|
19136
|
-
}
|
|
19416
|
+
capStartupLog(file);
|
|
19137
19417
|
return fs40.openSync(file, "a");
|
|
19138
19418
|
} catch {
|
|
19139
19419
|
return void 0;
|
|
19140
19420
|
}
|
|
19141
19421
|
}
|
|
19422
|
+
function recordStartupState(outcome, kind, detail) {
|
|
19423
|
+
try {
|
|
19424
|
+
if (outcome === "starting") {
|
|
19425
|
+
const prev = readStartupState();
|
|
19426
|
+
if (prev?.outcome === "starting") {
|
|
19427
|
+
const prevAt = new Date(prev.at).getTime();
|
|
19428
|
+
const age = Math.abs(Date.now() - prevAt);
|
|
19429
|
+
if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
|
|
19430
|
+
}
|
|
19431
|
+
}
|
|
19432
|
+
const file = DAEMON_STARTUP_STATE();
|
|
19433
|
+
const dir = path39.dirname(file);
|
|
19434
|
+
if (!fs40.existsSync(dir)) fs40.mkdirSync(dir, { recursive: true });
|
|
19435
|
+
const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
19436
|
+
if (kind) state.kind = kind;
|
|
19437
|
+
if (detail) state.detail = detail.slice(0, MAX_DETAIL);
|
|
19438
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
19439
|
+
try {
|
|
19440
|
+
fs40.writeFileSync(tmp, JSON.stringify(state), "utf-8");
|
|
19441
|
+
fs40.renameSync(tmp, file);
|
|
19442
|
+
} catch (err2) {
|
|
19443
|
+
try {
|
|
19444
|
+
fs40.unlinkSync(tmp);
|
|
19445
|
+
} catch {
|
|
19446
|
+
}
|
|
19447
|
+
throw err2;
|
|
19448
|
+
}
|
|
19449
|
+
} catch {
|
|
19450
|
+
}
|
|
19451
|
+
}
|
|
19452
|
+
function readStartupState() {
|
|
19453
|
+
try {
|
|
19454
|
+
const raw = fs40.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
|
|
19455
|
+
const s = JSON.parse(raw);
|
|
19456
|
+
if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
|
|
19457
|
+
return s;
|
|
19458
|
+
} catch {
|
|
19459
|
+
return null;
|
|
19460
|
+
}
|
|
19461
|
+
}
|
|
19462
|
+
function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
19463
|
+
const s = readStartupState();
|
|
19464
|
+
if (!s) return null;
|
|
19465
|
+
const at = new Date(s.at);
|
|
19466
|
+
if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
|
|
19467
|
+
switch (s.outcome) {
|
|
19468
|
+
case "ok":
|
|
19469
|
+
case "ok-elsewhere":
|
|
19470
|
+
return null;
|
|
19471
|
+
case "starting":
|
|
19472
|
+
if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
|
|
19473
|
+
return {
|
|
19474
|
+
kind: "did-not-start",
|
|
19475
|
+
detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
|
|
19476
|
+
at,
|
|
19477
|
+
// `at` is the FIRST attempt of the streak, not the most recent one.
|
|
19478
|
+
label: "start attempts failing since"
|
|
19479
|
+
};
|
|
19480
|
+
case "failed":
|
|
19481
|
+
return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
|
|
19482
|
+
default:
|
|
19483
|
+
return null;
|
|
19484
|
+
}
|
|
19485
|
+
}
|
|
19142
19486
|
function logDaemonStartup(kind, detail) {
|
|
19143
19487
|
try {
|
|
19144
19488
|
const file = DAEMON_STARTUP_LOG();
|
|
@@ -19150,12 +19494,15 @@ function logDaemonStartup(kind, detail) {
|
|
|
19150
19494
|
} catch {
|
|
19151
19495
|
}
|
|
19152
19496
|
}
|
|
19153
|
-
var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES;
|
|
19497
|
+
var DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
|
|
19154
19498
|
var init_startup_log = __esm({
|
|
19155
19499
|
"src/daemon/startup-log.ts"() {
|
|
19156
19500
|
"use strict";
|
|
19157
19501
|
DAEMON_STARTUP_LOG = () => path39.join(os37.homedir(), ".node9", "daemon-startup.log");
|
|
19158
19502
|
MAX_STARTUP_LOG_BYTES = 256 * 1024;
|
|
19503
|
+
DAEMON_STARTUP_STATE = () => path39.join(os37.homedir(), ".node9", "daemon-startup-state.json");
|
|
19504
|
+
MAX_DETAIL = 200;
|
|
19505
|
+
STARTING_GRACE_MS = 90 * 1e3;
|
|
19159
19506
|
}
|
|
19160
19507
|
});
|
|
19161
19508
|
|
|
@@ -19167,6 +19514,76 @@ import os38 from "os";
|
|
|
19167
19514
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
19168
19515
|
import { spawnSync } from "child_process";
|
|
19169
19516
|
import chalk6 from "chalk";
|
|
19517
|
+
function buildDaemonReport(allEntries, period, now) {
|
|
19518
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19519
|
+
let start = new Date(todayStart);
|
|
19520
|
+
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19521
|
+
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19522
|
+
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19523
|
+
const entries = allEntries.filter((e) => {
|
|
19524
|
+
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19525
|
+
return new Date(e.ts) >= start;
|
|
19526
|
+
});
|
|
19527
|
+
const isBlocked = (e) => classifyDecision(e).outcome === "deny";
|
|
19528
|
+
const checkedBy = (e) => typeof e.checkedBy === "string" ? e.checkedBy : void 0;
|
|
19529
|
+
const summary = {
|
|
19530
|
+
total: entries.length,
|
|
19531
|
+
// The inline `startsWith('allow')` this replaces counted every non-allow
|
|
19532
|
+
// row as "blocked" — so DLP findings, MCP-discovery events and, worst, all
|
|
19533
|
+
// the observe-mode "would have blocked" rows inflated the blocked count
|
|
19534
|
+
// with things that were never refusals.
|
|
19535
|
+
allowed: entries.filter((e) => classifyDecision(e).outcome === "allow").length,
|
|
19536
|
+
blocked: entries.filter(isBlocked).length,
|
|
19537
|
+
dlp: entries.filter((e) => checkedBy(e)?.includes("dlp")).length,
|
|
19538
|
+
loops: entries.filter((e) => checkedBy(e) === "loop-detected").length
|
|
19539
|
+
};
|
|
19540
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
19541
|
+
if (period === "today") {
|
|
19542
|
+
for (let h = 0; h < 24; h++) {
|
|
19543
|
+
const key = String(h).padStart(2, "0") + ":00";
|
|
19544
|
+
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19545
|
+
}
|
|
19546
|
+
for (const e of entries) {
|
|
19547
|
+
const hour = new Date(e.ts).getHours();
|
|
19548
|
+
const key = String(hour).padStart(2, "0") + ":00";
|
|
19549
|
+
const d = dailyMap.get(key);
|
|
19550
|
+
d.calls++;
|
|
19551
|
+
if (isBlocked(e)) d.blocked++;
|
|
19552
|
+
}
|
|
19553
|
+
} else {
|
|
19554
|
+
for (const e of entries) {
|
|
19555
|
+
const date = e.ts.slice(0, 10);
|
|
19556
|
+
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19557
|
+
d.calls++;
|
|
19558
|
+
if (isBlocked(e)) d.blocked++;
|
|
19559
|
+
dailyMap.set(date, d);
|
|
19560
|
+
}
|
|
19561
|
+
}
|
|
19562
|
+
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19563
|
+
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19564
|
+
for (const e of entries) {
|
|
19565
|
+
const tool = String(e.tool ?? "");
|
|
19566
|
+
topToolsMap.set(tool, (topToolsMap.get(tool) || 0) + 1);
|
|
19567
|
+
if (isBlocked(e)) topBlockedMap.set(tool, (topBlockedMap.get(tool) || 0) + 1);
|
|
19568
|
+
}
|
|
19569
|
+
const top5 = (m) => [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19570
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
19571
|
+
for (const e of entries) {
|
|
19572
|
+
const key = e.agent || "unknown";
|
|
19573
|
+
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19574
|
+
a.total++;
|
|
19575
|
+
if (isBlocked(e)) a.blocked++;
|
|
19576
|
+
if (checkedBy(e)?.includes("dlp")) a.dlp++;
|
|
19577
|
+
agentMap.set(key, a);
|
|
19578
|
+
}
|
|
19579
|
+
return {
|
|
19580
|
+
summary,
|
|
19581
|
+
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19582
|
+
topTools: top5(topToolsMap),
|
|
19583
|
+
topBlockedTools: top5(topBlockedMap),
|
|
19584
|
+
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
19585
|
+
};
|
|
19586
|
+
}
|
|
19170
19587
|
function startDaemon() {
|
|
19171
19588
|
try {
|
|
19172
19589
|
startCostSync();
|
|
@@ -19181,6 +19598,7 @@ function startDaemon() {
|
|
|
19181
19598
|
const stack = err2 instanceof Error ? err2.stack ?? err2.message : String(err2);
|
|
19182
19599
|
console.error("\n\u{1F6D1} Node9 daemon startup failed:\n" + stack);
|
|
19183
19600
|
logDaemonStartup("startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19601
|
+
recordStartupState("failed", "startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19184
19602
|
process.exit(1);
|
|
19185
19603
|
}
|
|
19186
19604
|
const internalToken = randomUUID4();
|
|
@@ -19553,6 +19971,10 @@ data: ${JSON.stringify(item.data)}
|
|
|
19553
19971
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
19554
19972
|
}
|
|
19555
19973
|
}
|
|
19974
|
+
if (req.method === "GET" && pathname === "/approver") {
|
|
19975
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19976
|
+
return res.end(JSON.stringify({ interactive: hasInteractiveClient() }));
|
|
19977
|
+
}
|
|
19556
19978
|
if (req.method === "GET" && pathname === "/state/check") {
|
|
19557
19979
|
const predicatesParam = reqUrl.searchParams.get("predicates") ?? "";
|
|
19558
19980
|
const predicates = predicatesParam.split(",").filter(Boolean);
|
|
@@ -19654,75 +20076,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
19654
20076
|
return [];
|
|
19655
20077
|
}
|
|
19656
20078
|
});
|
|
19657
|
-
const now = /* @__PURE__ */ new Date();
|
|
19658
|
-
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19659
|
-
let start = new Date(todayStart);
|
|
19660
|
-
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19661
|
-
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19662
|
-
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19663
|
-
const entries = allEntries.filter((e) => {
|
|
19664
|
-
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19665
|
-
return new Date(e.ts) >= start;
|
|
19666
|
-
});
|
|
19667
|
-
const summary = {
|
|
19668
|
-
total: entries.length,
|
|
19669
|
-
allowed: entries.filter((e) => e.decision && e.decision.startsWith("allow")).length,
|
|
19670
|
-
blocked: entries.filter((e) => e.decision && !e.decision.startsWith("allow")).length,
|
|
19671
|
-
dlp: entries.filter((e) => e.checkedBy && e.checkedBy.includes("dlp")).length,
|
|
19672
|
-
loops: entries.filter((e) => e.checkedBy === "loop-detected").length
|
|
19673
|
-
};
|
|
19674
|
-
const dailyMap = /* @__PURE__ */ new Map();
|
|
19675
|
-
if (period === "today") {
|
|
19676
|
-
for (let h = 0; h < 24; h++) {
|
|
19677
|
-
const key = String(h).padStart(2, "0") + ":00";
|
|
19678
|
-
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19679
|
-
}
|
|
19680
|
-
for (const e of entries) {
|
|
19681
|
-
const hour = new Date(e.ts).getHours();
|
|
19682
|
-
const key = String(hour).padStart(2, "0") + ":00";
|
|
19683
|
-
const d = dailyMap.get(key);
|
|
19684
|
-
d.calls++;
|
|
19685
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19686
|
-
}
|
|
19687
|
-
} else {
|
|
19688
|
-
for (const e of entries) {
|
|
19689
|
-
const date = e.ts.slice(0, 10);
|
|
19690
|
-
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19691
|
-
d.calls++;
|
|
19692
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19693
|
-
dailyMap.set(date, d);
|
|
19694
|
-
}
|
|
19695
|
-
}
|
|
19696
|
-
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19697
|
-
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19698
|
-
for (const e of entries) {
|
|
19699
|
-
topToolsMap.set(e.tool, (topToolsMap.get(e.tool) || 0) + 1);
|
|
19700
|
-
if (e.decision && !e.decision.startsWith("allow")) {
|
|
19701
|
-
topBlockedMap.set(e.tool, (topBlockedMap.get(e.tool) || 0) + 1);
|
|
19702
|
-
}
|
|
19703
|
-
}
|
|
19704
|
-
const topTools = [...topToolsMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19705
|
-
const topBlockedTools = [...topBlockedMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19706
|
-
const agentMap = /* @__PURE__ */ new Map();
|
|
19707
|
-
for (const e of entries) {
|
|
19708
|
-
const key = e.agent || "unknown";
|
|
19709
|
-
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19710
|
-
a.total++;
|
|
19711
|
-
if (e.decision && !e.decision.startsWith("allow")) a.blocked++;
|
|
19712
|
-
if (e.checkedBy?.includes("dlp")) a.dlp++;
|
|
19713
|
-
agentMap.set(key, a);
|
|
19714
|
-
}
|
|
19715
|
-
const byAgent = [...agentMap.values()].sort((a, b) => b.total - a.total);
|
|
19716
20079
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19717
|
-
return res.end(
|
|
19718
|
-
JSON.stringify({
|
|
19719
|
-
summary,
|
|
19720
|
-
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19721
|
-
topTools,
|
|
19722
|
-
topBlockedTools,
|
|
19723
|
-
byAgent
|
|
19724
|
-
})
|
|
19725
|
-
);
|
|
20080
|
+
return res.end(JSON.stringify(buildDaemonReport(allEntries, period, /* @__PURE__ */ new Date())));
|
|
19726
20081
|
} catch {
|
|
19727
20082
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
19728
20083
|
return res.end(JSON.stringify({ error: "Failed to parse report" }));
|
|
@@ -20025,6 +20380,23 @@ data: ${JSON.stringify(item.data)}
|
|
|
20025
20380
|
res.writeHead(404).end();
|
|
20026
20381
|
});
|
|
20027
20382
|
setDaemonServer(server);
|
|
20383
|
+
let bindAttempts = 0;
|
|
20384
|
+
const MAX_BIND_ATTEMPTS = 3;
|
|
20385
|
+
function retryListen() {
|
|
20386
|
+
if (++bindAttempts >= MAX_BIND_ATTEMPTS) {
|
|
20387
|
+
logDaemonStartup(
|
|
20388
|
+
"port-unavailable",
|
|
20389
|
+
`:${DAEMON_PORT} is held by something that is not a node9 daemon`
|
|
20390
|
+
);
|
|
20391
|
+
recordStartupState(
|
|
20392
|
+
"failed",
|
|
20393
|
+
"port-unavailable",
|
|
20394
|
+
`:${DAEMON_PORT} is held by another process that is not a node9 daemon \u2014 free the port, then: node9 daemon --background`
|
|
20395
|
+
);
|
|
20396
|
+
return process.exit(0);
|
|
20397
|
+
}
|
|
20398
|
+
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
20399
|
+
}
|
|
20028
20400
|
server.on("error", (e) => {
|
|
20029
20401
|
if (e.code === "EADDRINUSE") {
|
|
20030
20402
|
try {
|
|
@@ -20032,6 +20404,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
20032
20404
|
const { pid } = JSON.parse(fs41.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
20033
20405
|
process.kill(pid, 0);
|
|
20034
20406
|
logDaemonStartup("port-in-use", `another daemon (pid ${pid}) owns :${DAEMON_PORT}`);
|
|
20407
|
+
recordStartupState("ok-elsewhere");
|
|
20035
20408
|
return process.exit(0);
|
|
20036
20409
|
}
|
|
20037
20410
|
} catch {
|
|
@@ -20039,13 +20412,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
20039
20412
|
fs41.unlinkSync(DAEMON_PID_FILE);
|
|
20040
20413
|
} catch {
|
|
20041
20414
|
}
|
|
20042
|
-
|
|
20415
|
+
retryListen();
|
|
20043
20416
|
return;
|
|
20044
20417
|
}
|
|
20045
20418
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/settings`, {
|
|
20046
20419
|
signal: AbortSignal.timeout(1e3)
|
|
20047
20420
|
}).then((res) => {
|
|
20048
20421
|
if (res.ok) {
|
|
20422
|
+
let adopted = false;
|
|
20049
20423
|
try {
|
|
20050
20424
|
let orphanPid = null;
|
|
20051
20425
|
const ss = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
@@ -20073,19 +20447,38 @@ data: ${JSON.stringify(item.data)}
|
|
|
20073
20447
|
JSON.stringify({ pid: orphanPid, port: DAEMON_PORT, internalToken, autoStarted }),
|
|
20074
20448
|
{ mode: 384 }
|
|
20075
20449
|
);
|
|
20450
|
+
adopted = true;
|
|
20076
20451
|
}
|
|
20077
20452
|
} catch {
|
|
20078
20453
|
}
|
|
20454
|
+
if (adopted) {
|
|
20455
|
+
logDaemonStartup(
|
|
20456
|
+
"port-in-use-orphan",
|
|
20457
|
+
`adopted the daemon already on :${DAEMON_PORT}`
|
|
20458
|
+
);
|
|
20459
|
+
recordStartupState("ok-elsewhere");
|
|
20460
|
+
} else {
|
|
20461
|
+
logDaemonStartup(
|
|
20462
|
+
"orphan-unidentified",
|
|
20463
|
+
`healthy daemon on :${DAEMON_PORT} could not be identified \u2014 no pid file written`
|
|
20464
|
+
);
|
|
20465
|
+
recordStartupState(
|
|
20466
|
+
"failed",
|
|
20467
|
+
"orphan-unidentified",
|
|
20468
|
+
`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`
|
|
20469
|
+
);
|
|
20470
|
+
}
|
|
20079
20471
|
process.exit(0);
|
|
20080
20472
|
} else {
|
|
20081
|
-
|
|
20473
|
+
retryListen();
|
|
20082
20474
|
}
|
|
20083
20475
|
}).catch(() => {
|
|
20084
|
-
|
|
20476
|
+
retryListen();
|
|
20085
20477
|
});
|
|
20086
20478
|
return;
|
|
20087
20479
|
}
|
|
20088
20480
|
logDaemonStartup("bind-failed", e.message);
|
|
20481
|
+
recordStartupState("failed", "bind-failed", e.message);
|
|
20089
20482
|
console.error(chalk6.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
|
|
20090
20483
|
process.exit(1);
|
|
20091
20484
|
});
|
|
@@ -20103,6 +20496,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
20103
20496
|
{ mode: 384 }
|
|
20104
20497
|
);
|
|
20105
20498
|
console.error(chalk6.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE on 127.0.0.1:${DAEMON_PORT}`));
|
|
20499
|
+
logDaemonStartup("ok", `listening on ${DAEMON_HOST}:${DAEMON_PORT}`);
|
|
20500
|
+
recordStartupState("ok");
|
|
20106
20501
|
});
|
|
20107
20502
|
if (watchMode) {
|
|
20108
20503
|
console.error(chalk6.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
@@ -20120,6 +20515,7 @@ var init_server = __esm({
|
|
|
20120
20515
|
init_costSync();
|
|
20121
20516
|
init_sync();
|
|
20122
20517
|
init_audit_shipper();
|
|
20518
|
+
init_decision();
|
|
20123
20519
|
init_dlp_scanner();
|
|
20124
20520
|
init_mcp_reconciler();
|
|
20125
20521
|
init_hook_heal();
|
|
@@ -45302,7 +45698,7 @@ __export(tail_exports, {
|
|
|
45302
45698
|
});
|
|
45303
45699
|
import http5 from "http";
|
|
45304
45700
|
import chalk40 from "chalk";
|
|
45305
|
-
import
|
|
45701
|
+
import fs72 from "fs";
|
|
45306
45702
|
import os61 from "os";
|
|
45307
45703
|
import path68 from "path";
|
|
45308
45704
|
import readline6 from "readline";
|
|
@@ -45329,19 +45725,19 @@ function getModelContextLimit(model) {
|
|
|
45329
45725
|
}
|
|
45330
45726
|
function readSessionUsage() {
|
|
45331
45727
|
const projectsDir = path68.join(os61.homedir(), ".claude", "projects");
|
|
45332
|
-
if (!
|
|
45728
|
+
if (!fs72.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 fs72.readdirSync(projectsDir)) {
|
|
45337
45733
|
const dirPath = path68.join(projectsDir, dir);
|
|
45338
45734
|
try {
|
|
45339
|
-
if (!
|
|
45340
|
-
for (const file of
|
|
45735
|
+
if (!fs72.statSync(dirPath).isDirectory()) continue;
|
|
45736
|
+
for (const file of fs72.readdirSync(dirPath)) {
|
|
45341
45737
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
45342
45738
|
const filePath = path68.join(dirPath, file);
|
|
45343
45739
|
try {
|
|
45344
|
-
const mtime =
|
|
45740
|
+
const mtime = fs72.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 = fs72.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 (fs72.existsSync(PID_FILE)) {
|
|
45460
45856
|
try {
|
|
45461
|
-
const { port } = JSON.parse(
|
|
45857
|
+
const { port } = JSON.parse(fs72.readFileSync(PID_FILE, "utf-8"));
|
|
45462
45858
|
pidPort = port;
|
|
45463
45859
|
} catch {
|
|
45464
45860
|
console.error(chalk40.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(chalk40.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
45872
|
+
const startupFd = openStartupLogFd();
|
|
45873
|
+
recordStartupState("starting");
|
|
45476
45874
|
const child = spawn8(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
|
+
fs72.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 = path68.join(os61.homedir(), ".node9", "config.json");
|
|
45618
46023
|
try {
|
|
45619
|
-
const raw = JSON.parse(
|
|
46024
|
+
const raw = JSON.parse(fs72.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 = path68.join(os61.homedir(), ".node9", "config.json");
|
|
45636
46041
|
try {
|
|
45637
|
-
const raw = JSON.parse(
|
|
46042
|
+
const raw = JSON.parse(fs72.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
|
+
fs72.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
|
+
fs72.appendFileSync(
|
|
45816
46221
|
path68.join(os61.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 = path68.join(os61.homedir(), ".node9", "audit.log");
|
|
45881
46286
|
try {
|
|
45882
|
-
const unackedDlp =
|
|
46287
|
+
const unackedDlp = fs72.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 = fs72.statSync(auditLog).mtimeMs;
|
|
45923
46328
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
45924
46329
|
console.log("");
|
|
45925
46330
|
console.log(
|
|
@@ -46108,6 +46513,7 @@ var PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRA
|
|
|
46108
46513
|
var init_tail = __esm({
|
|
46109
46514
|
"src/tui/tail.ts"() {
|
|
46110
46515
|
"use strict";
|
|
46516
|
+
init_startup_log();
|
|
46111
46517
|
init_daemon2();
|
|
46112
46518
|
init_daemon();
|
|
46113
46519
|
PID_FILE = path68.join(os61.homedir(), ".node9", "daemon.pid");
|
|
@@ -46158,7 +46564,7 @@ __export(hud_exports, {
|
|
|
46158
46564
|
main: () => main,
|
|
46159
46565
|
renderEnvironmentLine: () => renderEnvironmentLine
|
|
46160
46566
|
});
|
|
46161
|
-
import
|
|
46567
|
+
import fs73 from "fs";
|
|
46162
46568
|
import path69 from "path";
|
|
46163
46569
|
import os62 from "os";
|
|
46164
46570
|
import http6 from "http";
|
|
@@ -46236,9 +46642,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
46236
46642
|
return ` (${m}m left)`;
|
|
46237
46643
|
}
|
|
46238
46644
|
function safeReadJson(filePath) {
|
|
46239
|
-
if (!
|
|
46645
|
+
if (!fs73.existsSync(filePath)) return null;
|
|
46240
46646
|
try {
|
|
46241
|
-
return JSON.parse(
|
|
46647
|
+
return JSON.parse(fs73.readFileSync(filePath, "utf-8"));
|
|
46242
46648
|
} catch {
|
|
46243
46649
|
return null;
|
|
46244
46650
|
}
|
|
@@ -46259,10 +46665,10 @@ function countHooksInFile(filePath) {
|
|
|
46259
46665
|
return Object.keys(cfg.hooks).length;
|
|
46260
46666
|
}
|
|
46261
46667
|
function countRulesInDir(rulesDir) {
|
|
46262
|
-
if (!
|
|
46668
|
+
if (!fs73.existsSync(rulesDir)) return 0;
|
|
46263
46669
|
let count = 0;
|
|
46264
46670
|
try {
|
|
46265
|
-
for (const entry of
|
|
46671
|
+
for (const entry of fs73.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
46266
46672
|
if (entry.isDirectory()) {
|
|
46267
46673
|
count += countRulesInDir(path69.join(rulesDir, entry.name));
|
|
46268
46674
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -46288,7 +46694,7 @@ function countConfigs(cwd) {
|
|
|
46288
46694
|
let hooksCount = 0;
|
|
46289
46695
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
46290
46696
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
46291
|
-
if (
|
|
46697
|
+
if (fs73.existsSync(path69.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46292
46698
|
rulesCount += countRulesInDir(path69.join(claudeDir, "rules"));
|
|
46293
46699
|
const userSettings = path69.join(claudeDir, "settings.json");
|
|
46294
46700
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
@@ -46299,18 +46705,18 @@ function countConfigs(cwd) {
|
|
|
46299
46705
|
userMcpServers.delete(name);
|
|
46300
46706
|
}
|
|
46301
46707
|
if (cwd) {
|
|
46302
|
-
if (
|
|
46303
|
-
if (
|
|
46708
|
+
if (fs73.existsSync(path69.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
46709
|
+
if (fs73.existsSync(path69.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46304
46710
|
const projectClaudeDir = path69.join(cwd, ".claude");
|
|
46305
46711
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
46306
46712
|
if (!overlapsUserScope) {
|
|
46307
|
-
if (
|
|
46713
|
+
if (fs73.existsSync(path69.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46308
46714
|
rulesCount += countRulesInDir(path69.join(projectClaudeDir, "rules"));
|
|
46309
46715
|
const projSettings = path69.join(projectClaudeDir, "settings.json");
|
|
46310
46716
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
46311
46717
|
hooksCount += countHooksInFile(projSettings);
|
|
46312
46718
|
}
|
|
46313
|
-
if (
|
|
46719
|
+
if (fs73.existsSync(path69.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46314
46720
|
const localSettings = path69.join(projectClaudeDir, "settings.local.json");
|
|
46315
46721
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
46316
46722
|
hooksCount += countHooksInFile(localSettings);
|
|
@@ -46348,11 +46754,11 @@ function readActiveShieldsHud() {
|
|
|
46348
46754
|
}
|
|
46349
46755
|
try {
|
|
46350
46756
|
const shieldsPath = path69.join(os62.homedir(), ".node9", "shields.json");
|
|
46351
|
-
if (!
|
|
46757
|
+
if (!fs73.existsSync(shieldsPath)) {
|
|
46352
46758
|
shieldsCache = { value: [], ts: now };
|
|
46353
46759
|
return [];
|
|
46354
46760
|
}
|
|
46355
|
-
const parsed = JSON.parse(
|
|
46761
|
+
const parsed = JSON.parse(fs73.readFileSync(shieldsPath, "utf-8"));
|
|
46356
46762
|
if (!Array.isArray(parsed.active)) {
|
|
46357
46763
|
shieldsCache = { value: [], ts: now };
|
|
46358
46764
|
return [];
|
|
@@ -46454,17 +46860,17 @@ function renderContextLine(stdin) {
|
|
|
46454
46860
|
async function main() {
|
|
46455
46861
|
try {
|
|
46456
46862
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
46457
|
-
if (
|
|
46863
|
+
if (fs73.existsSync(path69.join(os62.homedir(), ".node9", "hud-debug"))) {
|
|
46458
46864
|
try {
|
|
46459
46865
|
const logPath = path69.join(os62.homedir(), ".node9", "hud-debug.log");
|
|
46460
46866
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
46461
46867
|
let size = 0;
|
|
46462
46868
|
try {
|
|
46463
|
-
size =
|
|
46869
|
+
size = fs73.statSync(logPath).size;
|
|
46464
46870
|
} catch {
|
|
46465
46871
|
}
|
|
46466
46872
|
if (size < MAX_LOG_SIZE) {
|
|
46467
|
-
|
|
46873
|
+
fs73.appendFileSync(
|
|
46468
46874
|
logPath,
|
|
46469
46875
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
46470
46876
|
);
|
|
@@ -46488,8 +46894,8 @@ async function main() {
|
|
|
46488
46894
|
path69.join(cwd, "node9.config.json"),
|
|
46489
46895
|
path69.join(os62.homedir(), ".node9", "config.json")
|
|
46490
46896
|
]) {
|
|
46491
|
-
if (!
|
|
46492
|
-
const cfg = JSON.parse(
|
|
46897
|
+
if (!fs73.existsSync(configPath)) continue;
|
|
46898
|
+
const cfg = JSON.parse(fs73.readFileSync(configPath, "utf-8"));
|
|
46493
46899
|
const hud = cfg.settings?.hud;
|
|
46494
46900
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
46495
46901
|
}
|
|
@@ -46631,7 +47037,7 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
|
|
|
46631
47037
|
// src/cli.ts
|
|
46632
47038
|
init_daemon2();
|
|
46633
47039
|
import chalk41 from "chalk";
|
|
46634
|
-
import
|
|
47040
|
+
import fs74 from "fs";
|
|
46635
47041
|
import path70 from "path";
|
|
46636
47042
|
import os63 from "os";
|
|
46637
47043
|
import { spawn as spawn9 } from "child_process";
|
|
@@ -46837,9 +47243,11 @@ function logAutostartSkipThrottled(reason) {
|
|
|
46837
47243
|
if (Date.now() - fs44.statSync(stamp).mtimeMs < SKIP_THROTTLE_MS) return;
|
|
46838
47244
|
} catch {
|
|
46839
47245
|
}
|
|
47246
|
+
const dir = path42.join(os40.homedir(), ".node9");
|
|
47247
|
+
if (!fs44.existsSync(dir)) fs44.mkdirSync(dir, { recursive: true });
|
|
46840
47248
|
fs44.writeFileSync(stamp, "", "utf-8");
|
|
46841
47249
|
fs44.appendFileSync(
|
|
46842
|
-
path42.join(
|
|
47250
|
+
path42.join(dir, "hook-debug.log"),
|
|
46843
47251
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-skip: ${reason}
|
|
46844
47252
|
`,
|
|
46845
47253
|
"utf-8"
|
|
@@ -46858,6 +47266,8 @@ async function autoStartDaemonAndWait() {
|
|
|
46858
47266
|
}
|
|
46859
47267
|
if (!resolvedArgv1.endsWith(".js")) return false;
|
|
46860
47268
|
const startupFd = openStartupLogFd();
|
|
47269
|
+
recordStartupState("starting");
|
|
47270
|
+
let spawned = false;
|
|
46861
47271
|
try {
|
|
46862
47272
|
const child = spawn3(process.execPath, [resolvedArgv1, "daemon"], {
|
|
46863
47273
|
detached: true,
|
|
@@ -46867,13 +47277,24 @@ async function autoStartDaemonAndWait() {
|
|
|
46867
47277
|
NODE9_AUTO_STARTED: "1"
|
|
46868
47278
|
}
|
|
46869
47279
|
});
|
|
47280
|
+
child.on("error", (err2) => {
|
|
47281
|
+
if (readStartupState()?.outcome !== "starting") return;
|
|
47282
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
47283
|
+
});
|
|
46870
47284
|
child.unref();
|
|
47285
|
+
spawned = true;
|
|
46871
47286
|
for (let i = 0; i < 20; i++) {
|
|
46872
47287
|
await new Promise((r) => setTimeout(r, 250));
|
|
46873
47288
|
if (!isDaemonRunning()) continue;
|
|
46874
47289
|
if (await isDaemonReachable()) return true;
|
|
46875
47290
|
}
|
|
46876
|
-
} catch {
|
|
47291
|
+
} catch (err2) {
|
|
47292
|
+
if (!spawned)
|
|
47293
|
+
recordStartupState(
|
|
47294
|
+
"failed",
|
|
47295
|
+
"spawn-failed",
|
|
47296
|
+
err2 instanceof Error ? err2.message : String(err2)
|
|
47297
|
+
);
|
|
46877
47298
|
} finally {
|
|
46878
47299
|
if (startupFd !== void 0) {
|
|
46879
47300
|
try {
|
|
@@ -47686,12 +48107,16 @@ RAW: ${raw}
|
|
|
47686
48107
|
delete safeEnv[key];
|
|
47687
48108
|
}
|
|
47688
48109
|
const startupFd = openStartupLogFd();
|
|
48110
|
+
recordStartupState("starting");
|
|
47689
48111
|
try {
|
|
47690
48112
|
const d = spawn5(process.execPath, [scriptPath, "daemon"], {
|
|
47691
48113
|
detached: true,
|
|
47692
48114
|
stdio: ["ignore", "ignore", startupFd ?? "ignore"],
|
|
47693
48115
|
env: { ...safeEnv, NODE9_AUTO_STARTED: "1" }
|
|
47694
48116
|
});
|
|
48117
|
+
d.on("error", (err2) => {
|
|
48118
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
48119
|
+
});
|
|
47695
48120
|
d.unref();
|
|
47696
48121
|
} finally {
|
|
47697
48122
|
if (startupFd !== void 0) {
|
|
@@ -47704,6 +48129,7 @@ RAW: ${raw}
|
|
|
47704
48129
|
} catch (spawnErr) {
|
|
47705
48130
|
const logPath = path46.join(os44.homedir(), ".node9", "hook-debug.log");
|
|
47706
48131
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
48132
|
+
recordStartupState("failed", "spawn-aborted", msg);
|
|
47707
48133
|
try {
|
|
47708
48134
|
fs48.appendFileSync(
|
|
47709
48135
|
logPath,
|
|
@@ -48868,6 +49294,7 @@ function agoLabel(iso, now = Date.now()) {
|
|
|
48868
49294
|
}
|
|
48869
49295
|
|
|
48870
49296
|
// src/cli/commands/doctor.ts
|
|
49297
|
+
init_startup_log();
|
|
48871
49298
|
function registerDoctorCommand(program2, version2) {
|
|
48872
49299
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
|
|
48873
49300
|
const homeDir2 = os47.homedir();
|
|
@@ -48979,6 +49406,15 @@ function registerDoctorCommand(program2, version2) {
|
|
|
48979
49406
|
"Daemon not running \u2014 terminal & native approvals unavailable",
|
|
48980
49407
|
"Run: node9 daemon --background"
|
|
48981
49408
|
);
|
|
49409
|
+
const cause = readStartupCause();
|
|
49410
|
+
if (cause) {
|
|
49411
|
+
const suffix = cause.detail ? ` \u2014 ${cause.detail}` : "";
|
|
49412
|
+
console.log(
|
|
49413
|
+
chalk11.gray(
|
|
49414
|
+
` ${cause.label} ${agoLabel(cause.at.toISOString())}: ${cause.kind}${suffix}`
|
|
49415
|
+
)
|
|
49416
|
+
);
|
|
49417
|
+
}
|
|
48982
49418
|
}
|
|
48983
49419
|
const autostart = autostartAdvice({
|
|
48984
49420
|
installed: isDaemonServiceInstalled(),
|
|
@@ -49053,6 +49489,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
49053
49489
|
}
|
|
49054
49490
|
|
|
49055
49491
|
// src/cli/commands/audit.ts
|
|
49492
|
+
init_decision();
|
|
49056
49493
|
import chalk12 from "chalk";
|
|
49057
49494
|
import fs53 from "fs";
|
|
49058
49495
|
import path51 from "path";
|
|
@@ -49087,10 +49524,16 @@ function registerAuditCommand(program2) {
|
|
|
49087
49524
|
});
|
|
49088
49525
|
entries = entries.map((e) => ({
|
|
49089
49526
|
...e,
|
|
49090
|
-
|
|
49527
|
+
// classifyDecision is the ONE mapper (audit/decision.ts). The inline
|
|
49528
|
+
// `startsWith('allow') ? allow : deny` this replaces bucketed `dlp` and
|
|
49529
|
+
// `mcp-discovered` — findings, not verdicts — as DENY, inventing
|
|
49530
|
+
// refusals that never happened.
|
|
49531
|
+
// Pass the ROW, never a field pair — the attribution key differs by
|
|
49532
|
+
// producer (`checkedBy` from the gate, `source` from the hook/daemon).
|
|
49533
|
+
view: classifyDecision(e)
|
|
49091
49534
|
}));
|
|
49092
49535
|
if (options.tool) entries = entries.filter((e) => String(e.tool).includes(options.tool));
|
|
49093
|
-
if (options.deny) entries = entries.filter((e) => e.
|
|
49536
|
+
if (options.deny) entries = entries.filter((e) => e.view.outcome === "deny");
|
|
49094
49537
|
const limit = Math.max(1, parseInt(options.tail, 10) || 20);
|
|
49095
49538
|
entries = entries.slice(-limit);
|
|
49096
49539
|
if (options.json) {
|
|
@@ -49113,13 +49556,13 @@ function registerAuditCommand(program2) {
|
|
|
49113
49556
|
for (const e of entries) {
|
|
49114
49557
|
const time = formatRelativeTime(String(e.ts)).padEnd(12);
|
|
49115
49558
|
const tool = String(e.tool).slice(0, 17).padEnd(18);
|
|
49116
|
-
const result = e.
|
|
49559
|
+
const result = e.view.outcome === "allow" ? chalk12.green(e.view.label.padEnd(14)) : e.view.outcome === "deny" ? chalk12.red(e.view.label.padEnd(14)) : e.view.outcome === "observe" ? chalk12.yellow(e.view.label.padEnd(14)) : chalk12.gray(e.view.label.padEnd(14));
|
|
49117
49560
|
const checker = String(e.checkedBy || "unknown").slice(0, 14).padEnd(15);
|
|
49118
49561
|
const agent = String(e.agent || "unknown");
|
|
49119
49562
|
console.log(` ${time} ${tool} ${result} ${checker} ${agent}`);
|
|
49120
49563
|
}
|
|
49121
|
-
const allowed = entries.filter((e) => e.
|
|
49122
|
-
const denied = entries.filter((e) => e.
|
|
49564
|
+
const allowed = entries.filter((e) => e.view.outcome === "allow").length;
|
|
49565
|
+
const denied = entries.filter((e) => e.view.outcome === "deny").length;
|
|
49123
49566
|
console.log(chalk12.dim(" " + "\u2500".repeat(65)));
|
|
49124
49567
|
console.log(
|
|
49125
49568
|
` ${entries.length} entries | ${chalk12.green(allowed + " allowed")} | ${chalk12.red(denied + " denied")}
|
|
@@ -49135,6 +49578,7 @@ import chalk13 from "chalk";
|
|
|
49135
49578
|
init_costSync();
|
|
49136
49579
|
init_litellm();
|
|
49137
49580
|
init_cost_codex();
|
|
49581
|
+
init_decision();
|
|
49138
49582
|
import fs54 from "fs";
|
|
49139
49583
|
import os49 from "os";
|
|
49140
49584
|
import path52 from "path";
|
|
@@ -49228,8 +49672,15 @@ function parseAuditLog(logPath) {
|
|
|
49228
49672
|
}
|
|
49229
49673
|
});
|
|
49230
49674
|
}
|
|
49231
|
-
function
|
|
49232
|
-
|
|
49675
|
+
function viewOf(e) {
|
|
49676
|
+
const outcome = classifyDecision(e).outcome;
|
|
49677
|
+
const observed = outcome === "observe";
|
|
49678
|
+
return {
|
|
49679
|
+
observed,
|
|
49680
|
+
ran: outcome === "allow" || observed,
|
|
49681
|
+
blocked: outcome === "deny" || outcome === "unknown",
|
|
49682
|
+
info: outcome === "info"
|
|
49683
|
+
};
|
|
49233
49684
|
}
|
|
49234
49685
|
function isDlp(checkedBy) {
|
|
49235
49686
|
return !!checkedBy?.includes("dlp");
|
|
@@ -49642,12 +50093,12 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49642
50093
|
const priorStart = new Date(start.getTime() - periodMs);
|
|
49643
50094
|
const priorEntries = allEntries.filter((e) => {
|
|
49644
50095
|
if (e.source === "post-hook") return false;
|
|
50096
|
+
if (e.source === "response-dlp") return false;
|
|
50097
|
+
if (typeof e.decision !== "string") return false;
|
|
49645
50098
|
const ts = new Date(e.ts);
|
|
49646
50099
|
return ts >= priorStart && ts <= priorEnd;
|
|
49647
50100
|
});
|
|
49648
|
-
const priorBlocked = priorEntries.filter(
|
|
49649
|
-
(e) => typeof e.decision === "string" && !isAllow(e.decision)
|
|
49650
|
-
).length;
|
|
50101
|
+
const priorBlocked = priorEntries.filter((e) => viewOf(e).blocked).length;
|
|
49651
50102
|
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
49652
50103
|
const excludeTests = opts.excludeTests === true;
|
|
49653
50104
|
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
@@ -49690,15 +50141,17 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49690
50141
|
let dimInjectionBlocked = 0;
|
|
49691
50142
|
for (const e of entries) {
|
|
49692
50143
|
if (superseded.has(supersedeKey(e))) continue;
|
|
49693
|
-
const
|
|
50144
|
+
const view = viewOf(e);
|
|
49694
50145
|
const dateKey = e.ts.slice(0, 10);
|
|
49695
50146
|
const userInteracted = e.source === "daemon";
|
|
49696
|
-
if (
|
|
49697
|
-
if (
|
|
50147
|
+
if (view.observed) {
|
|
50148
|
+
if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
50149
|
+
} else if (view.info) {
|
|
50150
|
+
} else if (userInteracted) {
|
|
50151
|
+
if (view.ran) userApproved++;
|
|
49698
50152
|
else userDenied++;
|
|
49699
|
-
} else if (
|
|
50153
|
+
} else if (view.blocked) {
|
|
49700
50154
|
if (e.checkedBy === "timeout") timedOut++;
|
|
49701
|
-
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
49702
50155
|
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
49703
50156
|
else if (e.checkedBy === "local-decision") userDenied++;
|
|
49704
50157
|
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
@@ -49710,7 +50163,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49710
50163
|
if (cb.includes("would-block") && (cb.includes("pii") || cb.includes("dlp"))) {
|
|
49711
50164
|
dimDataObserved++;
|
|
49712
50165
|
}
|
|
49713
|
-
if (
|
|
50166
|
+
if (view.blocked && !userInteracted) {
|
|
49714
50167
|
switch (dimensionOfBlock(cb, e.ruleName ?? "")) {
|
|
49715
50168
|
case "network":
|
|
49716
50169
|
dimNetworkBlocked++;
|
|
@@ -49728,15 +50181,15 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49728
50181
|
}
|
|
49729
50182
|
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
49730
50183
|
t.calls++;
|
|
49731
|
-
if (
|
|
50184
|
+
if (view.blocked) t.blocked++;
|
|
49732
50185
|
toolMap.set(e.tool, t);
|
|
49733
|
-
if (
|
|
50186
|
+
if (view.blocked) {
|
|
49734
50187
|
const key = e.checkedBy ?? (e.source === "daemon" ? "local-decision" : null);
|
|
49735
50188
|
if (key) {
|
|
49736
50189
|
blockMap.set(key, (blockMap.get(key) ?? 0) + 1);
|
|
49737
50190
|
}
|
|
49738
50191
|
}
|
|
49739
|
-
if (
|
|
50192
|
+
if ((view.blocked || view.observed) && e.ruleName) {
|
|
49740
50193
|
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
49741
50194
|
}
|
|
49742
50195
|
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
@@ -49745,7 +50198,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49745
50198
|
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
49746
50199
|
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
49747
50200
|
d.calls++;
|
|
49748
|
-
if (
|
|
50201
|
+
if (view.blocked) d.blocked++;
|
|
49749
50202
|
dailyMap.set(dateKey, d);
|
|
49750
50203
|
}
|
|
49751
50204
|
for (const e of allEntries) {
|
|
@@ -50297,9 +50750,11 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
50297
50750
|
}
|
|
50298
50751
|
|
|
50299
50752
|
// src/cli/commands/daemon-cmd.ts
|
|
50753
|
+
init_startup_log();
|
|
50300
50754
|
init_daemon2();
|
|
50301
50755
|
import chalk14 from "chalk";
|
|
50302
50756
|
import { spawn as spawn6 } from "child_process";
|
|
50757
|
+
import fs55 from "fs";
|
|
50303
50758
|
var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
|
|
50304
50759
|
function registerDaemonCommand(program2) {
|
|
50305
50760
|
program2.command("daemon").description("Manage the local approval daemon").argument("[action]", `${VALID_ACTIONS} (default: start)`).option("-b, --background", "Start the daemon in the background (detached)").option(
|
|
@@ -50337,14 +50792,27 @@ function registerDaemonCommand(program2) {
|
|
|
50337
50792
|
if (cmd === "restart") {
|
|
50338
50793
|
stopDaemon();
|
|
50339
50794
|
await new Promise((r) => setTimeout(r, 500));
|
|
50795
|
+
const restartFd = openStartupLogFd();
|
|
50796
|
+
recordStartupState("starting");
|
|
50340
50797
|
const child = spawn6(process.execPath, [process.argv[1], "daemon"], {
|
|
50341
50798
|
detached: true,
|
|
50342
|
-
stdio: "ignore",
|
|
50799
|
+
stdio: ["ignore", "ignore", restartFd ?? "ignore"],
|
|
50343
50800
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
50344
50801
|
});
|
|
50802
|
+
child.on(
|
|
50803
|
+
"error",
|
|
50804
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50805
|
+
);
|
|
50345
50806
|
child.unref();
|
|
50807
|
+
if (restartFd !== void 0) {
|
|
50808
|
+
try {
|
|
50809
|
+
fs55.closeSync(restartFd);
|
|
50810
|
+
} catch {
|
|
50811
|
+
}
|
|
50812
|
+
}
|
|
50346
50813
|
if (child.pid) {
|
|
50347
|
-
console.log(chalk14.green(`\u2713 Daemon
|
|
50814
|
+
console.log(chalk14.green(`\u2713 Daemon relaunching (PID ${child.pid})`));
|
|
50815
|
+
console.log(chalk14.gray(" Confirm with: node9 status"));
|
|
50348
50816
|
} else {
|
|
50349
50817
|
console.error(chalk14.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
|
|
50350
50818
|
process.exit(1);
|
|
@@ -50367,13 +50835,33 @@ function registerDaemonCommand(program2) {
|
|
|
50367
50835
|
return;
|
|
50368
50836
|
}
|
|
50369
50837
|
if (options.background) {
|
|
50370
|
-
const
|
|
50371
|
-
|
|
50372
|
-
|
|
50373
|
-
|
|
50374
|
-
|
|
50375
|
-
|
|
50376
|
-
|
|
50838
|
+
const startupFd = openStartupLogFd();
|
|
50839
|
+
try {
|
|
50840
|
+
recordStartupState("starting");
|
|
50841
|
+
const child = spawn6(process.execPath, [process.argv[1], "daemon"], {
|
|
50842
|
+
detached: true,
|
|
50843
|
+
// Capture the child's stderr: a module-load crash prints its stack there
|
|
50844
|
+
// and dies before it can record anything itself.
|
|
50845
|
+
stdio: ["ignore", "ignore", startupFd ?? "ignore"]
|
|
50846
|
+
});
|
|
50847
|
+
child.on(
|
|
50848
|
+
"error",
|
|
50849
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50850
|
+
);
|
|
50851
|
+
child.unref();
|
|
50852
|
+
console.log(
|
|
50853
|
+
chalk14.green(`
|
|
50854
|
+
\u{1F6E1}\uFE0F Node9 daemon launching in background (PID ${child.pid})`)
|
|
50855
|
+
);
|
|
50856
|
+
console.log(chalk14.gray(" Confirm with: node9 status"));
|
|
50857
|
+
} finally {
|
|
50858
|
+
if (startupFd !== void 0) {
|
|
50859
|
+
try {
|
|
50860
|
+
fs55.closeSync(startupFd);
|
|
50861
|
+
} catch {
|
|
50862
|
+
}
|
|
50863
|
+
}
|
|
50864
|
+
}
|
|
50377
50865
|
process.exit(0);
|
|
50378
50866
|
}
|
|
50379
50867
|
startDaemon();
|
|
@@ -50388,7 +50876,7 @@ init_agent_wiring();
|
|
|
50388
50876
|
init_sync();
|
|
50389
50877
|
init_service();
|
|
50390
50878
|
import chalk15 from "chalk";
|
|
50391
|
-
import
|
|
50879
|
+
import fs56 from "fs";
|
|
50392
50880
|
import path53 from "path";
|
|
50393
50881
|
import os50 from "os";
|
|
50394
50882
|
function printAgentSection(label2, hookPairs, wrapped) {
|
|
@@ -50466,10 +50954,10 @@ function registerStatusCommand(program2) {
|
|
|
50466
50954
|
const projectConfig = path53.join(process.cwd(), "node9.config.json");
|
|
50467
50955
|
const globalConfig = path53.join(os50.homedir(), ".node9", "config.json");
|
|
50468
50956
|
console.log(
|
|
50469
|
-
` Local: ${
|
|
50957
|
+
` Local: ${fs56.existsSync(projectConfig) ? chalk15.green("Active (node9.config.json)") : chalk15.gray("Not present")}`
|
|
50470
50958
|
);
|
|
50471
50959
|
console.log(
|
|
50472
|
-
` Global: ${
|
|
50960
|
+
` Global: ${fs56.existsSync(globalConfig) ? chalk15.green("Active (~/.node9/config.json)") : chalk15.gray("Not present")}`
|
|
50473
50961
|
);
|
|
50474
50962
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
50475
50963
|
console.log(
|
|
@@ -50516,7 +51004,7 @@ init_shields();
|
|
|
50516
51004
|
init_service();
|
|
50517
51005
|
init_core();
|
|
50518
51006
|
import chalk16 from "chalk";
|
|
50519
|
-
import
|
|
51007
|
+
import fs57 from "fs";
|
|
50520
51008
|
import path54 from "path";
|
|
50521
51009
|
import os51 from "os";
|
|
50522
51010
|
import https6 from "https";
|
|
@@ -50605,15 +51093,15 @@ function registerInitCommand(program2) {
|
|
|
50605
51093
|
console.log("");
|
|
50606
51094
|
}
|
|
50607
51095
|
const configPath = path54.join(os51.homedir(), ".node9", "config.json");
|
|
50608
|
-
const isFirstInstall = !
|
|
50609
|
-
if (
|
|
51096
|
+
const isFirstInstall = !fs57.existsSync(configPath);
|
|
51097
|
+
if (fs57.existsSync(configPath) && !options.force) {
|
|
50610
51098
|
try {
|
|
50611
|
-
const existing = JSON.parse(
|
|
51099
|
+
const existing = JSON.parse(fs57.readFileSync(configPath, "utf-8"));
|
|
50612
51100
|
const settings = existing.settings ?? {};
|
|
50613
51101
|
if (settings.mode !== chosenMode) {
|
|
50614
51102
|
settings.mode = chosenMode;
|
|
50615
51103
|
existing.settings = settings;
|
|
50616
|
-
|
|
51104
|
+
fs57.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
50617
51105
|
console.log(chalk16.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
50618
51106
|
} else {
|
|
50619
51107
|
console.log(chalk16.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -50627,8 +51115,8 @@ function registerInitCommand(program2) {
|
|
|
50627
51115
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
50628
51116
|
};
|
|
50629
51117
|
const dir = path54.dirname(configPath);
|
|
50630
|
-
if (!
|
|
50631
|
-
|
|
51118
|
+
if (!fs57.existsSync(dir)) fs57.mkdirSync(dir, { recursive: true });
|
|
51119
|
+
fs57.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
50632
51120
|
console.log(chalk16.green(`\u2705 Config created: ${configPath}`));
|
|
50633
51121
|
console.log(chalk16.gray(` Mode: ${chosenMode}`));
|
|
50634
51122
|
}
|
|
@@ -50731,11 +51219,11 @@ init_agent_wiring();
|
|
|
50731
51219
|
init_setup();
|
|
50732
51220
|
init_hook_baseline();
|
|
50733
51221
|
import chalk17 from "chalk";
|
|
50734
|
-
import
|
|
51222
|
+
import fs58 from "fs";
|
|
50735
51223
|
var hasHookSurface = (a) => a.hooks.length > 0;
|
|
50736
51224
|
function backupForHeal(file) {
|
|
50737
51225
|
try {
|
|
50738
|
-
if (file &&
|
|
51226
|
+
if (file && fs58.existsSync(file)) fs58.copyFileSync(file, `${file}.node9-heal-bak`);
|
|
50739
51227
|
} catch {
|
|
50740
51228
|
}
|
|
50741
51229
|
}
|
|
@@ -51695,16 +52183,17 @@ function registerMcpGatewayCommand(program2) {
|
|
|
51695
52183
|
|
|
51696
52184
|
// src/mcp-server/index.ts
|
|
51697
52185
|
import readline5 from "readline";
|
|
51698
|
-
import
|
|
52186
|
+
import fs60 from "fs";
|
|
51699
52187
|
import os53 from "os";
|
|
51700
52188
|
import path57 from "path";
|
|
51701
52189
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
52190
|
+
init_decision();
|
|
51702
52191
|
init_core();
|
|
51703
52192
|
init_daemon();
|
|
51704
52193
|
init_shields();
|
|
51705
52194
|
|
|
51706
52195
|
// src/auth/egress-config.ts
|
|
51707
|
-
import
|
|
52196
|
+
import fs59 from "fs";
|
|
51708
52197
|
import os52 from "os";
|
|
51709
52198
|
import path56 from "path";
|
|
51710
52199
|
var DEFAULT_EGRESS = {
|
|
@@ -51720,7 +52209,7 @@ function egressConfigPath() {
|
|
|
51720
52209
|
function readEgressRawConfig() {
|
|
51721
52210
|
let text;
|
|
51722
52211
|
try {
|
|
51723
|
-
text =
|
|
52212
|
+
text = fs59.readFileSync(egressConfigPath(), "utf8");
|
|
51724
52213
|
} catch (err2) {
|
|
51725
52214
|
if (err2.code === "ENOENT") return {};
|
|
51726
52215
|
throw err2;
|
|
@@ -51735,8 +52224,8 @@ function readEgressRawConfig() {
|
|
|
51735
52224
|
}
|
|
51736
52225
|
function writeEgressRawConfig(config) {
|
|
51737
52226
|
const p = egressConfigPath();
|
|
51738
|
-
|
|
51739
|
-
|
|
52227
|
+
fs59.mkdirSync(path56.dirname(p), { recursive: true });
|
|
52228
|
+
fs59.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
51740
52229
|
}
|
|
51741
52230
|
function applyEgress(config, change) {
|
|
51742
52231
|
const policy = config.policy = config.policy ?? {};
|
|
@@ -51931,7 +52420,7 @@ var TOOLS = [
|
|
|
51931
52420
|
},
|
|
51932
52421
|
{
|
|
51933
52422
|
name: "node9_audit_get",
|
|
51934
|
-
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, tool name,
|
|
52423
|
+
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.",
|
|
51935
52424
|
inputSchema: {
|
|
51936
52425
|
type: "object",
|
|
51937
52426
|
properties: {
|
|
@@ -51941,8 +52430,8 @@ var TOOLS = [
|
|
|
51941
52430
|
},
|
|
51942
52431
|
filter: {
|
|
51943
52432
|
type: "string",
|
|
51944
|
-
enum: ["all", "
|
|
51945
|
-
description: 'Filter by
|
|
52433
|
+
enum: ["all", "allow", "deny", "observe", "info", "block"],
|
|
52434
|
+
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.'
|
|
51946
52435
|
}
|
|
51947
52436
|
},
|
|
51948
52437
|
required: []
|
|
@@ -52124,10 +52613,10 @@ function handleStatus() {
|
|
|
52124
52613
|
const projectConfig = path57.join(process.cwd(), "node9.config.json");
|
|
52125
52614
|
const globalConfig = path57.join(os53.homedir(), ".node9", "config.json");
|
|
52126
52615
|
lines.push(
|
|
52127
|
-
`Project config (node9.config.json): ${
|
|
52616
|
+
`Project config (node9.config.json): ${fs60.existsSync(projectConfig) ? "present" : "not found"}`
|
|
52128
52617
|
);
|
|
52129
52618
|
lines.push(
|
|
52130
|
-
`Global config (~/.node9/config.json): ${
|
|
52619
|
+
`Global config (~/.node9/config.json): ${fs60.existsSync(globalConfig) ? "present" : "not found"}`
|
|
52131
52620
|
);
|
|
52132
52621
|
return lines.join("\n");
|
|
52133
52622
|
}
|
|
@@ -52237,8 +52726,8 @@ var GLOBAL_CONFIG_PATH = path57.join(os53.homedir(), ".node9", "config.json");
|
|
|
52237
52726
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
52238
52727
|
function readGlobalConfigRaw() {
|
|
52239
52728
|
try {
|
|
52240
|
-
if (
|
|
52241
|
-
return JSON.parse(
|
|
52729
|
+
if (fs60.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
52730
|
+
return JSON.parse(fs60.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
52242
52731
|
}
|
|
52243
52732
|
} catch {
|
|
52244
52733
|
}
|
|
@@ -52246,8 +52735,8 @@ function readGlobalConfigRaw() {
|
|
|
52246
52735
|
}
|
|
52247
52736
|
function writeGlobalConfigRaw(data) {
|
|
52248
52737
|
const dir = path57.dirname(GLOBAL_CONFIG_PATH);
|
|
52249
|
-
if (!
|
|
52250
|
-
|
|
52738
|
+
if (!fs60.existsSync(dir)) fs60.mkdirSync(dir, { recursive: true });
|
|
52739
|
+
fs60.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
|
|
52251
52740
|
}
|
|
52252
52741
|
function handleApproverList() {
|
|
52253
52742
|
const config = getConfig();
|
|
@@ -52292,35 +52781,37 @@ function handleAuditGet(args) {
|
|
|
52292
52781
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
52293
52782
|
const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
|
|
52294
52783
|
const auditPath = path57.join(os53.homedir(), ".node9", "audit.log");
|
|
52295
|
-
if (!
|
|
52296
|
-
const rawLines =
|
|
52784
|
+
if (!fs60.existsSync(auditPath)) return "No audit log found.";
|
|
52785
|
+
const rawLines = fs60.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
52786
|
+
const wanted = filter === "block" ? "deny" : filter;
|
|
52297
52787
|
const parsed = [];
|
|
52298
52788
|
for (const line of rawLines) {
|
|
52299
52789
|
try {
|
|
52300
52790
|
const e = JSON.parse(line);
|
|
52301
|
-
const
|
|
52302
|
-
if (
|
|
52791
|
+
const view = classifyDecision(e);
|
|
52792
|
+
if (wanted && view.outcome !== wanted) continue;
|
|
52303
52793
|
const argsObj = e.args;
|
|
52304
52794
|
let detail = "";
|
|
52305
52795
|
if (argsObj) {
|
|
52306
52796
|
const cmd = argsObj.command ?? argsObj.file_path ?? argsObj.path ?? argsObj.sql;
|
|
52307
|
-
if (typeof cmd === "string" && cmd)
|
|
52308
|
-
detail = cmd.length > 80 ? cmd.slice(0, 80) + "\u2026" : cmd;
|
|
52309
|
-
}
|
|
52797
|
+
if (typeof cmd === "string" && cmd) detail = cmd;
|
|
52310
52798
|
}
|
|
52311
|
-
|
|
52799
|
+
if (!detail && typeof e.argsPreview === "string") detail = e.argsPreview;
|
|
52800
|
+
detail = detail.replace(/\s+/g, " ").trim();
|
|
52801
|
+
if (detail.length > 80) detail = detail.slice(0, 80) + "\u2026";
|
|
52802
|
+
const why = typeof e.ruleName === "string" && e.ruleName ? ` (${e.ruleName})` : "";
|
|
52312
52803
|
const toolPad = String(e.tool ?? "").padEnd(20);
|
|
52313
|
-
const line2 = `${e.ts} ${
|
|
52314
|
-
parsed.push({ raw: line,
|
|
52804
|
+
const line2 = `${e.ts} ${decisionTag(view)} ${toolPad} ${detail}${why}`;
|
|
52805
|
+
parsed.push({ raw: line, outcome: view.outcome, formatted: line2 });
|
|
52315
52806
|
} catch {
|
|
52316
|
-
parsed.push({ raw: line,
|
|
52807
|
+
parsed.push({ raw: line, outcome: "unknown", formatted: `[? unparseable] ${line}` });
|
|
52317
52808
|
}
|
|
52318
52809
|
}
|
|
52319
52810
|
const recent = parsed.slice(-limit);
|
|
52320
52811
|
if (recent.length === 0) {
|
|
52321
|
-
return filter ? `No ${
|
|
52812
|
+
return filter ? `No ${wanted} entries found in audit log.` : "Audit log is empty.";
|
|
52322
52813
|
}
|
|
52323
|
-
const header = filter ? `Last ${recent.length} ${
|
|
52814
|
+
const header = filter ? `Last ${recent.length} ${String(wanted).toUpperCase()} entries:` : `Last ${recent.length} audit entries:`;
|
|
52324
52815
|
return `${header}
|
|
52325
52816
|
|
|
52326
52817
|
${recent.map((e) => e.formatted).join("\n")}`;
|
|
@@ -52667,7 +53158,7 @@ function registerTrustCommand(program2) {
|
|
|
52667
53158
|
// src/cli/commands/mcp-pin.ts
|
|
52668
53159
|
init_mcp_pin();
|
|
52669
53160
|
import chalk24 from "chalk";
|
|
52670
|
-
import
|
|
53161
|
+
import fs61 from "fs";
|
|
52671
53162
|
|
|
52672
53163
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
52673
53164
|
init_mcp_wrap();
|
|
@@ -52863,6 +53354,7 @@ Restart ${[...agents].join(", ")}`) + chalk23.gray(" to activate. Undo any serve
|
|
|
52863
53354
|
}
|
|
52864
53355
|
|
|
52865
53356
|
// src/cli/commands/mcp-pin.ts
|
|
53357
|
+
init_mcp_wrap();
|
|
52866
53358
|
function registerMcpPinCommand(program2) {
|
|
52867
53359
|
const pinCmd = program2.command("mcp").description("Manage MCP servers \u2014 governance (gateway) + tool-definition pinning");
|
|
52868
53360
|
registerMcpGatewayCommand2(pinCmd);
|
|
@@ -52874,7 +53366,7 @@ function registerMcpPinCommand(program2) {
|
|
|
52874
53366
|
let repoCorrupt = false;
|
|
52875
53367
|
if (found.source === "repo") {
|
|
52876
53368
|
try {
|
|
52877
|
-
const raw =
|
|
53369
|
+
const raw = fs61.readFileSync(found.path, "utf-8");
|
|
52878
53370
|
const parsed = JSON.parse(raw);
|
|
52879
53371
|
repoEntries = parsed.servers ?? {};
|
|
52880
53372
|
} catch {
|
|
@@ -52988,6 +53480,88 @@ function registerMcpPinCommand(program2) {
|
|
|
52988
53480
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
52989
53481
|
console.log(chalk24.gray(" Next connection to each server will re-pin.\n"));
|
|
52990
53482
|
});
|
|
53483
|
+
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) => {
|
|
53484
|
+
if (opts.stale) {
|
|
53485
|
+
forgetAllStale();
|
|
53486
|
+
return;
|
|
53487
|
+
}
|
|
53488
|
+
if (!serverKey) {
|
|
53489
|
+
console.error(
|
|
53490
|
+
chalk24.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
53491
|
+
);
|
|
53492
|
+
process.exit(1);
|
|
53493
|
+
}
|
|
53494
|
+
let pins;
|
|
53495
|
+
try {
|
|
53496
|
+
pins = readMcpPins();
|
|
53497
|
+
} catch {
|
|
53498
|
+
console.error(chalk24.red("\n\u274C Pin file is corrupt."));
|
|
53499
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53500
|
+
process.exit(1);
|
|
53501
|
+
}
|
|
53502
|
+
if (!pins.servers[serverKey]) {
|
|
53503
|
+
console.error(chalk24.red(`
|
|
53504
|
+
\u274C No pin found for server key "${serverKey}"
|
|
53505
|
+
`));
|
|
53506
|
+
console.error(`Run ${chalk24.cyan("node9 mcp pin list")} to see pinned servers.
|
|
53507
|
+
`);
|
|
53508
|
+
process.exit(1);
|
|
53509
|
+
}
|
|
53510
|
+
const inv = inventoryMcp();
|
|
53511
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53512
|
+
if (liveKeys.has(serverKey)) {
|
|
53513
|
+
const agent = "an agent config";
|
|
53514
|
+
console.error(chalk24.red(`
|
|
53515
|
+
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
53516
|
+
console.error(
|
|
53517
|
+
chalk24.yellow(
|
|
53518
|
+
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
53519
|
+
`
|
|
53520
|
+
)
|
|
53521
|
+
);
|
|
53522
|
+
process.exit(1);
|
|
53523
|
+
}
|
|
53524
|
+
const label2 = pins.servers[serverKey].label;
|
|
53525
|
+
removePin(serverKey);
|
|
53526
|
+
console.log(chalk24.green(`
|
|
53527
|
+
\u2713 Forgot server ${chalk24.cyan(serverKey)}`));
|
|
53528
|
+
console.log(chalk24.gray(` Was: ${label2}`));
|
|
53529
|
+
console.log(chalk24.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
53530
|
+
});
|
|
53531
|
+
}
|
|
53532
|
+
function forgetAllStale() {
|
|
53533
|
+
let pins;
|
|
53534
|
+
try {
|
|
53535
|
+
pins = readMcpPins();
|
|
53536
|
+
} catch {
|
|
53537
|
+
console.error(chalk24.red("\n\u274C Pin file is corrupt."));
|
|
53538
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53539
|
+
process.exit(1);
|
|
53540
|
+
}
|
|
53541
|
+
const inv = inventoryMcp();
|
|
53542
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53543
|
+
if (liveKeys.size === 0) {
|
|
53544
|
+
console.error(chalk24.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
53545
|
+
console.error(
|
|
53546
|
+
chalk24.yellow(
|
|
53547
|
+
" 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"
|
|
53548
|
+
)
|
|
53549
|
+
);
|
|
53550
|
+
process.exit(1);
|
|
53551
|
+
}
|
|
53552
|
+
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
53553
|
+
if (stale.length === 0) {
|
|
53554
|
+
console.log(chalk24.gray("\nNo stale servers to remove.\n"));
|
|
53555
|
+
return;
|
|
53556
|
+
}
|
|
53557
|
+
for (const [sk, pin] of stale) {
|
|
53558
|
+
delete pins.servers[sk];
|
|
53559
|
+
console.log(chalk24.green(` \u2713 ${chalk24.cyan(sk)} ${chalk24.gray(pin.label)}`));
|
|
53560
|
+
}
|
|
53561
|
+
writeMcpPins(pins);
|
|
53562
|
+
console.log(chalk24.green(`
|
|
53563
|
+
Removed ${stale.length} stale server(s).
|
|
53564
|
+
`));
|
|
52991
53565
|
}
|
|
52992
53566
|
|
|
52993
53567
|
// src/cli/commands/sync.ts
|
|
@@ -53232,10 +53806,16 @@ var LABEL_WIDTH = 14;
|
|
|
53232
53806
|
function label(category) {
|
|
53233
53807
|
return chalk27.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
53234
53808
|
}
|
|
53235
|
-
function
|
|
53809
|
+
function guardedWhat(f) {
|
|
53810
|
+
if (f.detail.length === 0) return "this";
|
|
53811
|
+
const reads = f.coverageProbe?.kind === "fileRead" ? "reads of " : "";
|
|
53812
|
+
const more = f.detail.length > 1 ? ` and ${f.detail.length - 1} more` : "";
|
|
53813
|
+
return `${reads}${f.detail[0]}${more}`;
|
|
53814
|
+
}
|
|
53815
|
+
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
53236
53816
|
const lines = [];
|
|
53237
53817
|
const wt = showWeight && f.scoreWeight ? chalk27.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
53238
|
-
lines.push(` ${ICON[f.severity]} ${label(
|
|
53818
|
+
lines.push(` ${ICON[f.severity]} ${label(displayLabel)}${wt}${f.title}`);
|
|
53239
53819
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
53240
53820
|
const width = 80 - indent.length;
|
|
53241
53821
|
for (const s of [f.what, f.why, f.who]) {
|
|
@@ -53272,11 +53852,11 @@ function renderPosture(result) {
|
|
|
53272
53852
|
);
|
|
53273
53853
|
const headroom = openHeadroom(result.findings);
|
|
53274
53854
|
if (headroom > 0) {
|
|
53275
|
-
|
|
53276
|
-
"
|
|
53277
|
-
|
|
53278
|
-
|
|
53279
|
-
);
|
|
53855
|
+
const openExposures = result.findings.filter(
|
|
53856
|
+
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
53857
|
+
).length;
|
|
53858
|
+
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.`;
|
|
53859
|
+
for (const l of wrap(note, 76)) lines.push(" " + chalk27.gray(l));
|
|
53280
53860
|
}
|
|
53281
53861
|
lines.push("");
|
|
53282
53862
|
if (result.headline) {
|
|
@@ -53289,13 +53869,18 @@ function renderPosture(result) {
|
|
|
53289
53869
|
}
|
|
53290
53870
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
53291
53871
|
const open = result.findings.filter((f) => f.coverage?.state !== "covered");
|
|
53872
|
+
const collision = new Set(
|
|
53873
|
+
covered.map((f) => f.category).filter((c) => open.some((o) => o.category === c))
|
|
53874
|
+
);
|
|
53875
|
+
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
53292
53876
|
if (covered.length > 0) {
|
|
53293
53877
|
lines.push(" " + chalk27.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
53294
53878
|
for (const f of covered) {
|
|
53295
53879
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
53296
53880
|
const via = f.coverage?.via ?? "node9";
|
|
53881
|
+
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
53297
53882
|
lines.push(
|
|
53298
|
-
` ${chalk27.green("\u2705")} ${label(
|
|
53883
|
+
` ${chalk27.green("\u2705")} ${label(lbl)}${chalk27.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
53299
53884
|
);
|
|
53300
53885
|
}
|
|
53301
53886
|
lines.push("");
|
|
@@ -53305,17 +53890,17 @@ function renderPosture(result) {
|
|
|
53305
53890
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
53306
53891
|
if (node9Open.length > 0) {
|
|
53307
53892
|
lines.push(" " + chalk27.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
53308
|
-
for (const f of node9Open) lines.push(...renderFinding(f, true));
|
|
53893
|
+
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53309
53894
|
}
|
|
53310
53895
|
if (reduceOpen.length > 0) {
|
|
53311
53896
|
if (node9Open.length > 0) lines.push("");
|
|
53312
53897
|
lines.push(" " + chalk27.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
53313
|
-
for (const f of reduceOpen) lines.push(...renderFinding(f, true));
|
|
53898
|
+
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53314
53899
|
}
|
|
53315
53900
|
if (osOpen.length > 0) {
|
|
53316
53901
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
53317
53902
|
lines.push(" " + chalk27.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
53318
|
-
for (const f of osOpen) lines.push(...renderFinding(f));
|
|
53903
|
+
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
53319
53904
|
}
|
|
53320
53905
|
for (const cat of result.passedCategories) {
|
|
53321
53906
|
lines.push(` ${chalk27.green("\u2705")} ${label(cat)}${chalk27.gray("no issues found")}`);
|
|
@@ -53375,7 +53960,7 @@ import chalk30 from "chalk";
|
|
|
53375
53960
|
|
|
53376
53961
|
// src/ci-check/fetch.ts
|
|
53377
53962
|
var import_undici = __toESM(require_undici());
|
|
53378
|
-
import
|
|
53963
|
+
import fs62 from "fs";
|
|
53379
53964
|
import path58 from "path";
|
|
53380
53965
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
53381
53966
|
var cachedGhToken;
|
|
@@ -53459,7 +54044,7 @@ function parseRepoUrl(input) {
|
|
|
53459
54044
|
function isLocalPath(input) {
|
|
53460
54045
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) return true;
|
|
53461
54046
|
try {
|
|
53462
|
-
return
|
|
54047
|
+
return fs62.existsSync(input) && fs62.statSync(input).isDirectory();
|
|
53463
54048
|
} catch {
|
|
53464
54049
|
return false;
|
|
53465
54050
|
}
|
|
@@ -53576,8 +54161,8 @@ function readLocalTree(dir) {
|
|
|
53576
54161
|
const add = (rel) => {
|
|
53577
54162
|
const abs = path58.join(root, rel);
|
|
53578
54163
|
try {
|
|
53579
|
-
if (
|
|
53580
|
-
files.push({ path: rel, content:
|
|
54164
|
+
if (fs62.existsSync(abs) && fs62.statSync(abs).isFile()) {
|
|
54165
|
+
files.push({ path: rel, content: fs62.readFileSync(abs, "utf8") });
|
|
53581
54166
|
}
|
|
53582
54167
|
} catch {
|
|
53583
54168
|
}
|
|
@@ -53597,7 +54182,7 @@ function readLocalTree(dir) {
|
|
|
53597
54182
|
dirsVisited++;
|
|
53598
54183
|
let entries;
|
|
53599
54184
|
try {
|
|
53600
|
-
entries =
|
|
54185
|
+
entries = fs62.readdirSync(path58.join(root, relDir), { withFileTypes: true });
|
|
53601
54186
|
} catch {
|
|
53602
54187
|
return;
|
|
53603
54188
|
}
|
|
@@ -53620,8 +54205,8 @@ function readLocalTree(dir) {
|
|
|
53620
54205
|
for (const rel of matches) collect(rel);
|
|
53621
54206
|
const wfDir = path58.join(root, WORKFLOW_DIR);
|
|
53622
54207
|
try {
|
|
53623
|
-
if (
|
|
53624
|
-
for (const name of
|
|
54208
|
+
if (fs62.existsSync(wfDir)) {
|
|
54209
|
+
for (const name of fs62.readdirSync(wfDir)) {
|
|
53625
54210
|
if (/\.ya?ml$/.test(name)) add(path58.join(WORKFLOW_DIR, name));
|
|
53626
54211
|
}
|
|
53627
54212
|
}
|
|
@@ -54762,7 +55347,7 @@ import chalk32 from "chalk";
|
|
|
54762
55347
|
// src/shields/jail.ts
|
|
54763
55348
|
init_build();
|
|
54764
55349
|
init_shields();
|
|
54765
|
-
import
|
|
55350
|
+
import fs63 from "fs";
|
|
54766
55351
|
import os54 from "os";
|
|
54767
55352
|
import path59 from "path";
|
|
54768
55353
|
var USER_JAIL_SHIELD = "user-jail";
|
|
@@ -54772,7 +55357,7 @@ function jailStorePath() {
|
|
|
54772
55357
|
function readJailPaths() {
|
|
54773
55358
|
let text;
|
|
54774
55359
|
try {
|
|
54775
|
-
text =
|
|
55360
|
+
text = fs63.readFileSync(jailStorePath(), "utf8");
|
|
54776
55361
|
} catch (err2) {
|
|
54777
55362
|
if (err2.code === "ENOENT") return [];
|
|
54778
55363
|
throw err2;
|
|
@@ -54790,8 +55375,8 @@ function readJailPaths() {
|
|
|
54790
55375
|
}
|
|
54791
55376
|
function writeJailPaths(paths) {
|
|
54792
55377
|
const p = jailStorePath();
|
|
54793
|
-
|
|
54794
|
-
|
|
55378
|
+
fs63.mkdirSync(path59.dirname(p), { recursive: true });
|
|
55379
|
+
fs63.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
|
|
54795
55380
|
}
|
|
54796
55381
|
function addJailPath(rawPath, verdict) {
|
|
54797
55382
|
const norm = rawPath.trim();
|
|
@@ -54820,7 +55405,7 @@ function regenerateUserJail(paths) {
|
|
|
54820
55405
|
writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
|
|
54821
55406
|
}
|
|
54822
55407
|
try {
|
|
54823
|
-
|
|
55408
|
+
fs63.rmSync(file, { force: true });
|
|
54824
55409
|
} catch {
|
|
54825
55410
|
}
|
|
54826
55411
|
return;
|
|
@@ -54935,12 +55520,12 @@ function registerJailCommand(program2) {
|
|
|
54935
55520
|
// src/cli/commands/sandbox.ts
|
|
54936
55521
|
init_config();
|
|
54937
55522
|
import chalk33 from "chalk";
|
|
54938
|
-
import
|
|
55523
|
+
import fs66 from "fs";
|
|
54939
55524
|
import path62 from "path";
|
|
54940
55525
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
54941
55526
|
|
|
54942
55527
|
// src/sandbox/config.ts
|
|
54943
|
-
import
|
|
55528
|
+
import fs64 from "fs";
|
|
54944
55529
|
import path60 from "path";
|
|
54945
55530
|
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
54946
55531
|
var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
|
|
@@ -55018,12 +55603,12 @@ function sandboxConfigPath(cwd = process.cwd()) {
|
|
|
55018
55603
|
}
|
|
55019
55604
|
function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
|
|
55020
55605
|
const p = sandboxConfigPath(cwd);
|
|
55021
|
-
if (!
|
|
55606
|
+
if (!fs64.existsSync(p)) {
|
|
55022
55607
|
throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
|
|
55023
55608
|
}
|
|
55024
55609
|
let raw;
|
|
55025
55610
|
try {
|
|
55026
|
-
raw = parseYaml2(
|
|
55611
|
+
raw = parseYaml2(fs64.readFileSync(p, "utf-8"));
|
|
55027
55612
|
} catch (err2) {
|
|
55028
55613
|
throw new Error(
|
|
55029
55614
|
`sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
|
|
@@ -55082,7 +55667,7 @@ init_templates();
|
|
|
55082
55667
|
|
|
55083
55668
|
// src/sandbox/runtime.ts
|
|
55084
55669
|
init_templates();
|
|
55085
|
-
import
|
|
55670
|
+
import fs65 from "fs";
|
|
55086
55671
|
import os55 from "os";
|
|
55087
55672
|
import path61 from "path";
|
|
55088
55673
|
import crypto9 from "crypto";
|
|
@@ -55109,7 +55694,7 @@ function buildRunArgs(opts) {
|
|
|
55109
55694
|
args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
|
|
55110
55695
|
if (config.node9.mountAgentCredentials) {
|
|
55111
55696
|
const creds = agentCredentialsMount(config.agent);
|
|
55112
|
-
if (
|
|
55697
|
+
if (fs65.existsSync(creds.hostPath)) {
|
|
55113
55698
|
args.push("-v", `${creds.hostPath}:${creds.target}`);
|
|
55114
55699
|
}
|
|
55115
55700
|
}
|
|
@@ -55131,16 +55716,16 @@ function sandboxBuildDir(cwd = process.cwd()) {
|
|
|
55131
55716
|
}
|
|
55132
55717
|
function writeBuildContext(cwd, dockerfile, entrypoint) {
|
|
55133
55718
|
const dir = sandboxBuildDir(cwd);
|
|
55134
|
-
|
|
55135
|
-
|
|
55136
|
-
|
|
55719
|
+
fs65.mkdirSync(dir, { recursive: true });
|
|
55720
|
+
fs65.writeFileSync(path61.join(dir, "Dockerfile"), dockerfile);
|
|
55721
|
+
fs65.writeFileSync(path61.join(dir, "entrypoint.sh"), entrypoint);
|
|
55137
55722
|
return dir;
|
|
55138
55723
|
}
|
|
55139
55724
|
function writeAllowlist(cwd, hosts) {
|
|
55140
55725
|
const dir = path61.join(cwd, ".node9", "sandbox");
|
|
55141
|
-
|
|
55726
|
+
fs65.mkdirSync(dir, { recursive: true });
|
|
55142
55727
|
const p = path61.join(dir, "allowed-domains.txt");
|
|
55143
|
-
|
|
55728
|
+
fs65.writeFileSync(p, hosts.join("\n") + "\n");
|
|
55144
55729
|
return p;
|
|
55145
55730
|
}
|
|
55146
55731
|
function resolveHomePath(p) {
|
|
@@ -55149,7 +55734,7 @@ function resolveHomePath(p) {
|
|
|
55149
55734
|
|
|
55150
55735
|
// src/cli/commands/sandbox.ts
|
|
55151
55736
|
function seedDataDirConfig(dataDir, sandbox) {
|
|
55152
|
-
|
|
55737
|
+
fs66.mkdirSync(dataDir, { recursive: true });
|
|
55153
55738
|
const configPath = path62.join(dataDir, "config.json");
|
|
55154
55739
|
const seed = {
|
|
55155
55740
|
settings: {
|
|
@@ -55161,7 +55746,7 @@ function seedDataDirConfig(dataDir, sandbox) {
|
|
|
55161
55746
|
}
|
|
55162
55747
|
}
|
|
55163
55748
|
};
|
|
55164
|
-
|
|
55749
|
+
fs66.writeFileSync(configPath, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
55165
55750
|
}
|
|
55166
55751
|
function registerSandboxCommand(program2, version2) {
|
|
55167
55752
|
const node9Version2 = pinnedNode9Version(version2);
|
|
@@ -55169,13 +55754,13 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55169
55754
|
cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
|
|
55170
55755
|
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
55171
55756
|
const p = sandboxConfigPath();
|
|
55172
|
-
if (
|
|
55757
|
+
if (fs66.existsSync(p)) {
|
|
55173
55758
|
console.log(
|
|
55174
55759
|
chalk33.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
55175
55760
|
);
|
|
55176
55761
|
return;
|
|
55177
55762
|
}
|
|
55178
|
-
|
|
55763
|
+
fs66.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
55179
55764
|
console.log(
|
|
55180
55765
|
chalk33.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + chalk33.dim(` (agent: ${agent})`)
|
|
55181
55766
|
);
|
|
@@ -55216,7 +55801,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55216
55801
|
const hash = imageContentHash(dockerfile, entrypoint);
|
|
55217
55802
|
const image = sandbox.runtime.image;
|
|
55218
55803
|
const hashFile = path62.join(sandboxBuildDir(cwd), ".image-hash");
|
|
55219
|
-
const lastHash =
|
|
55804
|
+
const lastHash = fs66.existsSync(hashFile) ? fs66.readFileSync(hashFile, "utf-8").trim() : "";
|
|
55220
55805
|
const imageExists = spawnSync6(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
55221
55806
|
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
55222
55807
|
if (needBuild) {
|
|
@@ -55228,7 +55813,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55228
55813
|
console.error(chalk33.red(" build failed."));
|
|
55229
55814
|
process.exit(b.status ?? 1);
|
|
55230
55815
|
}
|
|
55231
|
-
|
|
55816
|
+
fs66.writeFileSync(hashFile, hash);
|
|
55232
55817
|
}
|
|
55233
55818
|
const dataDir = sandboxDataDir(cwd);
|
|
55234
55819
|
seedDataDirConfig(dataDir, sandbox);
|
|
@@ -55242,7 +55827,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55242
55827
|
});
|
|
55243
55828
|
if (sandbox.node9.mountAgentCredentials) {
|
|
55244
55829
|
const creds = agentCredentialsMount(sandbox.agent);
|
|
55245
|
-
if (
|
|
55830
|
+
if (fs66.existsSync(creds.hostPath)) {
|
|
55246
55831
|
console.log(chalk33.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
55247
55832
|
} else {
|
|
55248
55833
|
console.log(
|
|
@@ -55259,7 +55844,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55259
55844
|
});
|
|
55260
55845
|
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
55261
55846
|
const auditPath = path62.join(sandboxDataDir(), "audit.log");
|
|
55262
|
-
if (!
|
|
55847
|
+
if (!fs66.existsSync(auditPath)) {
|
|
55263
55848
|
console.log(chalk33.dim(" no sandbox audit yet."));
|
|
55264
55849
|
return;
|
|
55265
55850
|
}
|
|
@@ -55267,11 +55852,11 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55267
55852
|
});
|
|
55268
55853
|
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
55269
55854
|
const auditPath = path62.join(sandboxDataDir(), "audit.log");
|
|
55270
|
-
if (!
|
|
55855
|
+
if (!fs66.existsSync(auditPath)) {
|
|
55271
55856
|
console.log(chalk33.dim(" no sandbox audit yet."));
|
|
55272
55857
|
return;
|
|
55273
55858
|
}
|
|
55274
|
-
process.stdout.write(
|
|
55859
|
+
process.stdout.write(fs66.readFileSync(auditPath, "utf-8"));
|
|
55275
55860
|
});
|
|
55276
55861
|
cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
|
|
55277
55862
|
const cwd = process.cwd();
|
|
@@ -55285,18 +55870,19 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55285
55870
|
stdio: "ignore"
|
|
55286
55871
|
});
|
|
55287
55872
|
}
|
|
55288
|
-
|
|
55873
|
+
fs66.rmSync(path62.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
55289
55874
|
console.log(chalk33.green(" \u2713 sandbox image + build + data removed."));
|
|
55290
55875
|
});
|
|
55291
55876
|
}
|
|
55292
55877
|
|
|
55293
55878
|
// src/cli/commands/sessions.ts
|
|
55879
|
+
init_decision();
|
|
55294
55880
|
init_scan_summary();
|
|
55295
55881
|
init_litellm();
|
|
55296
55882
|
init_cost_gemini();
|
|
55297
55883
|
init_cost_codex();
|
|
55298
55884
|
import chalk34 from "chalk";
|
|
55299
|
-
import
|
|
55885
|
+
import fs67 from "fs";
|
|
55300
55886
|
import path63 from "path";
|
|
55301
55887
|
import os56 from "os";
|
|
55302
55888
|
function modelPrice(model) {
|
|
@@ -55390,7 +55976,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55390
55976
|
const aPath = auditPath ?? path63.join(os56.homedir(), ".node9", "audit.log");
|
|
55391
55977
|
let raw;
|
|
55392
55978
|
try {
|
|
55393
|
-
raw =
|
|
55979
|
+
raw = fs67.readFileSync(aPath, "utf-8");
|
|
55394
55980
|
} catch {
|
|
55395
55981
|
return [];
|
|
55396
55982
|
}
|
|
@@ -55400,7 +55986,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55400
55986
|
try {
|
|
55401
55987
|
const e = JSON.parse(line);
|
|
55402
55988
|
if (!e.ts || !e.tool || !e.decision) continue;
|
|
55403
|
-
if (e.
|
|
55989
|
+
if (classifyDecision(e).outcome === "allow") continue;
|
|
55404
55990
|
entries.push(e);
|
|
55405
55991
|
} catch {
|
|
55406
55992
|
}
|
|
@@ -55427,7 +56013,7 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
55427
56013
|
}
|
|
55428
56014
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
55429
56015
|
const tmpDir = path63.join(os56.homedir(), ".gemini", "tmp");
|
|
55430
|
-
if (!
|
|
56016
|
+
if (!fs67.existsSync(tmpDir)) return [];
|
|
55431
56017
|
const cutoff = days !== null ? (() => {
|
|
55432
56018
|
const d = /* @__PURE__ */ new Date();
|
|
55433
56019
|
d.setDate(d.getDate() - days);
|
|
@@ -55436,7 +56022,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55436
56022
|
})() : null;
|
|
55437
56023
|
let slugDirs;
|
|
55438
56024
|
try {
|
|
55439
|
-
slugDirs =
|
|
56025
|
+
slugDirs = fs67.readdirSync(tmpDir);
|
|
55440
56026
|
} catch {
|
|
55441
56027
|
return [];
|
|
55442
56028
|
}
|
|
@@ -55444,27 +56030,27 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55444
56030
|
for (const slug2 of slugDirs) {
|
|
55445
56031
|
const slugPath = path63.join(tmpDir, slug2);
|
|
55446
56032
|
try {
|
|
55447
|
-
if (!
|
|
56033
|
+
if (!fs67.statSync(slugPath).isDirectory()) continue;
|
|
55448
56034
|
} catch {
|
|
55449
56035
|
continue;
|
|
55450
56036
|
}
|
|
55451
56037
|
let projectRoot = path63.join(os56.homedir(), slug2);
|
|
55452
56038
|
try {
|
|
55453
|
-
projectRoot =
|
|
56039
|
+
projectRoot = fs67.readFileSync(path63.join(slugPath, ".project_root"), "utf-8").trim();
|
|
55454
56040
|
} catch {
|
|
55455
56041
|
}
|
|
55456
56042
|
const chatsDir = path63.join(slugPath, "chats");
|
|
55457
|
-
if (!
|
|
56043
|
+
if (!fs67.existsSync(chatsDir)) continue;
|
|
55458
56044
|
let chatFiles;
|
|
55459
56045
|
try {
|
|
55460
|
-
chatFiles =
|
|
56046
|
+
chatFiles = fs67.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
55461
56047
|
} catch {
|
|
55462
56048
|
continue;
|
|
55463
56049
|
}
|
|
55464
56050
|
for (const chatFile of chatFiles) {
|
|
55465
56051
|
let raw;
|
|
55466
56052
|
try {
|
|
55467
|
-
raw =
|
|
56053
|
+
raw = fs67.readFileSync(path63.join(chatsDir, chatFile), "utf-8");
|
|
55468
56054
|
} catch {
|
|
55469
56055
|
continue;
|
|
55470
56056
|
}
|
|
@@ -55545,7 +56131,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55545
56131
|
}
|
|
55546
56132
|
function buildCodexSessions(days, allAuditEntries) {
|
|
55547
56133
|
const sessionsBase = path63.join(os56.homedir(), ".codex", "sessions");
|
|
55548
|
-
if (!
|
|
56134
|
+
if (!fs67.existsSync(sessionsBase)) return [];
|
|
55549
56135
|
const cutoff = days !== null ? (() => {
|
|
55550
56136
|
const d = /* @__PURE__ */ new Date();
|
|
55551
56137
|
d.setDate(d.getDate() - days);
|
|
@@ -55554,28 +56140,28 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55554
56140
|
})() : null;
|
|
55555
56141
|
const jsonlFiles = [];
|
|
55556
56142
|
try {
|
|
55557
|
-
for (const year of
|
|
56143
|
+
for (const year of fs67.readdirSync(sessionsBase)) {
|
|
55558
56144
|
const yearPath = path63.join(sessionsBase, year);
|
|
55559
56145
|
try {
|
|
55560
|
-
if (!
|
|
56146
|
+
if (!fs67.statSync(yearPath).isDirectory()) continue;
|
|
55561
56147
|
} catch {
|
|
55562
56148
|
continue;
|
|
55563
56149
|
}
|
|
55564
|
-
for (const month of
|
|
56150
|
+
for (const month of fs67.readdirSync(yearPath)) {
|
|
55565
56151
|
const monthPath = path63.join(yearPath, month);
|
|
55566
56152
|
try {
|
|
55567
|
-
if (!
|
|
56153
|
+
if (!fs67.statSync(monthPath).isDirectory()) continue;
|
|
55568
56154
|
} catch {
|
|
55569
56155
|
continue;
|
|
55570
56156
|
}
|
|
55571
|
-
for (const day of
|
|
56157
|
+
for (const day of fs67.readdirSync(monthPath)) {
|
|
55572
56158
|
const dayPath = path63.join(monthPath, day);
|
|
55573
56159
|
try {
|
|
55574
|
-
if (!
|
|
56160
|
+
if (!fs67.statSync(dayPath).isDirectory()) continue;
|
|
55575
56161
|
} catch {
|
|
55576
56162
|
continue;
|
|
55577
56163
|
}
|
|
55578
|
-
for (const file of
|
|
56164
|
+
for (const file of fs67.readdirSync(dayPath)) {
|
|
55579
56165
|
if (file.endsWith(".jsonl")) jsonlFiles.push(path63.join(dayPath, file));
|
|
55580
56166
|
}
|
|
55581
56167
|
}
|
|
@@ -55588,7 +56174,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55588
56174
|
for (const filePath of jsonlFiles) {
|
|
55589
56175
|
let lines;
|
|
55590
56176
|
try {
|
|
55591
|
-
lines =
|
|
56177
|
+
lines = fs67.readFileSync(filePath, "utf-8").split("\n");
|
|
55592
56178
|
} catch {
|
|
55593
56179
|
continue;
|
|
55594
56180
|
}
|
|
@@ -55677,7 +56263,7 @@ function buildSessions(days, historyPath) {
|
|
|
55677
56263
|
const hPath = historyPath ?? path63.join(os56.homedir(), ".claude", "history.jsonl");
|
|
55678
56264
|
let historyRaw = "";
|
|
55679
56265
|
try {
|
|
55680
|
-
historyRaw =
|
|
56266
|
+
historyRaw = fs67.readFileSync(hPath, "utf-8");
|
|
55681
56267
|
} catch {
|
|
55682
56268
|
}
|
|
55683
56269
|
const cutoff = days !== null ? (() => {
|
|
@@ -55701,7 +56287,7 @@ function buildSessions(days, historyPath) {
|
|
|
55701
56287
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
55702
56288
|
let sessionLines = [];
|
|
55703
56289
|
try {
|
|
55704
|
-
sessionLines =
|
|
56290
|
+
sessionLines = fs67.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
55705
56291
|
} catch {
|
|
55706
56292
|
}
|
|
55707
56293
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -56095,12 +56681,12 @@ function registerSessionTaintCommand(program2) {
|
|
|
56095
56681
|
|
|
56096
56682
|
// src/cli/commands/skill-pin.ts
|
|
56097
56683
|
import chalk36 from "chalk";
|
|
56098
|
-
import
|
|
56684
|
+
import fs68 from "fs";
|
|
56099
56685
|
import os57 from "os";
|
|
56100
56686
|
import path64 from "path";
|
|
56101
56687
|
function wipeSkillSessions() {
|
|
56102
56688
|
try {
|
|
56103
|
-
|
|
56689
|
+
fs68.rmSync(path64.join(os57.homedir(), ".node9", "skill-sessions"), {
|
|
56104
56690
|
recursive: true,
|
|
56105
56691
|
force: true
|
|
56106
56692
|
});
|
|
@@ -56182,15 +56768,15 @@ function registerSkillPinCommand(program2) {
|
|
|
56182
56768
|
}
|
|
56183
56769
|
|
|
56184
56770
|
// src/cli/commands/decisions.ts
|
|
56185
|
-
import
|
|
56771
|
+
import fs69 from "fs";
|
|
56186
56772
|
import os58 from "os";
|
|
56187
56773
|
import path65 from "path";
|
|
56188
56774
|
import chalk37 from "chalk";
|
|
56189
56775
|
var DECISIONS_FILE2 = path65.join(os58.homedir(), ".node9", "decisions.json");
|
|
56190
56776
|
function readDecisions() {
|
|
56191
56777
|
try {
|
|
56192
|
-
if (!
|
|
56193
|
-
const raw =
|
|
56778
|
+
if (!fs69.existsSync(DECISIONS_FILE2)) return {};
|
|
56779
|
+
const raw = fs69.readFileSync(DECISIONS_FILE2, "utf-8");
|
|
56194
56780
|
const parsed = JSON.parse(raw);
|
|
56195
56781
|
const out = {};
|
|
56196
56782
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -56203,10 +56789,10 @@ function readDecisions() {
|
|
|
56203
56789
|
}
|
|
56204
56790
|
function writeDecisions(d) {
|
|
56205
56791
|
const dir = path65.dirname(DECISIONS_FILE2);
|
|
56206
|
-
if (!
|
|
56792
|
+
if (!fs69.existsSync(dir)) fs69.mkdirSync(dir, { recursive: true });
|
|
56207
56793
|
const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
|
|
56208
|
-
|
|
56209
|
-
|
|
56794
|
+
fs69.writeFileSync(tmp, JSON.stringify(d, null, 2));
|
|
56795
|
+
fs69.renameSync(tmp, DECISIONS_FILE2);
|
|
56210
56796
|
}
|
|
56211
56797
|
function registerDecisionsCommand(program2) {
|
|
56212
56798
|
const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
|
|
@@ -56263,7 +56849,7 @@ Persistent decisions (${entries.length})
|
|
|
56263
56849
|
|
|
56264
56850
|
// src/cli/commands/dlp.ts
|
|
56265
56851
|
import chalk38 from "chalk";
|
|
56266
|
-
import
|
|
56852
|
+
import fs70 from "fs";
|
|
56267
56853
|
import path66 from "path";
|
|
56268
56854
|
import os59 from "os";
|
|
56269
56855
|
var AUDIT_LOG = path66.join(os59.homedir(), ".node9", "audit.log");
|
|
@@ -56274,7 +56860,7 @@ function stripAnsi(s) {
|
|
|
56274
56860
|
}
|
|
56275
56861
|
function loadResolved() {
|
|
56276
56862
|
try {
|
|
56277
|
-
const raw = JSON.parse(
|
|
56863
|
+
const raw = JSON.parse(fs70.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
56278
56864
|
return new Set(raw);
|
|
56279
56865
|
} catch {
|
|
56280
56866
|
return /* @__PURE__ */ new Set();
|
|
@@ -56282,13 +56868,13 @@ function loadResolved() {
|
|
|
56282
56868
|
}
|
|
56283
56869
|
function saveResolved(resolved) {
|
|
56284
56870
|
try {
|
|
56285
|
-
|
|
56871
|
+
fs70.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
56286
56872
|
} catch {
|
|
56287
56873
|
}
|
|
56288
56874
|
}
|
|
56289
56875
|
function loadDlpFindings() {
|
|
56290
|
-
if (!
|
|
56291
|
-
return
|
|
56876
|
+
if (!fs70.existsSync(AUDIT_LOG)) return [];
|
|
56877
|
+
return fs70.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
56292
56878
|
if (!line.trim()) return [];
|
|
56293
56879
|
try {
|
|
56294
56880
|
const e = JSON.parse(line);
|
|
@@ -56387,13 +56973,13 @@ function registerDlpCommand(program2) {
|
|
|
56387
56973
|
// src/cli/commands/mask.ts
|
|
56388
56974
|
init_dlp();
|
|
56389
56975
|
import chalk39 from "chalk";
|
|
56390
|
-
import
|
|
56976
|
+
import fs71 from "fs";
|
|
56391
56977
|
import path67 from "path";
|
|
56392
56978
|
import os60 from "os";
|
|
56393
56979
|
function findJsonlFiles(dir) {
|
|
56394
56980
|
const results = [];
|
|
56395
|
-
if (!
|
|
56396
|
-
for (const entry of
|
|
56981
|
+
if (!fs71.existsSync(dir)) return results;
|
|
56982
|
+
for (const entry of fs71.readdirSync(dir, { withFileTypes: true })) {
|
|
56397
56983
|
const full = path67.join(dir, entry.name);
|
|
56398
56984
|
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
56399
56985
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
@@ -56437,7 +57023,7 @@ function redactJson(obj) {
|
|
|
56437
57023
|
function processFile(filePath, dryRun) {
|
|
56438
57024
|
let raw;
|
|
56439
57025
|
try {
|
|
56440
|
-
raw =
|
|
57026
|
+
raw = fs71.readFileSync(filePath, "utf-8");
|
|
56441
57027
|
} catch {
|
|
56442
57028
|
return { redactedLines: 0, patterns: [] };
|
|
56443
57029
|
}
|
|
@@ -56469,14 +57055,14 @@ function processFile(filePath, dryRun) {
|
|
|
56469
57055
|
}
|
|
56470
57056
|
}
|
|
56471
57057
|
if (!dryRun && redactedLines > 0) {
|
|
56472
|
-
|
|
57058
|
+
fs71.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
56473
57059
|
}
|
|
56474
57060
|
return { redactedLines, patterns };
|
|
56475
57061
|
}
|
|
56476
57062
|
function processJsonFile(filePath, dryRun) {
|
|
56477
57063
|
let raw;
|
|
56478
57064
|
try {
|
|
56479
|
-
raw =
|
|
57065
|
+
raw = fs71.readFileSync(filePath, "utf-8");
|
|
56480
57066
|
} catch {
|
|
56481
57067
|
return { redactedLines: 0, patterns: [] };
|
|
56482
57068
|
}
|
|
@@ -56489,14 +57075,14 @@ function processJsonFile(filePath, dryRun) {
|
|
|
56489
57075
|
const { value, modified, found } = redactJson(parsed);
|
|
56490
57076
|
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
56491
57077
|
if (!dryRun) {
|
|
56492
|
-
|
|
57078
|
+
fs71.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
56493
57079
|
}
|
|
56494
57080
|
return { redactedLines: 1, patterns: found };
|
|
56495
57081
|
}
|
|
56496
57082
|
function findJsonFiles(dir) {
|
|
56497
57083
|
const results = [];
|
|
56498
|
-
if (!
|
|
56499
|
-
for (const entry of
|
|
57084
|
+
if (!fs71.existsSync(dir)) return results;
|
|
57085
|
+
for (const entry of fs71.readdirSync(dir, { withFileTypes: true })) {
|
|
56500
57086
|
const full = path67.join(dir, entry.name);
|
|
56501
57087
|
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
56502
57088
|
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
@@ -56516,7 +57102,7 @@ function registerMaskCommand(program2) {
|
|
|
56516
57102
|
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
56517
57103
|
const filtered = cutoff ? allFiles.filter((f) => {
|
|
56518
57104
|
try {
|
|
56519
|
-
return
|
|
57105
|
+
return fs71.statSync(f.path).mtime >= cutoff;
|
|
56520
57106
|
} catch {
|
|
56521
57107
|
return false;
|
|
56522
57108
|
}
|
|
@@ -56572,7 +57158,7 @@ function registerMaskCommand(program2) {
|
|
|
56572
57158
|
// src/cli.ts
|
|
56573
57159
|
init_blast();
|
|
56574
57160
|
var { version } = JSON.parse(
|
|
56575
|
-
|
|
57161
|
+
fs74.readFileSync(path70.join(__dirname, "../package.json"), "utf-8")
|
|
56576
57162
|
);
|
|
56577
57163
|
var program = new Command();
|
|
56578
57164
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
@@ -56752,14 +57338,14 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
56752
57338
|
}
|
|
56753
57339
|
if (options.purge) {
|
|
56754
57340
|
const node9Dir = path70.join(os63.homedir(), ".node9");
|
|
56755
|
-
if (
|
|
57341
|
+
if (fs74.existsSync(node9Dir)) {
|
|
56756
57342
|
const confirmed = await confirm2({
|
|
56757
57343
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
56758
57344
|
default: false
|
|
56759
57345
|
});
|
|
56760
57346
|
if (confirmed) {
|
|
56761
|
-
|
|
56762
|
-
if (
|
|
57347
|
+
fs74.rmSync(node9Dir, { recursive: true });
|
|
57348
|
+
if (fs74.existsSync(node9Dir)) {
|
|
56763
57349
|
console.error(
|
|
56764
57350
|
chalk41.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
56765
57351
|
);
|
|
@@ -56924,12 +57510,12 @@ Run "node9 addto claude" to register it as the statusLine.`
|
|
|
56924
57510
|
if (subcommand === "debug") {
|
|
56925
57511
|
const flagFile = path70.join(os63.homedir(), ".node9", "hud-debug");
|
|
56926
57512
|
if (state === "on") {
|
|
56927
|
-
|
|
56928
|
-
|
|
57513
|
+
fs74.mkdirSync(path70.dirname(flagFile), { recursive: true });
|
|
57514
|
+
fs74.writeFileSync(flagFile, "");
|
|
56929
57515
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
56930
57516
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
56931
57517
|
} else if (state === "off") {
|
|
56932
|
-
if (
|
|
57518
|
+
if (fs74.existsSync(flagFile)) fs74.unlinkSync(flagFile);
|
|
56933
57519
|
console.log("HUD debug logging disabled.");
|
|
56934
57520
|
} else {
|
|
56935
57521
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -57054,7 +57640,7 @@ if (process.argv[2] !== "daemon") {
|
|
|
57054
57640
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
57055
57641
|
const logPath = path70.join(os63.homedir(), ".node9", "hook-debug.log");
|
|
57056
57642
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
57057
|
-
|
|
57643
|
+
fs74.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
57058
57644
|
`);
|
|
57059
57645
|
}
|
|
57060
57646
|
process.exit(0);
|