@node9/proxy 1.63.0 → 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 +908 -336
- package/dist/cli.mjs +906 -334
- package/dist/dashboard.mjs +82 -25
- package/dist/index.js +144 -39
- package/dist/index.mjs +144 -39
- package/dist/scan-ink.mjs +6 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -343,6 +343,7 @@ var init_config_schema = __esm({
|
|
|
343
343
|
// nudge-only (default), and the scan cadence in minutes.
|
|
344
344
|
mcpAutoWrap: import_zod.z.boolean().optional(),
|
|
345
345
|
mcpReconcileIntervalMinutes: import_zod.z.number().positive().optional(),
|
|
346
|
+
mcpStaleAfterDays: import_zod.z.number().min(0).optional(),
|
|
346
347
|
cloudSyncIntervalHours: import_zod.z.number().positive().optional(),
|
|
347
348
|
// Seconds-granular override for the cloud policy sync cadence. Wins over
|
|
348
349
|
// cloudSyncIntervalHours when set. Lets you opt into fast apply (e.g. 20)
|
|
@@ -3977,6 +3978,7 @@ var init_dist = __esm({
|
|
|
3977
3978
|
name: "redis",
|
|
3978
3979
|
description: "Protects Redis instances from destructive AI operations",
|
|
3979
3980
|
aliases: [],
|
|
3981
|
+
_scopeNote: "Every rule requires Redis to actually be involved: a client in the command (redis-cli / valkey-cli, in either order \u2014 `redis-cli FLUSHALL` and `echo FLUSHALL | redis-cli` are both real), OR the command field holding the bare keyword, which is what the MCP redis_command tool sends ({ command: 'FLUSHALL', args: [] }). Without the client requirement these rules matched the WORD anywhere \u2014 including prose in a heredoc writing a file \u2014 which produced two observed false positives: a test fixture describing the shield, and a commit message naming the rule. Every other builtin shield already scopes this way (aws s3, docker system prune, kubectl delete, gh repo delete). See redis-scope.spec.ts.",
|
|
3980
3982
|
smartRules: [
|
|
3981
3983
|
{
|
|
3982
3984
|
name: "shield:redis:block-flushall",
|
|
@@ -3985,7 +3987,7 @@ var init_dist = __esm({
|
|
|
3985
3987
|
{
|
|
3986
3988
|
field: "command",
|
|
3987
3989
|
op: "matches",
|
|
3988
|
-
value: "\\bFLUSHALL\\b",
|
|
3990
|
+
value: "(redis|valkey)-cli.*\\bFLUSHALL\\b|\\bFLUSHALL\\b.*(redis|valkey)-cli|^ ?FLUSHALL\\b|\\.flushall\\s*\\(",
|
|
3989
3991
|
flags: "i"
|
|
3990
3992
|
}
|
|
3991
3993
|
],
|
|
@@ -3999,7 +4001,7 @@ var init_dist = __esm({
|
|
|
3999
4001
|
{
|
|
4000
4002
|
field: "command",
|
|
4001
4003
|
op: "matches",
|
|
4002
|
-
value: "\\bFLUSHDB\\b",
|
|
4004
|
+
value: "(redis|valkey)-cli.*\\bFLUSHDB\\b|\\bFLUSHDB\\b.*(redis|valkey)-cli|^ ?FLUSHDB\\b|\\.flushdb\\s*\\(",
|
|
4003
4005
|
flags: "i"
|
|
4004
4006
|
}
|
|
4005
4007
|
],
|
|
@@ -4013,7 +4015,7 @@ var init_dist = __esm({
|
|
|
4013
4015
|
{
|
|
4014
4016
|
field: "command",
|
|
4015
4017
|
op: "matches",
|
|
4016
|
-
value: "\\
|
|
4018
|
+
value: "(redis|valkey)-cli.*CONFIG\\s+RESETSTAT|^ ?CONFIG\\s+RESETSTAT",
|
|
4017
4019
|
flags: "i"
|
|
4018
4020
|
}
|
|
4019
4021
|
],
|
|
@@ -4027,7 +4029,7 @@ var init_dist = __esm({
|
|
|
4027
4029
|
{
|
|
4028
4030
|
field: "command",
|
|
4029
4031
|
op: "matches",
|
|
4030
|
-
value: "\\bCONFIG\\s+SET\\b",
|
|
4032
|
+
value: "(redis|valkey)-cli.*\\bCONFIG\\s+SET\\b|\\bCONFIG\\s+SET\\b.*(redis|valkey)-cli|^ ?CONFIG\\s+SET\\b",
|
|
4031
4033
|
flags: "i"
|
|
4032
4034
|
}
|
|
4033
4035
|
],
|
|
@@ -4041,7 +4043,7 @@ var init_dist = __esm({
|
|
|
4041
4043
|
{
|
|
4042
4044
|
field: "command",
|
|
4043
4045
|
op: "matches",
|
|
4044
|
-
value: "
|
|
4046
|
+
value: "(redis|valkey)-cli.*\\bDEL\\b.*[*?\\[]|^ ?DEL\\b.*[*?\\[]|-cli.*--scan.*xargs.*del",
|
|
4045
4047
|
flags: "i"
|
|
4046
4048
|
}
|
|
4047
4049
|
],
|
|
@@ -4608,6 +4610,48 @@ function getActiveEnvironment(config) {
|
|
|
4608
4610
|
const env = config.settings.environment || process.env.NODE_ENV || "development";
|
|
4609
4611
|
return config.environments[env] ?? null;
|
|
4610
4612
|
}
|
|
4613
|
+
function readRulesCacheResilient(cacheFile) {
|
|
4614
|
+
let existed = false;
|
|
4615
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
4616
|
+
let content;
|
|
4617
|
+
try {
|
|
4618
|
+
content = import_fs4.default.readFileSync(cacheFile, "utf-8");
|
|
4619
|
+
existed = true;
|
|
4620
|
+
} catch (err2) {
|
|
4621
|
+
if (err2.code === "ENOENT") return {};
|
|
4622
|
+
continue;
|
|
4623
|
+
}
|
|
4624
|
+
try {
|
|
4625
|
+
return JSON.parse(content);
|
|
4626
|
+
} catch {
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
if (existed) {
|
|
4630
|
+
const backup = import_path4.default.join(import_path4.default.dirname(cacheFile), "rules-cache.last-good.json");
|
|
4631
|
+
if (backup !== cacheFile) {
|
|
4632
|
+
try {
|
|
4633
|
+
const raw = JSON.parse(import_fs4.default.readFileSync(backup, "utf-8"));
|
|
4634
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
|
|
4635
|
+
return raw;
|
|
4636
|
+
} catch {
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
logCacheReadIssue(cacheFile, "RULES_CACHE_UNREADABLE");
|
|
4640
|
+
}
|
|
4641
|
+
return {};
|
|
4642
|
+
}
|
|
4643
|
+
function logCacheReadIssue(cacheFile, kind) {
|
|
4644
|
+
if (cacheReadFailureLogged) return;
|
|
4645
|
+
cacheReadFailureLogged = true;
|
|
4646
|
+
try {
|
|
4647
|
+
import_fs4.default.appendFileSync(
|
|
4648
|
+
import_path4.default.join(import_os4.default.homedir(), ".node9", "hook-debug.log"),
|
|
4649
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
|
|
4650
|
+
`
|
|
4651
|
+
);
|
|
4652
|
+
} catch {
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4611
4655
|
function getConfig(cwd) {
|
|
4612
4656
|
if (!cwd && cachedConfig) return cachedConfig;
|
|
4613
4657
|
const globalPath = import_path4.default.join(import_os4.default.homedir(), ".node9", "config.json");
|
|
@@ -4675,6 +4719,7 @@ function getConfig(cwd) {
|
|
|
4675
4719
|
if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
|
|
4676
4720
|
if (s.mcpReconcileIntervalMinutes !== void 0)
|
|
4677
4721
|
mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
|
|
4722
|
+
if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
|
|
4678
4723
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
4679
4724
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
4680
4725
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -4684,12 +4729,13 @@ function getConfig(cwd) {
|
|
|
4684
4729
|
if (p.smartRules) {
|
|
4685
4730
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
4686
4731
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
4687
|
-
const
|
|
4732
|
+
const localRules = p.smartRules.map(({ pinned: _pinned, ...r }) => r);
|
|
4733
|
+
const userRuleNames = new Set(localRules.filter((r) => r.name).map((r) => r.name));
|
|
4688
4734
|
const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
|
|
4689
4735
|
const filteredNonBlocks = defaultNonBlocks.filter(
|
|
4690
4736
|
(r) => !r.name || !userRuleNames.has(r.name)
|
|
4691
4737
|
);
|
|
4692
|
-
mergedPolicy.smartRules = [...filteredBlocks, ...
|
|
4738
|
+
mergedPolicy.smartRules = [...filteredBlocks, ...localRules, ...filteredNonBlocks];
|
|
4693
4739
|
}
|
|
4694
4740
|
if (p.snapshot) {
|
|
4695
4741
|
const s2 = p.snapshot;
|
|
@@ -4754,10 +4800,12 @@ function getConfig(cwd) {
|
|
|
4754
4800
|
applyLayer(globalConfig);
|
|
4755
4801
|
applyLayer(projectConfig);
|
|
4756
4802
|
let cloudManagedShields = [];
|
|
4803
|
+
let modeCloudControlled = false;
|
|
4804
|
+
let modeCloudStaged = false;
|
|
4757
4805
|
{
|
|
4758
4806
|
const cacheFile = import_path4.default.join(import_os4.default.homedir(), ".node9", "rules-cache.json");
|
|
4759
4807
|
try {
|
|
4760
|
-
const raw =
|
|
4808
|
+
const raw = readRulesCacheResilient(cacheFile);
|
|
4761
4809
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
4762
4810
|
applyLayer({ policy: { smartRules: raw.rules } });
|
|
4763
4811
|
}
|
|
@@ -4774,6 +4822,9 @@ function getConfig(cwd) {
|
|
|
4774
4822
|
locked.includes("mode")
|
|
4775
4823
|
);
|
|
4776
4824
|
}
|
|
4825
|
+
if (typeof mc.mode === "string" || locked.includes("mode")) {
|
|
4826
|
+
modeCloudControlled = true;
|
|
4827
|
+
}
|
|
4777
4828
|
if (mc.egress && typeof mc.egress === "object") {
|
|
4778
4829
|
const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
|
|
4779
4830
|
mergedPolicy.egress = applyManagedEgress(
|
|
@@ -4872,19 +4923,28 @@ function getConfig(cwd) {
|
|
|
4872
4923
|
}
|
|
4873
4924
|
if (raw.shadowMode === true) {
|
|
4874
4925
|
mergedSettings.mode = "observe";
|
|
4926
|
+
modeCloudStaged = true;
|
|
4875
4927
|
}
|
|
4876
4928
|
} catch {
|
|
4877
4929
|
}
|
|
4878
4930
|
}
|
|
4879
4931
|
const shieldOverrides = readShieldOverrides();
|
|
4880
4932
|
const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
|
|
4933
|
+
const cloudManagedSet = new Set(cloudManagedShields);
|
|
4881
4934
|
for (const shieldName of activeShieldNames) {
|
|
4882
|
-
const
|
|
4935
|
+
const isCloudMandated = cloudManagedSet.has(shieldName);
|
|
4936
|
+
const shield = isCloudMandated ? BUILTIN_SHIELDS[shieldName] : getShield(shieldName);
|
|
4883
4937
|
if (!shield) continue;
|
|
4884
4938
|
const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
4885
|
-
const ruleOverrides = shieldOverrides[shieldName] ?? {};
|
|
4939
|
+
const ruleOverrides = isCloudMandated ? {} : shieldOverrides[shieldName] ?? {};
|
|
4886
4940
|
for (const rule of shield.smartRules) {
|
|
4887
|
-
|
|
4941
|
+
const collides = rule.name ? existingRuleNames.has(rule.name) : false;
|
|
4942
|
+
if (isCloudMandated) {
|
|
4943
|
+
if (collides) {
|
|
4944
|
+
mergedPolicy.smartRules = mergedPolicy.smartRules.filter((r) => r.name !== rule.name);
|
|
4945
|
+
}
|
|
4946
|
+
mergedPolicy.smartRules.push({ ...rule, pinned: true });
|
|
4947
|
+
} else if (!collides) {
|
|
4888
4948
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
4889
4949
|
mergedPolicy.smartRules.push(
|
|
4890
4950
|
overrideVerdict !== void 0 ? { ...rule, verdict: overrideVerdict } : rule
|
|
@@ -4900,7 +4960,27 @@ function getConfig(cwd) {
|
|
|
4900
4960
|
for (const rule of ADVISORY_SMART_RULES) {
|
|
4901
4961
|
if (!existingAdvisoryNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
4902
4962
|
}
|
|
4903
|
-
|
|
4963
|
+
const envMode = process.env.NODE9_MODE;
|
|
4964
|
+
if (envMode && !modeCloudControlled && ["observe", "audit", "standard", "strict"].includes(envMode)) {
|
|
4965
|
+
mergedSettings.mode = envMode;
|
|
4966
|
+
}
|
|
4967
|
+
if (cloudManagedShields.length > 0 && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
|
|
4968
|
+
mergedSettings.mode = "standard";
|
|
4969
|
+
}
|
|
4970
|
+
const managedFloorActive = cloudManagedShields.length > 0 || modeCloudControlled && mergedSettings.mode === "strict";
|
|
4971
|
+
if (modeCloudControlled && mergedSettings.mode === "strict") {
|
|
4972
|
+
for (const name of Object.keys(mergedEnvironments)) {
|
|
4973
|
+
if (mergedEnvironments[name]?.requireApproval === false) {
|
|
4974
|
+
const cleaned = { ...mergedEnvironments[name] };
|
|
4975
|
+
delete cleaned.requireApproval;
|
|
4976
|
+
mergedEnvironments[name] = cleaned;
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
if (managedFloorActive) {
|
|
4981
|
+
mergedPolicy.ignoredTools = [...DEFAULT_CONFIG.policy.ignoredTools];
|
|
4982
|
+
mergedPolicy.sandboxPaths = [...DEFAULT_CONFIG.policy.sandboxPaths];
|
|
4983
|
+
}
|
|
4904
4984
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
4905
4985
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
4906
4986
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
@@ -4969,7 +5049,7 @@ ${error.replace("Invalid config:\n", "")}
|
|
|
4969
5049
|
}
|
|
4970
5050
|
return sanitized;
|
|
4971
5051
|
}
|
|
4972
|
-
var import_fs4, import_path4, import_os4, DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig;
|
|
5052
|
+
var import_fs4, import_path4, import_os4, DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig, cacheReadFailureLogged;
|
|
4973
5053
|
var init_config = __esm({
|
|
4974
5054
|
"src/config/index.ts"() {
|
|
4975
5055
|
"use strict";
|
|
@@ -4981,6 +5061,7 @@ var init_config = __esm({
|
|
|
4981
5061
|
init_managed();
|
|
4982
5062
|
init_build();
|
|
4983
5063
|
init_trusted_hosts();
|
|
5064
|
+
init_dist();
|
|
4984
5065
|
DANGEROUS_WORDS = [
|
|
4985
5066
|
"mkfs",
|
|
4986
5067
|
// formats/wipes a filesystem partition
|
|
@@ -5258,6 +5339,7 @@ var init_config = __esm({
|
|
|
5258
5339
|
}
|
|
5259
5340
|
];
|
|
5260
5341
|
cachedConfig = null;
|
|
5342
|
+
cacheReadFailureLogged = false;
|
|
5261
5343
|
}
|
|
5262
5344
|
});
|
|
5263
5345
|
|
|
@@ -5975,6 +6057,18 @@ async function isDaemonReachable(timeoutMs = 500) {
|
|
|
5975
6057
|
return false;
|
|
5976
6058
|
}
|
|
5977
6059
|
}
|
|
6060
|
+
async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
6061
|
+
try {
|
|
6062
|
+
const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/approver`, {
|
|
6063
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
6064
|
+
});
|
|
6065
|
+
if (!res.ok) return false;
|
|
6066
|
+
const body = await res.json();
|
|
6067
|
+
return body.interactive === true;
|
|
6068
|
+
} catch {
|
|
6069
|
+
return false;
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
5978
6072
|
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent) {
|
|
5979
6073
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
5980
6074
|
const ctrl = new AbortController();
|
|
@@ -6859,6 +6953,12 @@ function isNetworkTool(toolName, args) {
|
|
|
6859
6953
|
function notifyActivity(data) {
|
|
6860
6954
|
return notifyActivitySocket(data);
|
|
6861
6955
|
}
|
|
6956
|
+
async function hasReachableHumanApprover(opts) {
|
|
6957
|
+
const hasDisplay = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
6958
|
+
const nativeReachable = !opts.calledFromDaemon && opts.approvers.native !== false && hasDisplay;
|
|
6959
|
+
if (nativeReachable) return true;
|
|
6960
|
+
return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
|
|
6961
|
+
}
|
|
6862
6962
|
async function authorizeHeadless(toolName, args, meta, options) {
|
|
6863
6963
|
if (!options?.calledFromDaemon) {
|
|
6864
6964
|
const actId = (0, import_crypto5.randomUUID)();
|
|
@@ -7173,6 +7273,35 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7173
7273
|
return { approved: true, checkedBy: "local-policy" };
|
|
7174
7274
|
}
|
|
7175
7275
|
if (policyResult.decision === "block") {
|
|
7276
|
+
const daemonUp = isDaemonRunning();
|
|
7277
|
+
let humanApproverReachable = false;
|
|
7278
|
+
if (!policyResult.dependsOnStatePredicates?.length && daemonUp && !isTestEnv2) {
|
|
7279
|
+
humanApproverReachable = await hasReachableHumanApprover({
|
|
7280
|
+
approvers,
|
|
7281
|
+
calledFromDaemon: options?.calledFromDaemon
|
|
7282
|
+
});
|
|
7283
|
+
}
|
|
7284
|
+
const mayDowngrade = daemonUp && !isTestEnv2 && humanApproverReachable;
|
|
7285
|
+
const hardBlock = () => {
|
|
7286
|
+
if (!isManual)
|
|
7287
|
+
appendLocalAudit(
|
|
7288
|
+
toolName,
|
|
7289
|
+
args,
|
|
7290
|
+
"deny",
|
|
7291
|
+
"smart-rule-block",
|
|
7292
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
7293
|
+
hashAuditArgs
|
|
7294
|
+
);
|
|
7295
|
+
return {
|
|
7296
|
+
approved: false,
|
|
7297
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7298
|
+
blockedBy: "local-config",
|
|
7299
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
7300
|
+
ruleHit: policyResult.ruleName,
|
|
7301
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7302
|
+
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7303
|
+
};
|
|
7304
|
+
};
|
|
7176
7305
|
if (policyResult.dependsOnStatePredicates?.length) {
|
|
7177
7306
|
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
7178
7307
|
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
@@ -7189,7 +7318,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7189
7318
|
if (predicatesMet && policyResult.recoveryCommand) {
|
|
7190
7319
|
statefulRecoveryCommand = policyResult.recoveryCommand;
|
|
7191
7320
|
}
|
|
7192
|
-
} else if (
|
|
7321
|
+
} else if (mayDowngrade) {
|
|
7193
7322
|
if (!isManual)
|
|
7194
7323
|
appendLocalAudit(
|
|
7195
7324
|
toolName,
|
|
@@ -7211,36 +7340,13 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7211
7340
|
};
|
|
7212
7341
|
}
|
|
7213
7342
|
} else {
|
|
7214
|
-
|
|
7215
|
-
appendLocalAudit(
|
|
7216
|
-
toolName,
|
|
7217
|
-
args,
|
|
7218
|
-
"deny",
|
|
7219
|
-
"smart-rule-block",
|
|
7220
|
-
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
7221
|
-
// panel can attribute this block to its specific shield
|
|
7222
|
-
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
7223
|
-
// rule→shield map. checkedBy stays as the generic
|
|
7224
|
-
// `smart-rule-block` for backward compat with existing
|
|
7225
|
-
// log readers.
|
|
7226
|
-
{ ...meta, ruleName: policyResult.ruleName },
|
|
7227
|
-
hashAuditArgs
|
|
7228
|
-
);
|
|
7229
|
-
return {
|
|
7230
|
-
approved: false,
|
|
7231
|
-
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
7232
|
-
blockedBy: "local-config",
|
|
7233
|
-
blockedByLabel: policyResult.blockedByLabel,
|
|
7234
|
-
ruleHit: policyResult.ruleName,
|
|
7235
|
-
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
7236
|
-
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
|
|
7237
|
-
};
|
|
7343
|
+
return hardBlock();
|
|
7238
7344
|
}
|
|
7239
7345
|
}
|
|
7240
7346
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
7241
7347
|
policyMatchedField = policyResult.matchedField;
|
|
7242
7348
|
policyMatchedWord = policyResult.matchedWord;
|
|
7243
|
-
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
7349
|
+
if (policyResult.ruleName || policyResult.tier === 7) localSmartRuleMatched = true;
|
|
7244
7350
|
if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
|
|
7245
7351
|
else if (policyResult.reason) policyRuleDescription = policyResult.reason;
|
|
7246
7352
|
riskMetadata = computeRiskMetadata(
|
|
@@ -7252,7 +7358,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
7252
7358
|
policyResult.ruleName
|
|
7253
7359
|
);
|
|
7254
7360
|
if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
|
|
7255
|
-
const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
|
|
7361
|
+
const persistent = policyResult.ruleName || policyResult.tier === 7 ? null : getPersistentDecision(toolName);
|
|
7256
7362
|
if (persistent === "allow" && !appPermReview) {
|
|
7257
7363
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
|
|
7258
7364
|
return { approved: true, checkedBy: "persistent" };
|
|
@@ -15919,6 +16025,9 @@ data: ${JSON.stringify(data)}
|
|
|
15919
16025
|
}
|
|
15920
16026
|
});
|
|
15921
16027
|
}
|
|
16028
|
+
function hasInteractiveClient() {
|
|
16029
|
+
return [...sseClients].some((c) => c.capabilities.includes("input"));
|
|
16030
|
+
}
|
|
15922
16031
|
function broadcastForensic(finding) {
|
|
15923
16032
|
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
15924
16033
|
const event = {
|
|
@@ -17156,18 +17265,27 @@ var init_score = __esm({
|
|
|
17156
17265
|
|
|
17157
17266
|
// src/posture/headline.ts
|
|
17158
17267
|
function worstFinding(findings) {
|
|
17159
|
-
|
|
17268
|
+
const cmp = (x, y) => x < y ? -1 : x > y ? 1 : 0;
|
|
17269
|
+
return [...findings].sort(
|
|
17270
|
+
(a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || cmp(a.category, b.category) || cmp(a.title, b.title)
|
|
17271
|
+
)[0];
|
|
17272
|
+
}
|
|
17273
|
+
function actionFromFinding(f) {
|
|
17274
|
+
if (!f?.fix) return null;
|
|
17275
|
+
const fix = f.fix.replace(/^fix it now:\s*/i, "");
|
|
17276
|
+
const where = f.detail.length === 0 ? "" : f.detail.length === 1 ? ` \u2014 found: ${f.detail[0]}` : ` \u2014 found: ${f.detail[0]} and ${f.detail.length - 1} more`;
|
|
17277
|
+
return fix + where;
|
|
17160
17278
|
}
|
|
17161
17279
|
function deriveHeadline(allFindings) {
|
|
17162
17280
|
const findings = allFindings.filter(
|
|
17163
17281
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix"
|
|
17164
17282
|
);
|
|
17165
17283
|
if (findings.length === 0 || findings.every((f) => f.severity === "advisory")) return null;
|
|
17166
|
-
const
|
|
17167
|
-
const secrets =
|
|
17168
|
-
const egressOpen =
|
|
17169
|
-
const noIsolation =
|
|
17170
|
-
const gateWeak =
|
|
17284
|
+
const has2 = (category) => findings.some((f) => f.category === category);
|
|
17285
|
+
const secrets = has2("Secrets");
|
|
17286
|
+
const egressOpen = has2("Egress");
|
|
17287
|
+
const noIsolation = has2("Isolation");
|
|
17288
|
+
const gateWeak = has2("Approval gate");
|
|
17171
17289
|
const notWired = findings.some((f) => f.category === "Coverage" && f.severity === "critical");
|
|
17172
17290
|
const observeOnly = findings.some((f) => f.category === "Coverage" && f.severity === "high");
|
|
17173
17291
|
let risk;
|
|
@@ -17190,13 +17308,16 @@ function deriveHeadline(allFindings) {
|
|
|
17190
17308
|
} else if (observeOnly) {
|
|
17191
17309
|
action = "Switch node9 to enforcing mode \u2014 right now it is only watching, not blocking.";
|
|
17192
17310
|
} else if (egressOpen) {
|
|
17193
|
-
|
|
17311
|
+
const egressFix = actionFromFinding(
|
|
17312
|
+
worstFinding(findings.filter((f) => f.category === "Egress"))
|
|
17313
|
+
);
|
|
17314
|
+
action = egressFix ? `${egressFix} Closing the exit breaks the exfiltration chain.` : "lock egress to an allowlist (node9 can enforce it) \u2014 it closes the exit the exfiltration needs.";
|
|
17194
17315
|
} else if (secrets) {
|
|
17195
|
-
action = "node9 can block reads of sensitive
|
|
17316
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Secrets"))) ?? "node9 can block reads of sensitive credential files in-path (`node9 shield enable project-jail`).";
|
|
17196
17317
|
} else if (gateWeak) {
|
|
17197
|
-
action = "node9 can enforce destructive-command blocking in-path.";
|
|
17318
|
+
action = actionFromFinding(worstFinding(findings.filter((f) => f.category === "Approval gate"))) ?? "node9 can enforce destructive-command blocking in-path (`node9 shield enable bash-safe`).";
|
|
17198
17319
|
} else {
|
|
17199
|
-
action = worstFinding(findings)
|
|
17320
|
+
action = actionFromFinding(worstFinding(findings)) ?? "Review the findings below.";
|
|
17200
17321
|
}
|
|
17201
17322
|
return { risk, action };
|
|
17202
17323
|
}
|
|
@@ -17734,6 +17855,21 @@ function inventoryMcp(home = import_os31.default.homedir()) {
|
|
|
17734
17855
|
}
|
|
17735
17856
|
return out;
|
|
17736
17857
|
}
|
|
17858
|
+
function inventoryServerKeys(inv) {
|
|
17859
|
+
const keys = /* @__PURE__ */ new Set();
|
|
17860
|
+
for (const e of inv) {
|
|
17861
|
+
if (e.state === "gatewayed") {
|
|
17862
|
+
const i = e.args.indexOf("--upstream");
|
|
17863
|
+
if (i >= 0 && e.args[i + 1]) {
|
|
17864
|
+
keys.add(getServerKey(e.args[i + 1]));
|
|
17865
|
+
}
|
|
17866
|
+
} else if (e.state === "ungoverned") {
|
|
17867
|
+
const cmd = [e.command, ...e.args].map(quoteArg).join(" ");
|
|
17868
|
+
keys.add(getServerKey(cmd));
|
|
17869
|
+
}
|
|
17870
|
+
}
|
|
17871
|
+
return keys;
|
|
17872
|
+
}
|
|
17737
17873
|
function writeMcpEntry(mcpFile, format, name, entry) {
|
|
17738
17874
|
const key = format === "toml" ? "mcp_servers" : "mcpServers";
|
|
17739
17875
|
let root = {};
|
|
@@ -17761,6 +17897,7 @@ var init_mcp_wrap = __esm({
|
|
|
17761
17897
|
import_smol_toml4 = require("smol-toml");
|
|
17762
17898
|
init_agent_wiring();
|
|
17763
17899
|
init_mcp_cmd();
|
|
17900
|
+
init_mcp_pin();
|
|
17764
17901
|
init_mcp_cmd();
|
|
17765
17902
|
}
|
|
17766
17903
|
});
|
|
@@ -18189,9 +18326,12 @@ function extractManagedConfig(body) {
|
|
|
18189
18326
|
return out.mode !== void 0 || out.egress !== void 0 || out.dlp !== void 0 || out.approvers !== void 0 || out.reviewChannel !== void 0 || out.approvalTimeoutMs !== void 0 || out.injectionScan !== void 0 || out.loopDetection !== void 0 || out.skillPinning !== void 0 || out.jailPaths !== void 0 || out.trustedHosts !== void 0 || out.appPermissions !== void 0 ? out : void 0;
|
|
18190
18327
|
}
|
|
18191
18328
|
function writeCache2(cache) {
|
|
18192
|
-
const
|
|
18193
|
-
|
|
18194
|
-
|
|
18329
|
+
const data = JSON.stringify(cache, null, 2) + "\n";
|
|
18330
|
+
atomicWriteSync2(rulesCacheFile(), data, "utf-8");
|
|
18331
|
+
try {
|
|
18332
|
+
atomicWriteSync2(rulesCacheBackupFile(), data, "utf-8");
|
|
18333
|
+
} catch {
|
|
18334
|
+
}
|
|
18195
18335
|
}
|
|
18196
18336
|
async function syncOnce() {
|
|
18197
18337
|
const creds = readCredentials();
|
|
@@ -18485,7 +18625,7 @@ function startForensicBroadcast() {
|
|
|
18485
18625
|
const recurring = setInterval(() => void tick(), FORENSIC_BROADCAST_INTERVAL_MS);
|
|
18486
18626
|
recurring.unref();
|
|
18487
18627
|
}
|
|
18488
|
-
var import_fs35, import_https4, import_os32, import_path34, FINDING_TO_SIGNAL3, rulesCacheFile, DEFAULT_API_URL2, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, syncHealthFile, STALE_MIN_MS, STALE_MAX_MS, STALE_FACTOR, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
18628
|
+
var import_fs35, import_https4, import_os32, import_path34, FINDING_TO_SIGNAL3, rulesCacheFile, rulesCacheBackupFile, DEFAULT_API_URL2, DEFAULT_INTERVAL_HOURS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS, syncHealthFile, STALE_MIN_MS, STALE_MAX_MS, STALE_FACTOR, FORENSIC_BROADCAST_INTERVAL_MS, FORENSIC_INITIAL_DELAY_MS, forensicBroadcastOffsets;
|
|
18489
18629
|
var init_sync = __esm({
|
|
18490
18630
|
"src/daemon/sync.ts"() {
|
|
18491
18631
|
"use strict";
|
|
@@ -18499,6 +18639,7 @@ var init_sync = __esm({
|
|
|
18499
18639
|
init_ship();
|
|
18500
18640
|
init_build2();
|
|
18501
18641
|
init_mcp_tools();
|
|
18642
|
+
init_state2();
|
|
18502
18643
|
init_mcp_status();
|
|
18503
18644
|
init_ship2();
|
|
18504
18645
|
init_shields();
|
|
@@ -18519,6 +18660,7 @@ var init_sync = __esm({
|
|
|
18519
18660
|
"long-output-redacted": "longOutputRedactions"
|
|
18520
18661
|
};
|
|
18521
18662
|
rulesCacheFile = () => import_path34.default.join(import_os32.default.homedir(), ".node9", "rules-cache.json");
|
|
18663
|
+
rulesCacheBackupFile = () => import_path34.default.join(import_os32.default.homedir(), ".node9", "rules-cache.last-good.json");
|
|
18522
18664
|
DEFAULT_API_URL2 = "https://api.node9.ai/api/v1/intercept/policies/sync";
|
|
18523
18665
|
DEFAULT_INTERVAL_HOURS = 5;
|
|
18524
18666
|
MIN_INTERVAL_SECONDS = 15;
|
|
@@ -18760,6 +18902,53 @@ var init_audit_shipper = __esm({
|
|
|
18760
18902
|
}
|
|
18761
18903
|
});
|
|
18762
18904
|
|
|
18905
|
+
// src/audit/decision.ts
|
|
18906
|
+
function classifyDecision(a, b) {
|
|
18907
|
+
const isRow = !!a && typeof a === "object";
|
|
18908
|
+
const decision = isRow ? a.decision : a;
|
|
18909
|
+
const attribution = isRow ? a.checkedBy ?? a.source : b;
|
|
18910
|
+
const raw = typeof decision === "string" ? decision : String(decision ?? "");
|
|
18911
|
+
const src = typeof attribution === "string" ? attribution.toLowerCase() : "";
|
|
18912
|
+
const d = raw.toLowerCase();
|
|
18913
|
+
if (src && has(src, "observe-mode")) {
|
|
18914
|
+
return { outcome: "observe", label: "Would block", raw };
|
|
18915
|
+
}
|
|
18916
|
+
if (d === "dlp") return { outcome: "info", label: "Finding", raw };
|
|
18917
|
+
if (d === "mcp-discovered") return { outcome: "info", label: "Info", raw };
|
|
18918
|
+
if (d === "allow" || d === "allowed") {
|
|
18919
|
+
if (src === "post-hook") return { outcome: "allow", label: "Ran", raw };
|
|
18920
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18921
|
+
return { outcome: "allow", label: "Approved", raw };
|
|
18922
|
+
}
|
|
18923
|
+
return { outcome: "allow", label: "Auto-allowed", raw };
|
|
18924
|
+
}
|
|
18925
|
+
if (d === "deny" || d === "auto-deny" || d === "block") {
|
|
18926
|
+
if (TIMEOUT_SOURCES.has(src)) {
|
|
18927
|
+
return { outcome: "deny", label: "Timed out", raw };
|
|
18928
|
+
}
|
|
18929
|
+
if (HUMAN_SOURCES.has(src)) {
|
|
18930
|
+
return { outcome: "deny", label: "Denied", raw };
|
|
18931
|
+
}
|
|
18932
|
+
return { outcome: "deny", label: "Blocked", raw };
|
|
18933
|
+
}
|
|
18934
|
+
if (d === "review" || d === "pending") {
|
|
18935
|
+
return { outcome: "info", label: "Pending", raw };
|
|
18936
|
+
}
|
|
18937
|
+
return { outcome: "unknown", label: raw ? `? ${raw}` : "? (none)", raw };
|
|
18938
|
+
}
|
|
18939
|
+
function decisionTag(view) {
|
|
18940
|
+
return `[${view.label}]`.padEnd(14);
|
|
18941
|
+
}
|
|
18942
|
+
var HUMAN_SOURCES, TIMEOUT_SOURCES, has;
|
|
18943
|
+
var init_decision = __esm({
|
|
18944
|
+
"src/audit/decision.ts"() {
|
|
18945
|
+
"use strict";
|
|
18946
|
+
HUMAN_SOURCES = /* @__PURE__ */ new Set(["daemon", "cloud", "local-decision", "inline-review-approved"]);
|
|
18947
|
+
TIMEOUT_SOURCES = /* @__PURE__ */ new Set(["timeout"]);
|
|
18948
|
+
has = (s, needle) => s.includes(needle);
|
|
18949
|
+
}
|
|
18950
|
+
});
|
|
18951
|
+
|
|
18763
18952
|
// src/daemon/dlp-scanner.ts
|
|
18764
18953
|
function loadIndex() {
|
|
18765
18954
|
try {
|
|
@@ -18995,6 +19184,7 @@ function runMcpReconcile() {
|
|
|
18995
19184
|
}
|
|
18996
19185
|
const baseline = loadBaseline();
|
|
18997
19186
|
const creds = getCredentials();
|
|
19187
|
+
reconcileStale(inv, creds);
|
|
18998
19188
|
const fresh = inv.filter((e) => e.state === "ungoverned" && !baseline.has(idKey(e)));
|
|
18999
19189
|
if (fresh.length === 0) return;
|
|
19000
19190
|
const wrappedAgents = /* @__PURE__ */ new Set();
|
|
@@ -19034,6 +19224,77 @@ function runMcpReconcile() {
|
|
|
19034
19224
|
}
|
|
19035
19225
|
saveBaseline(baseline);
|
|
19036
19226
|
}
|
|
19227
|
+
function reconcileStale(inv, creds) {
|
|
19228
|
+
let pins;
|
|
19229
|
+
try {
|
|
19230
|
+
pins = readMcpPins();
|
|
19231
|
+
} catch {
|
|
19232
|
+
return;
|
|
19233
|
+
}
|
|
19234
|
+
const serverKeys = Object.keys(pins.servers);
|
|
19235
|
+
if (serverKeys.length === 0) return;
|
|
19236
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
19237
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19238
|
+
let dirty = false;
|
|
19239
|
+
for (const sk of serverKeys) {
|
|
19240
|
+
const pin = pins.servers[sk];
|
|
19241
|
+
if (liveKeys.has(sk)) {
|
|
19242
|
+
if (pin.lastSeen !== now) {
|
|
19243
|
+
pin.lastSeen = now;
|
|
19244
|
+
dirty = true;
|
|
19245
|
+
}
|
|
19246
|
+
} else if (!pin.lastSeen) {
|
|
19247
|
+
pin.lastSeen = pin.pinnedAt;
|
|
19248
|
+
dirty = true;
|
|
19249
|
+
}
|
|
19250
|
+
}
|
|
19251
|
+
const staleDays = getConfig().settings.mcpStaleAfterDays ?? DEFAULT_STALE_DAYS;
|
|
19252
|
+
if (staleDays > 0 && liveKeys.size > 0) {
|
|
19253
|
+
const staleMs = staleDays * 864e5;
|
|
19254
|
+
for (const sk of serverKeys) {
|
|
19255
|
+
const pin = pins.servers[sk];
|
|
19256
|
+
if (liveKeys.has(sk)) continue;
|
|
19257
|
+
const age = Date.now() - Date.parse(pin.lastSeen ?? pin.pinnedAt);
|
|
19258
|
+
if (age >= staleMs) {
|
|
19259
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
19260
|
+
event: "mcp-pin-auto-removed",
|
|
19261
|
+
serverKey: sk,
|
|
19262
|
+
label: pin.label,
|
|
19263
|
+
lastSeen: pin.lastSeen,
|
|
19264
|
+
pinnedAt: pin.pinnedAt
|
|
19265
|
+
});
|
|
19266
|
+
if (creds) {
|
|
19267
|
+
try {
|
|
19268
|
+
void auditLocalAllow(
|
|
19269
|
+
`mcp-server:${sk}`,
|
|
19270
|
+
{
|
|
19271
|
+
serverKey: sk,
|
|
19272
|
+
label: pin.label,
|
|
19273
|
+
lastSeen: pin.lastSeen,
|
|
19274
|
+
reason: "stale",
|
|
19275
|
+
staleDays
|
|
19276
|
+
},
|
|
19277
|
+
"mcp-server-removed",
|
|
19278
|
+
creds,
|
|
19279
|
+
{ mcpServer: pin.label },
|
|
19280
|
+
void 0,
|
|
19281
|
+
false
|
|
19282
|
+
);
|
|
19283
|
+
} catch {
|
|
19284
|
+
}
|
|
19285
|
+
}
|
|
19286
|
+
delete pins.servers[sk];
|
|
19287
|
+
dirty = true;
|
|
19288
|
+
}
|
|
19289
|
+
}
|
|
19290
|
+
}
|
|
19291
|
+
if (dirty) {
|
|
19292
|
+
try {
|
|
19293
|
+
writeMcpPins(pins);
|
|
19294
|
+
} catch {
|
|
19295
|
+
}
|
|
19296
|
+
}
|
|
19297
|
+
}
|
|
19037
19298
|
function startMcpReconciler() {
|
|
19038
19299
|
setImmediate(() => {
|
|
19039
19300
|
try {
|
|
@@ -19055,7 +19316,7 @@ function startMcpReconciler() {
|
|
|
19055
19316
|
};
|
|
19056
19317
|
schedule();
|
|
19057
19318
|
}
|
|
19058
|
-
var import_fs38, import_path37, import_os35, import_crypto10, BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN;
|
|
19319
|
+
var import_fs38, import_path37, import_os35, import_crypto10, BASELINE_FILE2, BASELINE_CAP, DEFAULT_INTERVAL_MIN, DEFAULT_STALE_DAYS;
|
|
19059
19320
|
var init_mcp_reconciler = __esm({
|
|
19060
19321
|
"src/daemon/mcp-reconciler.ts"() {
|
|
19061
19322
|
"use strict";
|
|
@@ -19068,9 +19329,11 @@ var init_mcp_reconciler = __esm({
|
|
|
19068
19329
|
init_config();
|
|
19069
19330
|
init_cloud();
|
|
19070
19331
|
init_audit();
|
|
19332
|
+
init_mcp_pin();
|
|
19071
19333
|
BASELINE_FILE2 = import_path37.default.join(import_os35.default.homedir(), ".node9", "mcp-baseline.json");
|
|
19072
19334
|
BASELINE_CAP = 500;
|
|
19073
19335
|
DEFAULT_INTERVAL_MIN = 60;
|
|
19336
|
+
DEFAULT_STALE_DAYS = 7;
|
|
19074
19337
|
}
|
|
19075
19338
|
});
|
|
19076
19339
|
|
|
@@ -19142,20 +19405,87 @@ var init_hook_heal = __esm({
|
|
|
19142
19405
|
});
|
|
19143
19406
|
|
|
19144
19407
|
// src/daemon/startup-log.ts
|
|
19408
|
+
function capStartupLog(file) {
|
|
19409
|
+
try {
|
|
19410
|
+
if (import_fs39.default.statSync(file).size > MAX_STARTUP_LOG_BYTES) import_fs39.default.truncateSync(file);
|
|
19411
|
+
} catch {
|
|
19412
|
+
}
|
|
19413
|
+
}
|
|
19145
19414
|
function openStartupLogFd() {
|
|
19146
19415
|
try {
|
|
19147
19416
|
const file = DAEMON_STARTUP_LOG();
|
|
19148
19417
|
const dir = import_path38.default.dirname(file);
|
|
19149
19418
|
if (!import_fs39.default.existsSync(dir)) import_fs39.default.mkdirSync(dir, { recursive: true });
|
|
19150
|
-
|
|
19151
|
-
if (import_fs39.default.statSync(file).size > MAX_STARTUP_LOG_BYTES) import_fs39.default.truncateSync(file);
|
|
19152
|
-
} catch {
|
|
19153
|
-
}
|
|
19419
|
+
capStartupLog(file);
|
|
19154
19420
|
return import_fs39.default.openSync(file, "a");
|
|
19155
19421
|
} catch {
|
|
19156
19422
|
return void 0;
|
|
19157
19423
|
}
|
|
19158
19424
|
}
|
|
19425
|
+
function recordStartupState(outcome, kind, detail) {
|
|
19426
|
+
try {
|
|
19427
|
+
if (outcome === "starting") {
|
|
19428
|
+
const prev = readStartupState();
|
|
19429
|
+
if (prev?.outcome === "starting") {
|
|
19430
|
+
const prevAt = new Date(prev.at).getTime();
|
|
19431
|
+
const age = Math.abs(Date.now() - prevAt);
|
|
19432
|
+
if (!isNaN(prevAt) && age < 24 * 60 * 60 * 1e3) return;
|
|
19433
|
+
}
|
|
19434
|
+
}
|
|
19435
|
+
const file = DAEMON_STARTUP_STATE();
|
|
19436
|
+
const dir = import_path38.default.dirname(file);
|
|
19437
|
+
if (!import_fs39.default.existsSync(dir)) import_fs39.default.mkdirSync(dir, { recursive: true });
|
|
19438
|
+
const state = { outcome, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
19439
|
+
if (kind) state.kind = kind;
|
|
19440
|
+
if (detail) state.detail = detail.slice(0, MAX_DETAIL);
|
|
19441
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
19442
|
+
try {
|
|
19443
|
+
import_fs39.default.writeFileSync(tmp, JSON.stringify(state), "utf-8");
|
|
19444
|
+
import_fs39.default.renameSync(tmp, file);
|
|
19445
|
+
} catch (err2) {
|
|
19446
|
+
try {
|
|
19447
|
+
import_fs39.default.unlinkSync(tmp);
|
|
19448
|
+
} catch {
|
|
19449
|
+
}
|
|
19450
|
+
throw err2;
|
|
19451
|
+
}
|
|
19452
|
+
} catch {
|
|
19453
|
+
}
|
|
19454
|
+
}
|
|
19455
|
+
function readStartupState() {
|
|
19456
|
+
try {
|
|
19457
|
+
const raw = import_fs39.default.readFileSync(DAEMON_STARTUP_STATE(), "utf-8");
|
|
19458
|
+
const s = JSON.parse(raw);
|
|
19459
|
+
if (!s || typeof s.outcome !== "string" || typeof s.at !== "string") return null;
|
|
19460
|
+
return s;
|
|
19461
|
+
} catch {
|
|
19462
|
+
return null;
|
|
19463
|
+
}
|
|
19464
|
+
}
|
|
19465
|
+
function readStartupCause(maxAgeMs = 24 * 60 * 60 * 1e3) {
|
|
19466
|
+
const s = readStartupState();
|
|
19467
|
+
if (!s) return null;
|
|
19468
|
+
const at = new Date(s.at);
|
|
19469
|
+
if (isNaN(at.getTime()) || Date.now() - at.getTime() > maxAgeMs) return null;
|
|
19470
|
+
switch (s.outcome) {
|
|
19471
|
+
case "ok":
|
|
19472
|
+
case "ok-elsewhere":
|
|
19473
|
+
return null;
|
|
19474
|
+
case "starting":
|
|
19475
|
+
if (Date.now() - at.getTime() < STARTING_GRACE_MS) return null;
|
|
19476
|
+
return {
|
|
19477
|
+
kind: "did-not-start",
|
|
19478
|
+
detail: "the daemon did not come up \u2014 see ~/.node9/daemon-startup.log and hook-debug.log",
|
|
19479
|
+
at,
|
|
19480
|
+
// `at` is the FIRST attempt of the streak, not the most recent one.
|
|
19481
|
+
label: "start attempts failing since"
|
|
19482
|
+
};
|
|
19483
|
+
case "failed":
|
|
19484
|
+
return { kind: s.kind || "failed", detail: s.detail || "", at, label: "last start attempt" };
|
|
19485
|
+
default:
|
|
19486
|
+
return null;
|
|
19487
|
+
}
|
|
19488
|
+
}
|
|
19159
19489
|
function logDaemonStartup(kind, detail) {
|
|
19160
19490
|
try {
|
|
19161
19491
|
const file = DAEMON_STARTUP_LOG();
|
|
@@ -19167,7 +19497,7 @@ function logDaemonStartup(kind, detail) {
|
|
|
19167
19497
|
} catch {
|
|
19168
19498
|
}
|
|
19169
19499
|
}
|
|
19170
|
-
var import_fs39, import_path38, import_os36, DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES;
|
|
19500
|
+
var import_fs39, import_path38, import_os36, DAEMON_STARTUP_LOG, MAX_STARTUP_LOG_BYTES, DAEMON_STARTUP_STATE, MAX_DETAIL, STARTING_GRACE_MS;
|
|
19171
19501
|
var init_startup_log = __esm({
|
|
19172
19502
|
"src/daemon/startup-log.ts"() {
|
|
19173
19503
|
"use strict";
|
|
@@ -19176,10 +19506,83 @@ var init_startup_log = __esm({
|
|
|
19176
19506
|
import_os36 = __toESM(require("os"));
|
|
19177
19507
|
DAEMON_STARTUP_LOG = () => import_path38.default.join(import_os36.default.homedir(), ".node9", "daemon-startup.log");
|
|
19178
19508
|
MAX_STARTUP_LOG_BYTES = 256 * 1024;
|
|
19509
|
+
DAEMON_STARTUP_STATE = () => import_path38.default.join(import_os36.default.homedir(), ".node9", "daemon-startup-state.json");
|
|
19510
|
+
MAX_DETAIL = 200;
|
|
19511
|
+
STARTING_GRACE_MS = 90 * 1e3;
|
|
19179
19512
|
}
|
|
19180
19513
|
});
|
|
19181
19514
|
|
|
19182
19515
|
// src/daemon/server.ts
|
|
19516
|
+
function buildDaemonReport(allEntries, period, now) {
|
|
19517
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19518
|
+
let start = new Date(todayStart);
|
|
19519
|
+
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19520
|
+
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19521
|
+
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19522
|
+
const entries = allEntries.filter((e) => {
|
|
19523
|
+
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19524
|
+
return new Date(e.ts) >= start;
|
|
19525
|
+
});
|
|
19526
|
+
const isBlocked = (e) => classifyDecision(e).outcome === "deny";
|
|
19527
|
+
const checkedBy = (e) => typeof e.checkedBy === "string" ? e.checkedBy : void 0;
|
|
19528
|
+
const summary = {
|
|
19529
|
+
total: entries.length,
|
|
19530
|
+
// The inline `startsWith('allow')` this replaces counted every non-allow
|
|
19531
|
+
// row as "blocked" — so DLP findings, MCP-discovery events and, worst, all
|
|
19532
|
+
// the observe-mode "would have blocked" rows inflated the blocked count
|
|
19533
|
+
// with things that were never refusals.
|
|
19534
|
+
allowed: entries.filter((e) => classifyDecision(e).outcome === "allow").length,
|
|
19535
|
+
blocked: entries.filter(isBlocked).length,
|
|
19536
|
+
dlp: entries.filter((e) => checkedBy(e)?.includes("dlp")).length,
|
|
19537
|
+
loops: entries.filter((e) => checkedBy(e) === "loop-detected").length
|
|
19538
|
+
};
|
|
19539
|
+
const dailyMap = /* @__PURE__ */ new Map();
|
|
19540
|
+
if (period === "today") {
|
|
19541
|
+
for (let h = 0; h < 24; h++) {
|
|
19542
|
+
const key = String(h).padStart(2, "0") + ":00";
|
|
19543
|
+
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19544
|
+
}
|
|
19545
|
+
for (const e of entries) {
|
|
19546
|
+
const hour = new Date(e.ts).getHours();
|
|
19547
|
+
const key = String(hour).padStart(2, "0") + ":00";
|
|
19548
|
+
const d = dailyMap.get(key);
|
|
19549
|
+
d.calls++;
|
|
19550
|
+
if (isBlocked(e)) d.blocked++;
|
|
19551
|
+
}
|
|
19552
|
+
} else {
|
|
19553
|
+
for (const e of entries) {
|
|
19554
|
+
const date = e.ts.slice(0, 10);
|
|
19555
|
+
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19556
|
+
d.calls++;
|
|
19557
|
+
if (isBlocked(e)) d.blocked++;
|
|
19558
|
+
dailyMap.set(date, d);
|
|
19559
|
+
}
|
|
19560
|
+
}
|
|
19561
|
+
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19562
|
+
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19563
|
+
for (const e of entries) {
|
|
19564
|
+
const tool = String(e.tool ?? "");
|
|
19565
|
+
topToolsMap.set(tool, (topToolsMap.get(tool) || 0) + 1);
|
|
19566
|
+
if (isBlocked(e)) topBlockedMap.set(tool, (topBlockedMap.get(tool) || 0) + 1);
|
|
19567
|
+
}
|
|
19568
|
+
const top5 = (m) => [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19569
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
19570
|
+
for (const e of entries) {
|
|
19571
|
+
const key = e.agent || "unknown";
|
|
19572
|
+
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19573
|
+
a.total++;
|
|
19574
|
+
if (isBlocked(e)) a.blocked++;
|
|
19575
|
+
if (checkedBy(e)?.includes("dlp")) a.dlp++;
|
|
19576
|
+
agentMap.set(key, a);
|
|
19577
|
+
}
|
|
19578
|
+
return {
|
|
19579
|
+
summary,
|
|
19580
|
+
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19581
|
+
topTools: top5(topToolsMap),
|
|
19582
|
+
topBlockedTools: top5(topBlockedMap),
|
|
19583
|
+
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
19584
|
+
};
|
|
19585
|
+
}
|
|
19183
19586
|
function startDaemon() {
|
|
19184
19587
|
try {
|
|
19185
19588
|
startCostSync();
|
|
@@ -19194,6 +19597,7 @@ function startDaemon() {
|
|
|
19194
19597
|
const stack = err2 instanceof Error ? err2.stack ?? err2.message : String(err2);
|
|
19195
19598
|
console.error("\n\u{1F6D1} Node9 daemon startup failed:\n" + stack);
|
|
19196
19599
|
logDaemonStartup("startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19600
|
+
recordStartupState("failed", "startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
19197
19601
|
process.exit(1);
|
|
19198
19602
|
}
|
|
19199
19603
|
const internalToken = (0, import_crypto11.randomUUID)();
|
|
@@ -19566,6 +19970,10 @@ data: ${JSON.stringify(item.data)}
|
|
|
19566
19970
|
return res.end(JSON.stringify({ error: "internal" }));
|
|
19567
19971
|
}
|
|
19568
19972
|
}
|
|
19973
|
+
if (req.method === "GET" && pathname === "/approver") {
|
|
19974
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19975
|
+
return res.end(JSON.stringify({ interactive: hasInteractiveClient() }));
|
|
19976
|
+
}
|
|
19569
19977
|
if (req.method === "GET" && pathname === "/state/check") {
|
|
19570
19978
|
const predicatesParam = reqUrl.searchParams.get("predicates") ?? "";
|
|
19571
19979
|
const predicates = predicatesParam.split(",").filter(Boolean);
|
|
@@ -19667,75 +20075,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
19667
20075
|
return [];
|
|
19668
20076
|
}
|
|
19669
20077
|
});
|
|
19670
|
-
const now = /* @__PURE__ */ new Date();
|
|
19671
|
-
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
19672
|
-
let start = new Date(todayStart);
|
|
19673
|
-
if (period === "7d") start.setDate(start.getDate() - 6);
|
|
19674
|
-
else if (period === "30d") start.setDate(start.getDate() - 29);
|
|
19675
|
-
else if (period === "month") start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
19676
|
-
const entries = allEntries.filter((e) => {
|
|
19677
|
-
if (e.source === "post-hook" || e.source === "response-dlp") return false;
|
|
19678
|
-
return new Date(e.ts) >= start;
|
|
19679
|
-
});
|
|
19680
|
-
const summary = {
|
|
19681
|
-
total: entries.length,
|
|
19682
|
-
allowed: entries.filter((e) => e.decision && e.decision.startsWith("allow")).length,
|
|
19683
|
-
blocked: entries.filter((e) => e.decision && !e.decision.startsWith("allow")).length,
|
|
19684
|
-
dlp: entries.filter((e) => e.checkedBy && e.checkedBy.includes("dlp")).length,
|
|
19685
|
-
loops: entries.filter((e) => e.checkedBy === "loop-detected").length
|
|
19686
|
-
};
|
|
19687
|
-
const dailyMap = /* @__PURE__ */ new Map();
|
|
19688
|
-
if (period === "today") {
|
|
19689
|
-
for (let h = 0; h < 24; h++) {
|
|
19690
|
-
const key = String(h).padStart(2, "0") + ":00";
|
|
19691
|
-
dailyMap.set(key, { date: key, calls: 0, blocked: 0 });
|
|
19692
|
-
}
|
|
19693
|
-
for (const e of entries) {
|
|
19694
|
-
const hour = new Date(e.ts).getHours();
|
|
19695
|
-
const key = String(hour).padStart(2, "0") + ":00";
|
|
19696
|
-
const d = dailyMap.get(key);
|
|
19697
|
-
d.calls++;
|
|
19698
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19699
|
-
}
|
|
19700
|
-
} else {
|
|
19701
|
-
for (const e of entries) {
|
|
19702
|
-
const date = e.ts.slice(0, 10);
|
|
19703
|
-
const d = dailyMap.get(date) || { date, calls: 0, blocked: 0 };
|
|
19704
|
-
d.calls++;
|
|
19705
|
-
if (e.decision && !e.decision.startsWith("allow")) d.blocked++;
|
|
19706
|
-
dailyMap.set(date, d);
|
|
19707
|
-
}
|
|
19708
|
-
}
|
|
19709
|
-
const topToolsMap = /* @__PURE__ */ new Map();
|
|
19710
|
-
const topBlockedMap = /* @__PURE__ */ new Map();
|
|
19711
|
-
for (const e of entries) {
|
|
19712
|
-
topToolsMap.set(e.tool, (topToolsMap.get(e.tool) || 0) + 1);
|
|
19713
|
-
if (e.decision && !e.decision.startsWith("allow")) {
|
|
19714
|
-
topBlockedMap.set(e.tool, (topBlockedMap.get(e.tool) || 0) + 1);
|
|
19715
|
-
}
|
|
19716
|
-
}
|
|
19717
|
-
const topTools = [...topToolsMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19718
|
-
const topBlockedTools = [...topBlockedMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([name, value]) => ({ name, value }));
|
|
19719
|
-
const agentMap = /* @__PURE__ */ new Map();
|
|
19720
|
-
for (const e of entries) {
|
|
19721
|
-
const key = e.agent || "unknown";
|
|
19722
|
-
const a = agentMap.get(key) ?? { agent: key, total: 0, blocked: 0, dlp: 0 };
|
|
19723
|
-
a.total++;
|
|
19724
|
-
if (e.decision && !e.decision.startsWith("allow")) a.blocked++;
|
|
19725
|
-
if (e.checkedBy?.includes("dlp")) a.dlp++;
|
|
19726
|
-
agentMap.set(key, a);
|
|
19727
|
-
}
|
|
19728
|
-
const byAgent = [...agentMap.values()].sort((a, b) => b.total - a.total);
|
|
19729
20078
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19730
|
-
return res.end(
|
|
19731
|
-
JSON.stringify({
|
|
19732
|
-
summary,
|
|
19733
|
-
daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
|
|
19734
|
-
topTools,
|
|
19735
|
-
topBlockedTools,
|
|
19736
|
-
byAgent
|
|
19737
|
-
})
|
|
19738
|
-
);
|
|
20079
|
+
return res.end(JSON.stringify(buildDaemonReport(allEntries, period, /* @__PURE__ */ new Date())));
|
|
19739
20080
|
} catch {
|
|
19740
20081
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
19741
20082
|
return res.end(JSON.stringify({ error: "Failed to parse report" }));
|
|
@@ -20038,6 +20379,23 @@ data: ${JSON.stringify(item.data)}
|
|
|
20038
20379
|
res.writeHead(404).end();
|
|
20039
20380
|
});
|
|
20040
20381
|
setDaemonServer(server);
|
|
20382
|
+
let bindAttempts = 0;
|
|
20383
|
+
const MAX_BIND_ATTEMPTS = 3;
|
|
20384
|
+
function retryListen() {
|
|
20385
|
+
if (++bindAttempts >= MAX_BIND_ATTEMPTS) {
|
|
20386
|
+
logDaemonStartup(
|
|
20387
|
+
"port-unavailable",
|
|
20388
|
+
`:${DAEMON_PORT} is held by something that is not a node9 daemon`
|
|
20389
|
+
);
|
|
20390
|
+
recordStartupState(
|
|
20391
|
+
"failed",
|
|
20392
|
+
"port-unavailable",
|
|
20393
|
+
`:${DAEMON_PORT} is held by another process that is not a node9 daemon \u2014 free the port, then: node9 daemon --background`
|
|
20394
|
+
);
|
|
20395
|
+
return process.exit(0);
|
|
20396
|
+
}
|
|
20397
|
+
server.listen(DAEMON_PORT, DAEMON_HOST);
|
|
20398
|
+
}
|
|
20041
20399
|
server.on("error", (e) => {
|
|
20042
20400
|
if (e.code === "EADDRINUSE") {
|
|
20043
20401
|
try {
|
|
@@ -20045,6 +20403,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
20045
20403
|
const { pid } = JSON.parse(import_fs40.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
20046
20404
|
process.kill(pid, 0);
|
|
20047
20405
|
logDaemonStartup("port-in-use", `another daemon (pid ${pid}) owns :${DAEMON_PORT}`);
|
|
20406
|
+
recordStartupState("ok-elsewhere");
|
|
20048
20407
|
return process.exit(0);
|
|
20049
20408
|
}
|
|
20050
20409
|
} catch {
|
|
@@ -20052,13 +20411,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
20052
20411
|
import_fs40.default.unlinkSync(DAEMON_PID_FILE);
|
|
20053
20412
|
} catch {
|
|
20054
20413
|
}
|
|
20055
|
-
|
|
20414
|
+
retryListen();
|
|
20056
20415
|
return;
|
|
20057
20416
|
}
|
|
20058
20417
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/settings`, {
|
|
20059
20418
|
signal: AbortSignal.timeout(1e3)
|
|
20060
20419
|
}).then((res) => {
|
|
20061
20420
|
if (res.ok) {
|
|
20421
|
+
let adopted = false;
|
|
20062
20422
|
try {
|
|
20063
20423
|
let orphanPid = null;
|
|
20064
20424
|
const ss = (0, import_child_process2.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
@@ -20086,19 +20446,38 @@ data: ${JSON.stringify(item.data)}
|
|
|
20086
20446
|
JSON.stringify({ pid: orphanPid, port: DAEMON_PORT, internalToken, autoStarted }),
|
|
20087
20447
|
{ mode: 384 }
|
|
20088
20448
|
);
|
|
20449
|
+
adopted = true;
|
|
20089
20450
|
}
|
|
20090
20451
|
} catch {
|
|
20091
20452
|
}
|
|
20453
|
+
if (adopted) {
|
|
20454
|
+
logDaemonStartup(
|
|
20455
|
+
"port-in-use-orphan",
|
|
20456
|
+
`adopted the daemon already on :${DAEMON_PORT}`
|
|
20457
|
+
);
|
|
20458
|
+
recordStartupState("ok-elsewhere");
|
|
20459
|
+
} else {
|
|
20460
|
+
logDaemonStartup(
|
|
20461
|
+
"orphan-unidentified",
|
|
20462
|
+
`healthy daemon on :${DAEMON_PORT} could not be identified \u2014 no pid file written`
|
|
20463
|
+
);
|
|
20464
|
+
recordStartupState(
|
|
20465
|
+
"failed",
|
|
20466
|
+
"orphan-unidentified",
|
|
20467
|
+
`a healthy daemon is running on :${DAEMON_PORT} but its process could not be identified, so node9 cannot track it \u2014 install \`ss\` or \`lsof\`, or restart it with: node9 daemon --background`
|
|
20468
|
+
);
|
|
20469
|
+
}
|
|
20092
20470
|
process.exit(0);
|
|
20093
20471
|
} else {
|
|
20094
|
-
|
|
20472
|
+
retryListen();
|
|
20095
20473
|
}
|
|
20096
20474
|
}).catch(() => {
|
|
20097
|
-
|
|
20475
|
+
retryListen();
|
|
20098
20476
|
});
|
|
20099
20477
|
return;
|
|
20100
20478
|
}
|
|
20101
20479
|
logDaemonStartup("bind-failed", e.message);
|
|
20480
|
+
recordStartupState("failed", "bind-failed", e.message);
|
|
20102
20481
|
console.error(import_chalk6.default.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
|
|
20103
20482
|
process.exit(1);
|
|
20104
20483
|
});
|
|
@@ -20116,6 +20495,8 @@ data: ${JSON.stringify(item.data)}
|
|
|
20116
20495
|
{ mode: 384 }
|
|
20117
20496
|
);
|
|
20118
20497
|
console.error(import_chalk6.default.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE on 127.0.0.1:${DAEMON_PORT}`));
|
|
20498
|
+
logDaemonStartup("ok", `listening on ${DAEMON_HOST}:${DAEMON_PORT}`);
|
|
20499
|
+
recordStartupState("ok");
|
|
20119
20500
|
});
|
|
20120
20501
|
if (watchMode) {
|
|
20121
20502
|
console.error(import_chalk6.default.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
@@ -20141,6 +20522,7 @@ var init_server = __esm({
|
|
|
20141
20522
|
init_costSync();
|
|
20142
20523
|
init_sync();
|
|
20143
20524
|
init_audit_shipper();
|
|
20525
|
+
init_decision();
|
|
20144
20526
|
init_dlp_scanner();
|
|
20145
20527
|
init_mcp_reconciler();
|
|
20146
20528
|
init_hook_heal();
|
|
@@ -45343,19 +45725,19 @@ function getModelContextLimit(model) {
|
|
|
45343
45725
|
}
|
|
45344
45726
|
function readSessionUsage() {
|
|
45345
45727
|
const projectsDir = import_path67.default.join(import_os60.default.homedir(), ".claude", "projects");
|
|
45346
|
-
if (!
|
|
45728
|
+
if (!import_fs71.default.existsSync(projectsDir)) return null;
|
|
45347
45729
|
let latestFile = null;
|
|
45348
45730
|
let latestMtime = 0;
|
|
45349
45731
|
try {
|
|
45350
|
-
for (const dir of
|
|
45732
|
+
for (const dir of import_fs71.default.readdirSync(projectsDir)) {
|
|
45351
45733
|
const dirPath = import_path67.default.join(projectsDir, dir);
|
|
45352
45734
|
try {
|
|
45353
|
-
if (!
|
|
45354
|
-
for (const file of
|
|
45735
|
+
if (!import_fs71.default.statSync(dirPath).isDirectory()) continue;
|
|
45736
|
+
for (const file of import_fs71.default.readdirSync(dirPath)) {
|
|
45355
45737
|
if (!file.endsWith(".jsonl") || file.startsWith("agent-")) continue;
|
|
45356
45738
|
const filePath = import_path67.default.join(dirPath, file);
|
|
45357
45739
|
try {
|
|
45358
|
-
const mtime =
|
|
45740
|
+
const mtime = import_fs71.default.statSync(filePath).mtimeMs;
|
|
45359
45741
|
if (mtime > latestMtime) {
|
|
45360
45742
|
latestMtime = mtime;
|
|
45361
45743
|
latestFile = filePath;
|
|
@@ -45370,7 +45752,7 @@ function readSessionUsage() {
|
|
|
45370
45752
|
}
|
|
45371
45753
|
if (!latestFile) return null;
|
|
45372
45754
|
try {
|
|
45373
|
-
const lines =
|
|
45755
|
+
const lines = import_fs71.default.readFileSync(latestFile, "utf-8").split("\n");
|
|
45374
45756
|
let lastModel = "";
|
|
45375
45757
|
let lastInput = 0;
|
|
45376
45758
|
let lastOutput = 0;
|
|
@@ -45470,9 +45852,9 @@ function renderPending(activity) {
|
|
|
45470
45852
|
}
|
|
45471
45853
|
async function ensureDaemon() {
|
|
45472
45854
|
let pidPort = null;
|
|
45473
|
-
if (
|
|
45855
|
+
if (import_fs71.default.existsSync(PID_FILE)) {
|
|
45474
45856
|
try {
|
|
45475
|
-
const { port } = JSON.parse(
|
|
45857
|
+
const { port } = JSON.parse(import_fs71.default.readFileSync(PID_FILE, "utf-8"));
|
|
45476
45858
|
pidPort = port;
|
|
45477
45859
|
} catch {
|
|
45478
45860
|
console.error(import_chalk40.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -45487,12 +45869,21 @@ async function ensureDaemon() {
|
|
|
45487
45869
|
} catch {
|
|
45488
45870
|
}
|
|
45489
45871
|
console.log(import_chalk40.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
45872
|
+
const startupFd = openStartupLogFd();
|
|
45873
|
+
recordStartupState("starting");
|
|
45490
45874
|
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
45491
45875
|
detached: true,
|
|
45492
|
-
stdio: "ignore",
|
|
45876
|
+
stdio: ["ignore", "ignore", startupFd ?? "ignore"],
|
|
45493
45877
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
45494
45878
|
});
|
|
45879
|
+
child.on("error", (err2) => recordStartupState("failed", "spawn-failed", err2.message));
|
|
45495
45880
|
child.unref();
|
|
45881
|
+
if (startupFd !== void 0) {
|
|
45882
|
+
try {
|
|
45883
|
+
import_fs71.default.closeSync(startupFd);
|
|
45884
|
+
} catch {
|
|
45885
|
+
}
|
|
45886
|
+
}
|
|
45496
45887
|
for (let i = 0; i < 20; i++) {
|
|
45497
45888
|
await new Promise((r) => setTimeout(r, 250));
|
|
45498
45889
|
try {
|
|
@@ -45630,7 +46021,7 @@ function buildRecoveryCardLines(req) {
|
|
|
45630
46021
|
function readApproversFromDisk() {
|
|
45631
46022
|
const configPath = import_path67.default.join(import_os60.default.homedir(), ".node9", "config.json");
|
|
45632
46023
|
try {
|
|
45633
|
-
const raw = JSON.parse(
|
|
46024
|
+
const raw = JSON.parse(import_fs71.default.readFileSync(configPath, "utf-8"));
|
|
45634
46025
|
const settings = raw.settings ?? {};
|
|
45635
46026
|
return settings.approvers ?? {};
|
|
45636
46027
|
} catch {
|
|
@@ -45648,13 +46039,13 @@ function approverStatusLine() {
|
|
|
45648
46039
|
function toggleApprover(channel) {
|
|
45649
46040
|
const configPath = import_path67.default.join(import_os60.default.homedir(), ".node9", "config.json");
|
|
45650
46041
|
try {
|
|
45651
|
-
const raw = JSON.parse(
|
|
46042
|
+
const raw = JSON.parse(import_fs71.default.readFileSync(configPath, "utf-8"));
|
|
45652
46043
|
const settings = raw.settings ?? {};
|
|
45653
46044
|
const approvers = settings.approvers ?? {};
|
|
45654
46045
|
approvers[channel] = approvers[channel] === false;
|
|
45655
46046
|
settings.approvers = approvers;
|
|
45656
46047
|
raw.settings = settings;
|
|
45657
|
-
|
|
46048
|
+
import_fs71.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
45658
46049
|
} catch (err2) {
|
|
45659
46050
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
45660
46051
|
`);
|
|
@@ -45826,7 +46217,7 @@ async function startTail(options = {}) {
|
|
|
45826
46217
|
}
|
|
45827
46218
|
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).catch((err2) => {
|
|
45828
46219
|
try {
|
|
45829
|
-
|
|
46220
|
+
import_fs71.default.appendFileSync(
|
|
45830
46221
|
import_path67.default.join(import_os60.default.homedir(), ".node9", "hook-debug.log"),
|
|
45831
46222
|
`[tail] POST /decision failed: ${String(err2)}
|
|
45832
46223
|
`
|
|
@@ -45893,7 +46284,7 @@ async function startTail(options = {}) {
|
|
|
45893
46284
|
}
|
|
45894
46285
|
const auditLog = import_path67.default.join(import_os60.default.homedir(), ".node9", "audit.log");
|
|
45895
46286
|
try {
|
|
45896
|
-
const unackedDlp =
|
|
46287
|
+
const unackedDlp = import_fs71.default.readFileSync(auditLog, "utf-8").split("\n").filter((l) => l.includes('"response-dlp"')).length;
|
|
45897
46288
|
if (unackedDlp > 0) {
|
|
45898
46289
|
console.log("");
|
|
45899
46290
|
console.log(
|
|
@@ -45933,7 +46324,7 @@ async function startTail(options = {}) {
|
|
|
45933
46324
|
if (stallWarned) return;
|
|
45934
46325
|
if (Date.now() - lastActivityFromDaemon < STALL_THRESHOLD_MS) return;
|
|
45935
46326
|
try {
|
|
45936
|
-
const auditMtime =
|
|
46327
|
+
const auditMtime = import_fs71.default.statSync(auditLog).mtimeMs;
|
|
45937
46328
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
45938
46329
|
console.log("");
|
|
45939
46330
|
console.log(
|
|
@@ -46118,17 +46509,18 @@ async function startTail(options = {}) {
|
|
|
46118
46509
|
process.exit(1);
|
|
46119
46510
|
});
|
|
46120
46511
|
}
|
|
46121
|
-
var import_http5, import_chalk40,
|
|
46512
|
+
var import_http5, import_chalk40, import_fs71, import_os60, import_path67, import_readline6, import_child_process14, PID_FILE, ICONS, MODEL_CONTEXT_LIMITS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, pendingShownForId, pendingWrappedLines, DIVIDER;
|
|
46122
46513
|
var init_tail = __esm({
|
|
46123
46514
|
"src/tui/tail.ts"() {
|
|
46124
46515
|
"use strict";
|
|
46125
46516
|
import_http5 = __toESM(require("http"));
|
|
46126
46517
|
import_chalk40 = __toESM(require("chalk"));
|
|
46127
|
-
|
|
46518
|
+
import_fs71 = __toESM(require("fs"));
|
|
46128
46519
|
import_os60 = __toESM(require("os"));
|
|
46129
46520
|
import_path67 = __toESM(require("path"));
|
|
46130
46521
|
import_readline6 = __toESM(require("readline"));
|
|
46131
46522
|
import_child_process14 = require("child_process");
|
|
46523
|
+
init_startup_log();
|
|
46132
46524
|
init_daemon2();
|
|
46133
46525
|
init_daemon();
|
|
46134
46526
|
PID_FILE = import_path67.default.join(import_os60.default.homedir(), ".node9", "daemon.pid");
|
|
@@ -46253,9 +46645,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
46253
46645
|
return ` (${m}m left)`;
|
|
46254
46646
|
}
|
|
46255
46647
|
function safeReadJson(filePath) {
|
|
46256
|
-
if (!
|
|
46648
|
+
if (!import_fs72.default.existsSync(filePath)) return null;
|
|
46257
46649
|
try {
|
|
46258
|
-
return JSON.parse(
|
|
46650
|
+
return JSON.parse(import_fs72.default.readFileSync(filePath, "utf-8"));
|
|
46259
46651
|
} catch {
|
|
46260
46652
|
return null;
|
|
46261
46653
|
}
|
|
@@ -46276,10 +46668,10 @@ function countHooksInFile(filePath) {
|
|
|
46276
46668
|
return Object.keys(cfg.hooks).length;
|
|
46277
46669
|
}
|
|
46278
46670
|
function countRulesInDir(rulesDir) {
|
|
46279
|
-
if (!
|
|
46671
|
+
if (!import_fs72.default.existsSync(rulesDir)) return 0;
|
|
46280
46672
|
let count = 0;
|
|
46281
46673
|
try {
|
|
46282
|
-
for (const entry of
|
|
46674
|
+
for (const entry of import_fs72.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
46283
46675
|
if (entry.isDirectory()) {
|
|
46284
46676
|
count += countRulesInDir(import_path68.default.join(rulesDir, entry.name));
|
|
46285
46677
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -46305,7 +46697,7 @@ function countConfigs(cwd) {
|
|
|
46305
46697
|
let hooksCount = 0;
|
|
46306
46698
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
46307
46699
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
46308
|
-
if (
|
|
46700
|
+
if (import_fs72.default.existsSync(import_path68.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46309
46701
|
rulesCount += countRulesInDir(import_path68.default.join(claudeDir, "rules"));
|
|
46310
46702
|
const userSettings = import_path68.default.join(claudeDir, "settings.json");
|
|
46311
46703
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
@@ -46316,18 +46708,18 @@ function countConfigs(cwd) {
|
|
|
46316
46708
|
userMcpServers.delete(name);
|
|
46317
46709
|
}
|
|
46318
46710
|
if (cwd) {
|
|
46319
|
-
if (
|
|
46320
|
-
if (
|
|
46711
|
+
if (import_fs72.default.existsSync(import_path68.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
46712
|
+
if (import_fs72.default.existsSync(import_path68.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46321
46713
|
const projectClaudeDir = import_path68.default.join(cwd, ".claude");
|
|
46322
46714
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
46323
46715
|
if (!overlapsUserScope) {
|
|
46324
|
-
if (
|
|
46716
|
+
if (import_fs72.default.existsSync(import_path68.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
46325
46717
|
rulesCount += countRulesInDir(import_path68.default.join(projectClaudeDir, "rules"));
|
|
46326
46718
|
const projSettings = import_path68.default.join(projectClaudeDir, "settings.json");
|
|
46327
46719
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
46328
46720
|
hooksCount += countHooksInFile(projSettings);
|
|
46329
46721
|
}
|
|
46330
|
-
if (
|
|
46722
|
+
if (import_fs72.default.existsSync(import_path68.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
46331
46723
|
const localSettings = import_path68.default.join(projectClaudeDir, "settings.local.json");
|
|
46332
46724
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
46333
46725
|
hooksCount += countHooksInFile(localSettings);
|
|
@@ -46365,11 +46757,11 @@ function readActiveShieldsHud() {
|
|
|
46365
46757
|
}
|
|
46366
46758
|
try {
|
|
46367
46759
|
const shieldsPath = import_path68.default.join(import_os61.default.homedir(), ".node9", "shields.json");
|
|
46368
|
-
if (!
|
|
46760
|
+
if (!import_fs72.default.existsSync(shieldsPath)) {
|
|
46369
46761
|
shieldsCache = { value: [], ts: now };
|
|
46370
46762
|
return [];
|
|
46371
46763
|
}
|
|
46372
|
-
const parsed = JSON.parse(
|
|
46764
|
+
const parsed = JSON.parse(import_fs72.default.readFileSync(shieldsPath, "utf-8"));
|
|
46373
46765
|
if (!Array.isArray(parsed.active)) {
|
|
46374
46766
|
shieldsCache = { value: [], ts: now };
|
|
46375
46767
|
return [];
|
|
@@ -46471,17 +46863,17 @@ function renderContextLine(stdin) {
|
|
|
46471
46863
|
async function main() {
|
|
46472
46864
|
try {
|
|
46473
46865
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
46474
|
-
if (
|
|
46866
|
+
if (import_fs72.default.existsSync(import_path68.default.join(import_os61.default.homedir(), ".node9", "hud-debug"))) {
|
|
46475
46867
|
try {
|
|
46476
46868
|
const logPath = import_path68.default.join(import_os61.default.homedir(), ".node9", "hud-debug.log");
|
|
46477
46869
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
46478
46870
|
let size = 0;
|
|
46479
46871
|
try {
|
|
46480
|
-
size =
|
|
46872
|
+
size = import_fs72.default.statSync(logPath).size;
|
|
46481
46873
|
} catch {
|
|
46482
46874
|
}
|
|
46483
46875
|
if (size < MAX_LOG_SIZE) {
|
|
46484
|
-
|
|
46876
|
+
import_fs72.default.appendFileSync(
|
|
46485
46877
|
logPath,
|
|
46486
46878
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
46487
46879
|
);
|
|
@@ -46505,8 +46897,8 @@ async function main() {
|
|
|
46505
46897
|
import_path68.default.join(cwd, "node9.config.json"),
|
|
46506
46898
|
import_path68.default.join(import_os61.default.homedir(), ".node9", "config.json")
|
|
46507
46899
|
]) {
|
|
46508
|
-
if (!
|
|
46509
|
-
const cfg = JSON.parse(
|
|
46900
|
+
if (!import_fs72.default.existsSync(configPath)) continue;
|
|
46901
|
+
const cfg = JSON.parse(import_fs72.default.readFileSync(configPath, "utf-8"));
|
|
46510
46902
|
const hud = cfg.settings?.hud;
|
|
46511
46903
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
46512
46904
|
}
|
|
@@ -46524,11 +46916,11 @@ async function main() {
|
|
|
46524
46916
|
renderOffline();
|
|
46525
46917
|
}
|
|
46526
46918
|
}
|
|
46527
|
-
var
|
|
46919
|
+
var import_fs72, import_path68, import_os61, import_http6, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
46528
46920
|
var init_hud = __esm({
|
|
46529
46921
|
"src/cli/hud.ts"() {
|
|
46530
46922
|
"use strict";
|
|
46531
|
-
|
|
46923
|
+
import_fs72 = __toESM(require("fs"));
|
|
46532
46924
|
import_path68 = __toESM(require("path"));
|
|
46533
46925
|
import_os61 = __toESM(require("os"));
|
|
46534
46926
|
import_http6 = __toESM(require("http"));
|
|
@@ -46652,7 +47044,7 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
|
|
|
46652
47044
|
// src/cli.ts
|
|
46653
47045
|
init_daemon2();
|
|
46654
47046
|
var import_chalk41 = __toESM(require("chalk"));
|
|
46655
|
-
var
|
|
47047
|
+
var import_fs73 = __toESM(require("fs"));
|
|
46656
47048
|
var import_path69 = __toESM(require("path"));
|
|
46657
47049
|
var import_os62 = __toESM(require("os"));
|
|
46658
47050
|
var import_child_process15 = require("child_process");
|
|
@@ -46858,9 +47250,11 @@ function logAutostartSkipThrottled(reason) {
|
|
|
46858
47250
|
if (Date.now() - import_fs43.default.statSync(stamp).mtimeMs < SKIP_THROTTLE_MS) return;
|
|
46859
47251
|
} catch {
|
|
46860
47252
|
}
|
|
47253
|
+
const dir = import_path41.default.join(import_os39.default.homedir(), ".node9");
|
|
47254
|
+
if (!import_fs43.default.existsSync(dir)) import_fs43.default.mkdirSync(dir, { recursive: true });
|
|
46861
47255
|
import_fs43.default.writeFileSync(stamp, "", "utf-8");
|
|
46862
47256
|
import_fs43.default.appendFileSync(
|
|
46863
|
-
import_path41.default.join(
|
|
47257
|
+
import_path41.default.join(dir, "hook-debug.log"),
|
|
46864
47258
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] daemon-autostart-skip: ${reason}
|
|
46865
47259
|
`,
|
|
46866
47260
|
"utf-8"
|
|
@@ -46879,6 +47273,8 @@ async function autoStartDaemonAndWait() {
|
|
|
46879
47273
|
}
|
|
46880
47274
|
if (!resolvedArgv1.endsWith(".js")) return false;
|
|
46881
47275
|
const startupFd = openStartupLogFd();
|
|
47276
|
+
recordStartupState("starting");
|
|
47277
|
+
let spawned = false;
|
|
46882
47278
|
try {
|
|
46883
47279
|
const child = (0, import_child_process5.spawn)(process.execPath, [resolvedArgv1, "daemon"], {
|
|
46884
47280
|
detached: true,
|
|
@@ -46888,13 +47284,24 @@ async function autoStartDaemonAndWait() {
|
|
|
46888
47284
|
NODE9_AUTO_STARTED: "1"
|
|
46889
47285
|
}
|
|
46890
47286
|
});
|
|
47287
|
+
child.on("error", (err2) => {
|
|
47288
|
+
if (readStartupState()?.outcome !== "starting") return;
|
|
47289
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
47290
|
+
});
|
|
46891
47291
|
child.unref();
|
|
47292
|
+
spawned = true;
|
|
46892
47293
|
for (let i = 0; i < 20; i++) {
|
|
46893
47294
|
await new Promise((r) => setTimeout(r, 250));
|
|
46894
47295
|
if (!isDaemonRunning()) continue;
|
|
46895
47296
|
if (await isDaemonReachable()) return true;
|
|
46896
47297
|
}
|
|
46897
|
-
} catch {
|
|
47298
|
+
} catch (err2) {
|
|
47299
|
+
if (!spawned)
|
|
47300
|
+
recordStartupState(
|
|
47301
|
+
"failed",
|
|
47302
|
+
"spawn-failed",
|
|
47303
|
+
err2 instanceof Error ? err2.message : String(err2)
|
|
47304
|
+
);
|
|
46898
47305
|
} finally {
|
|
46899
47306
|
if (startupFd !== void 0) {
|
|
46900
47307
|
try {
|
|
@@ -47707,12 +48114,16 @@ RAW: ${raw}
|
|
|
47707
48114
|
delete safeEnv[key];
|
|
47708
48115
|
}
|
|
47709
48116
|
const startupFd = openStartupLogFd();
|
|
48117
|
+
recordStartupState("starting");
|
|
47710
48118
|
try {
|
|
47711
48119
|
const d = (0, import_child_process7.spawn)(process.execPath, [scriptPath, "daemon"], {
|
|
47712
48120
|
detached: true,
|
|
47713
48121
|
stdio: ["ignore", "ignore", startupFd ?? "ignore"],
|
|
47714
48122
|
env: { ...safeEnv, NODE9_AUTO_STARTED: "1" }
|
|
47715
48123
|
});
|
|
48124
|
+
d.on("error", (err2) => {
|
|
48125
|
+
recordStartupState("failed", "spawn-failed", err2.message);
|
|
48126
|
+
});
|
|
47716
48127
|
d.unref();
|
|
47717
48128
|
} finally {
|
|
47718
48129
|
if (startupFd !== void 0) {
|
|
@@ -47725,6 +48136,7 @@ RAW: ${raw}
|
|
|
47725
48136
|
} catch (spawnErr) {
|
|
47726
48137
|
const logPath = import_path45.default.join(import_os43.default.homedir(), ".node9", "hook-debug.log");
|
|
47727
48138
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
48139
|
+
recordStartupState("failed", "spawn-aborted", msg);
|
|
47728
48140
|
try {
|
|
47729
48141
|
import_fs47.default.appendFileSync(
|
|
47730
48142
|
logPath,
|
|
@@ -48889,6 +49301,7 @@ function agoLabel(iso, now = Date.now()) {
|
|
|
48889
49301
|
}
|
|
48890
49302
|
|
|
48891
49303
|
// src/cli/commands/doctor.ts
|
|
49304
|
+
init_startup_log();
|
|
48892
49305
|
function registerDoctorCommand(program2, version2) {
|
|
48893
49306
|
program2.command("doctor").description("Check that Node9 is installed and configured correctly").action(async () => {
|
|
48894
49307
|
const homeDir2 = import_os46.default.homedir();
|
|
@@ -49000,6 +49413,15 @@ function registerDoctorCommand(program2, version2) {
|
|
|
49000
49413
|
"Daemon not running \u2014 terminal & native approvals unavailable",
|
|
49001
49414
|
"Run: node9 daemon --background"
|
|
49002
49415
|
);
|
|
49416
|
+
const cause = readStartupCause();
|
|
49417
|
+
if (cause) {
|
|
49418
|
+
const suffix = cause.detail ? ` \u2014 ${cause.detail}` : "";
|
|
49419
|
+
console.log(
|
|
49420
|
+
import_chalk11.default.gray(
|
|
49421
|
+
` ${cause.label} ${agoLabel(cause.at.toISOString())}: ${cause.kind}${suffix}`
|
|
49422
|
+
)
|
|
49423
|
+
);
|
|
49424
|
+
}
|
|
49003
49425
|
}
|
|
49004
49426
|
const autostart = autostartAdvice({
|
|
49005
49427
|
installed: isDaemonServiceInstalled(),
|
|
@@ -49077,6 +49499,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
49077
49499
|
var import_chalk12 = __toESM(require("chalk"));
|
|
49078
49500
|
var import_fs52 = __toESM(require("fs"));
|
|
49079
49501
|
var import_path50 = __toESM(require("path"));
|
|
49502
|
+
init_decision();
|
|
49080
49503
|
var import_os47 = __toESM(require("os"));
|
|
49081
49504
|
function formatRelativeTime(timestamp) {
|
|
49082
49505
|
const diff = Date.now() - new Date(timestamp).getTime();
|
|
@@ -49108,10 +49531,16 @@ function registerAuditCommand(program2) {
|
|
|
49108
49531
|
});
|
|
49109
49532
|
entries = entries.map((e) => ({
|
|
49110
49533
|
...e,
|
|
49111
|
-
|
|
49534
|
+
// classifyDecision is the ONE mapper (audit/decision.ts). The inline
|
|
49535
|
+
// `startsWith('allow') ? allow : deny` this replaces bucketed `dlp` and
|
|
49536
|
+
// `mcp-discovered` — findings, not verdicts — as DENY, inventing
|
|
49537
|
+
// refusals that never happened.
|
|
49538
|
+
// Pass the ROW, never a field pair — the attribution key differs by
|
|
49539
|
+
// producer (`checkedBy` from the gate, `source` from the hook/daemon).
|
|
49540
|
+
view: classifyDecision(e)
|
|
49112
49541
|
}));
|
|
49113
49542
|
if (options.tool) entries = entries.filter((e) => String(e.tool).includes(options.tool));
|
|
49114
|
-
if (options.deny) entries = entries.filter((e) => e.
|
|
49543
|
+
if (options.deny) entries = entries.filter((e) => e.view.outcome === "deny");
|
|
49115
49544
|
const limit = Math.max(1, parseInt(options.tail, 10) || 20);
|
|
49116
49545
|
entries = entries.slice(-limit);
|
|
49117
49546
|
if (options.json) {
|
|
@@ -49134,13 +49563,13 @@ function registerAuditCommand(program2) {
|
|
|
49134
49563
|
for (const e of entries) {
|
|
49135
49564
|
const time = formatRelativeTime(String(e.ts)).padEnd(12);
|
|
49136
49565
|
const tool = String(e.tool).slice(0, 17).padEnd(18);
|
|
49137
|
-
const result = e.
|
|
49566
|
+
const result = e.view.outcome === "allow" ? import_chalk12.default.green(e.view.label.padEnd(14)) : e.view.outcome === "deny" ? import_chalk12.default.red(e.view.label.padEnd(14)) : e.view.outcome === "observe" ? import_chalk12.default.yellow(e.view.label.padEnd(14)) : import_chalk12.default.gray(e.view.label.padEnd(14));
|
|
49138
49567
|
const checker = String(e.checkedBy || "unknown").slice(0, 14).padEnd(15);
|
|
49139
49568
|
const agent = String(e.agent || "unknown");
|
|
49140
49569
|
console.log(` ${time} ${tool} ${result} ${checker} ${agent}`);
|
|
49141
49570
|
}
|
|
49142
|
-
const allowed = entries.filter((e) => e.
|
|
49143
|
-
const denied = entries.filter((e) => e.
|
|
49571
|
+
const allowed = entries.filter((e) => e.view.outcome === "allow").length;
|
|
49572
|
+
const denied = entries.filter((e) => e.view.outcome === "deny").length;
|
|
49144
49573
|
console.log(import_chalk12.default.dim(" " + "\u2500".repeat(65)));
|
|
49145
49574
|
console.log(
|
|
49146
49575
|
` ${entries.length} entries | ${import_chalk12.default.green(allowed + " allowed")} | ${import_chalk12.default.red(denied + " denied")}
|
|
@@ -49159,6 +49588,7 @@ var import_path51 = __toESM(require("path"));
|
|
|
49159
49588
|
init_costSync();
|
|
49160
49589
|
init_litellm();
|
|
49161
49590
|
init_cost_codex();
|
|
49591
|
+
init_decision();
|
|
49162
49592
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
49163
49593
|
function buildTestTimestamps(allEntries) {
|
|
49164
49594
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -49249,8 +49679,15 @@ function parseAuditLog(logPath) {
|
|
|
49249
49679
|
}
|
|
49250
49680
|
});
|
|
49251
49681
|
}
|
|
49252
|
-
function
|
|
49253
|
-
|
|
49682
|
+
function viewOf(e) {
|
|
49683
|
+
const outcome = classifyDecision(e).outcome;
|
|
49684
|
+
const observed = outcome === "observe";
|
|
49685
|
+
return {
|
|
49686
|
+
observed,
|
|
49687
|
+
ran: outcome === "allow" || observed,
|
|
49688
|
+
blocked: outcome === "deny" || outcome === "unknown",
|
|
49689
|
+
info: outcome === "info"
|
|
49690
|
+
};
|
|
49254
49691
|
}
|
|
49255
49692
|
function isDlp(checkedBy) {
|
|
49256
49693
|
return !!checkedBy?.includes("dlp");
|
|
@@ -49663,12 +50100,12 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49663
50100
|
const priorStart = new Date(start.getTime() - periodMs);
|
|
49664
50101
|
const priorEntries = allEntries.filter((e) => {
|
|
49665
50102
|
if (e.source === "post-hook") return false;
|
|
50103
|
+
if (e.source === "response-dlp") return false;
|
|
50104
|
+
if (typeof e.decision !== "string") return false;
|
|
49666
50105
|
const ts = new Date(e.ts);
|
|
49667
50106
|
return ts >= priorStart && ts <= priorEnd;
|
|
49668
50107
|
});
|
|
49669
|
-
const priorBlocked = priorEntries.filter(
|
|
49670
|
-
(e) => typeof e.decision === "string" && !isAllow(e.decision)
|
|
49671
|
-
).length;
|
|
50108
|
+
const priorBlocked = priorEntries.filter((e) => viewOf(e).blocked).length;
|
|
49672
50109
|
const priorBlockRate = priorEntries.length > 0 ? priorBlocked / priorEntries.length : null;
|
|
49673
50110
|
const excludeTests = opts.excludeTests === true;
|
|
49674
50111
|
const testTs = excludeTests ? buildTestTimestamps(allEntries) : /* @__PURE__ */ new Set();
|
|
@@ -49711,15 +50148,17 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49711
50148
|
let dimInjectionBlocked = 0;
|
|
49712
50149
|
for (const e of entries) {
|
|
49713
50150
|
if (superseded.has(supersedeKey(e))) continue;
|
|
49714
|
-
const
|
|
50151
|
+
const view = viewOf(e);
|
|
49715
50152
|
const dateKey = e.ts.slice(0, 10);
|
|
49716
50153
|
const userInteracted = e.source === "daemon";
|
|
49717
|
-
if (
|
|
49718
|
-
if (
|
|
50154
|
+
if (view.observed) {
|
|
50155
|
+
if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
50156
|
+
} else if (view.info) {
|
|
50157
|
+
} else if (userInteracted) {
|
|
50158
|
+
if (view.ran) userApproved++;
|
|
49719
50159
|
else userDenied++;
|
|
49720
|
-
} else if (
|
|
50160
|
+
} else if (view.blocked) {
|
|
49721
50161
|
if (e.checkedBy === "timeout") timedOut++;
|
|
49722
|
-
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
49723
50162
|
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
49724
50163
|
else if (e.checkedBy === "local-decision") userDenied++;
|
|
49725
50164
|
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
@@ -49731,7 +50170,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49731
50170
|
if (cb.includes("would-block") && (cb.includes("pii") || cb.includes("dlp"))) {
|
|
49732
50171
|
dimDataObserved++;
|
|
49733
50172
|
}
|
|
49734
|
-
if (
|
|
50173
|
+
if (view.blocked && !userInteracted) {
|
|
49735
50174
|
switch (dimensionOfBlock(cb, e.ruleName ?? "")) {
|
|
49736
50175
|
case "network":
|
|
49737
50176
|
dimNetworkBlocked++;
|
|
@@ -49749,15 +50188,15 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49749
50188
|
}
|
|
49750
50189
|
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
49751
50190
|
t.calls++;
|
|
49752
|
-
if (
|
|
50191
|
+
if (view.blocked) t.blocked++;
|
|
49753
50192
|
toolMap.set(e.tool, t);
|
|
49754
|
-
if (
|
|
50193
|
+
if (view.blocked) {
|
|
49755
50194
|
const key = e.checkedBy ?? (e.source === "daemon" ? "local-decision" : null);
|
|
49756
50195
|
if (key) {
|
|
49757
50196
|
blockMap.set(key, (blockMap.get(key) ?? 0) + 1);
|
|
49758
50197
|
}
|
|
49759
50198
|
}
|
|
49760
|
-
if (
|
|
50199
|
+
if ((view.blocked || view.observed) && e.ruleName) {
|
|
49761
50200
|
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
49762
50201
|
}
|
|
49763
50202
|
if (e.agent) agentMap.set(e.agent, (agentMap.get(e.agent) ?? 0) + 1);
|
|
@@ -49766,7 +50205,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
49766
50205
|
hourMap.set(hour, (hourMap.get(hour) ?? 0) + 1);
|
|
49767
50206
|
const d = dailyMap.get(dateKey) ?? { calls: 0, blocked: 0 };
|
|
49768
50207
|
d.calls++;
|
|
49769
|
-
if (
|
|
50208
|
+
if (view.blocked) d.blocked++;
|
|
49770
50209
|
dailyMap.set(dateKey, d);
|
|
49771
50210
|
}
|
|
49772
50211
|
for (const e of allEntries) {
|
|
@@ -50320,6 +50759,8 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
50320
50759
|
// src/cli/commands/daemon-cmd.ts
|
|
50321
50760
|
var import_chalk14 = __toESM(require("chalk"));
|
|
50322
50761
|
var import_child_process9 = require("child_process");
|
|
50762
|
+
var import_fs54 = __toESM(require("fs"));
|
|
50763
|
+
init_startup_log();
|
|
50323
50764
|
init_daemon2();
|
|
50324
50765
|
var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
|
|
50325
50766
|
function registerDaemonCommand(program2) {
|
|
@@ -50358,14 +50799,27 @@ function registerDaemonCommand(program2) {
|
|
|
50358
50799
|
if (cmd === "restart") {
|
|
50359
50800
|
stopDaemon();
|
|
50360
50801
|
await new Promise((r) => setTimeout(r, 500));
|
|
50802
|
+
const restartFd = openStartupLogFd();
|
|
50803
|
+
recordStartupState("starting");
|
|
50361
50804
|
const child = (0, import_child_process9.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
50362
50805
|
detached: true,
|
|
50363
|
-
stdio: "ignore",
|
|
50806
|
+
stdio: ["ignore", "ignore", restartFd ?? "ignore"],
|
|
50364
50807
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
50365
50808
|
});
|
|
50809
|
+
child.on(
|
|
50810
|
+
"error",
|
|
50811
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50812
|
+
);
|
|
50366
50813
|
child.unref();
|
|
50814
|
+
if (restartFd !== void 0) {
|
|
50815
|
+
try {
|
|
50816
|
+
import_fs54.default.closeSync(restartFd);
|
|
50817
|
+
} catch {
|
|
50818
|
+
}
|
|
50819
|
+
}
|
|
50367
50820
|
if (child.pid) {
|
|
50368
|
-
console.log(import_chalk14.default.green(`\u2713 Daemon
|
|
50821
|
+
console.log(import_chalk14.default.green(`\u2713 Daemon relaunching (PID ${child.pid})`));
|
|
50822
|
+
console.log(import_chalk14.default.gray(" Confirm with: node9 status"));
|
|
50369
50823
|
} else {
|
|
50370
50824
|
console.error(import_chalk14.default.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
|
|
50371
50825
|
process.exit(1);
|
|
@@ -50388,13 +50842,33 @@ function registerDaemonCommand(program2) {
|
|
|
50388
50842
|
return;
|
|
50389
50843
|
}
|
|
50390
50844
|
if (options.background) {
|
|
50391
|
-
const
|
|
50392
|
-
|
|
50393
|
-
|
|
50394
|
-
|
|
50395
|
-
|
|
50396
|
-
|
|
50397
|
-
|
|
50845
|
+
const startupFd = openStartupLogFd();
|
|
50846
|
+
try {
|
|
50847
|
+
recordStartupState("starting");
|
|
50848
|
+
const child = (0, import_child_process9.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
50849
|
+
detached: true,
|
|
50850
|
+
// Capture the child's stderr: a module-load crash prints its stack there
|
|
50851
|
+
// and dies before it can record anything itself.
|
|
50852
|
+
stdio: ["ignore", "ignore", startupFd ?? "ignore"]
|
|
50853
|
+
});
|
|
50854
|
+
child.on(
|
|
50855
|
+
"error",
|
|
50856
|
+
(err2) => recordStartupState("failed", "spawn-failed", err2.message)
|
|
50857
|
+
);
|
|
50858
|
+
child.unref();
|
|
50859
|
+
console.log(
|
|
50860
|
+
import_chalk14.default.green(`
|
|
50861
|
+
\u{1F6E1}\uFE0F Node9 daemon launching in background (PID ${child.pid})`)
|
|
50862
|
+
);
|
|
50863
|
+
console.log(import_chalk14.default.gray(" Confirm with: node9 status"));
|
|
50864
|
+
} finally {
|
|
50865
|
+
if (startupFd !== void 0) {
|
|
50866
|
+
try {
|
|
50867
|
+
import_fs54.default.closeSync(startupFd);
|
|
50868
|
+
} catch {
|
|
50869
|
+
}
|
|
50870
|
+
}
|
|
50871
|
+
}
|
|
50398
50872
|
process.exit(0);
|
|
50399
50873
|
}
|
|
50400
50874
|
startDaemon();
|
|
@@ -50404,7 +50878,7 @@ function registerDaemonCommand(program2) {
|
|
|
50404
50878
|
|
|
50405
50879
|
// src/cli/commands/status.ts
|
|
50406
50880
|
var import_chalk15 = __toESM(require("chalk"));
|
|
50407
|
-
var
|
|
50881
|
+
var import_fs55 = __toESM(require("fs"));
|
|
50408
50882
|
var import_path52 = __toESM(require("path"));
|
|
50409
50883
|
var import_os49 = __toESM(require("os"));
|
|
50410
50884
|
init_core();
|
|
@@ -50487,10 +50961,10 @@ function registerStatusCommand(program2) {
|
|
|
50487
50961
|
const projectConfig = import_path52.default.join(process.cwd(), "node9.config.json");
|
|
50488
50962
|
const globalConfig = import_path52.default.join(import_os49.default.homedir(), ".node9", "config.json");
|
|
50489
50963
|
console.log(
|
|
50490
|
-
` Local: ${
|
|
50964
|
+
` Local: ${import_fs55.default.existsSync(projectConfig) ? import_chalk15.default.green("Active (node9.config.json)") : import_chalk15.default.gray("Not present")}`
|
|
50491
50965
|
);
|
|
50492
50966
|
console.log(
|
|
50493
|
-
` Global: ${
|
|
50967
|
+
` Global: ${import_fs55.default.existsSync(globalConfig) ? import_chalk15.default.green("Active (~/.node9/config.json)") : import_chalk15.default.gray("Not present")}`
|
|
50494
50968
|
);
|
|
50495
50969
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
50496
50970
|
console.log(
|
|
@@ -50532,7 +51006,7 @@ function registerStatusCommand(program2) {
|
|
|
50532
51006
|
|
|
50533
51007
|
// src/cli/commands/init.ts
|
|
50534
51008
|
var import_chalk16 = __toESM(require("chalk"));
|
|
50535
|
-
var
|
|
51009
|
+
var import_fs56 = __toESM(require("fs"));
|
|
50536
51010
|
var import_path53 = __toESM(require("path"));
|
|
50537
51011
|
var import_os50 = __toESM(require("os"));
|
|
50538
51012
|
var import_https6 = __toESM(require("https"));
|
|
@@ -50626,15 +51100,15 @@ function registerInitCommand(program2) {
|
|
|
50626
51100
|
console.log("");
|
|
50627
51101
|
}
|
|
50628
51102
|
const configPath = import_path53.default.join(import_os50.default.homedir(), ".node9", "config.json");
|
|
50629
|
-
const isFirstInstall = !
|
|
50630
|
-
if (
|
|
51103
|
+
const isFirstInstall = !import_fs56.default.existsSync(configPath);
|
|
51104
|
+
if (import_fs56.default.existsSync(configPath) && !options.force) {
|
|
50631
51105
|
try {
|
|
50632
|
-
const existing = JSON.parse(
|
|
51106
|
+
const existing = JSON.parse(import_fs56.default.readFileSync(configPath, "utf-8"));
|
|
50633
51107
|
const settings = existing.settings ?? {};
|
|
50634
51108
|
if (settings.mode !== chosenMode) {
|
|
50635
51109
|
settings.mode = chosenMode;
|
|
50636
51110
|
existing.settings = settings;
|
|
50637
|
-
|
|
51111
|
+
import_fs56.default.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
50638
51112
|
console.log(import_chalk16.default.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
50639
51113
|
} else {
|
|
50640
51114
|
console.log(import_chalk16.default.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
@@ -50648,8 +51122,8 @@ function registerInitCommand(program2) {
|
|
|
50648
51122
|
settings: { ...DEFAULT_CONFIG.settings, mode: chosenMode }
|
|
50649
51123
|
};
|
|
50650
51124
|
const dir = import_path53.default.dirname(configPath);
|
|
50651
|
-
if (!
|
|
50652
|
-
|
|
51125
|
+
if (!import_fs56.default.existsSync(dir)) import_fs56.default.mkdirSync(dir, { recursive: true });
|
|
51126
|
+
import_fs56.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
50653
51127
|
console.log(import_chalk16.default.green(`\u2705 Config created: ${configPath}`));
|
|
50654
51128
|
console.log(import_chalk16.default.gray(` Mode: ${chosenMode}`));
|
|
50655
51129
|
}
|
|
@@ -50749,14 +51223,14 @@ function registerInitCommand(program2) {
|
|
|
50749
51223
|
|
|
50750
51224
|
// src/cli/commands/heal.ts
|
|
50751
51225
|
var import_chalk17 = __toESM(require("chalk"));
|
|
50752
|
-
var
|
|
51226
|
+
var import_fs57 = __toESM(require("fs"));
|
|
50753
51227
|
init_agent_wiring();
|
|
50754
51228
|
init_setup();
|
|
50755
51229
|
init_hook_baseline();
|
|
50756
51230
|
var hasHookSurface = (a) => a.hooks.length > 0;
|
|
50757
51231
|
function backupForHeal(file) {
|
|
50758
51232
|
try {
|
|
50759
|
-
if (file &&
|
|
51233
|
+
if (file && import_fs57.default.existsSync(file)) import_fs57.default.copyFileSync(file, `${file}.node9-heal-bak`);
|
|
50760
51234
|
} catch {
|
|
50761
51235
|
}
|
|
50762
51236
|
}
|
|
@@ -51716,16 +52190,17 @@ function registerMcpGatewayCommand(program2) {
|
|
|
51716
52190
|
|
|
51717
52191
|
// src/mcp-server/index.ts
|
|
51718
52192
|
var import_readline5 = __toESM(require("readline"));
|
|
51719
|
-
var
|
|
52193
|
+
var import_fs59 = __toESM(require("fs"));
|
|
51720
52194
|
var import_os52 = __toESM(require("os"));
|
|
51721
52195
|
var import_path56 = __toESM(require("path"));
|
|
51722
52196
|
var import_child_process11 = require("child_process");
|
|
52197
|
+
init_decision();
|
|
51723
52198
|
init_core();
|
|
51724
52199
|
init_daemon();
|
|
51725
52200
|
init_shields();
|
|
51726
52201
|
|
|
51727
52202
|
// src/auth/egress-config.ts
|
|
51728
|
-
var
|
|
52203
|
+
var import_fs58 = __toESM(require("fs"));
|
|
51729
52204
|
var import_os51 = __toESM(require("os"));
|
|
51730
52205
|
var import_path55 = __toESM(require("path"));
|
|
51731
52206
|
var DEFAULT_EGRESS = {
|
|
@@ -51741,7 +52216,7 @@ function egressConfigPath() {
|
|
|
51741
52216
|
function readEgressRawConfig() {
|
|
51742
52217
|
let text;
|
|
51743
52218
|
try {
|
|
51744
|
-
text =
|
|
52219
|
+
text = import_fs58.default.readFileSync(egressConfigPath(), "utf8");
|
|
51745
52220
|
} catch (err2) {
|
|
51746
52221
|
if (err2.code === "ENOENT") return {};
|
|
51747
52222
|
throw err2;
|
|
@@ -51756,8 +52231,8 @@ function readEgressRawConfig() {
|
|
|
51756
52231
|
}
|
|
51757
52232
|
function writeEgressRawConfig(config) {
|
|
51758
52233
|
const p = egressConfigPath();
|
|
51759
|
-
|
|
51760
|
-
|
|
52234
|
+
import_fs58.default.mkdirSync(import_path55.default.dirname(p), { recursive: true });
|
|
52235
|
+
import_fs58.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
51761
52236
|
}
|
|
51762
52237
|
function applyEgress(config, change) {
|
|
51763
52238
|
const policy = config.policy = config.policy ?? {};
|
|
@@ -51952,7 +52427,7 @@ var TOOLS = [
|
|
|
51952
52427
|
},
|
|
51953
52428
|
{
|
|
51954
52429
|
name: "node9_audit_get",
|
|
51955
|
-
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, tool name,
|
|
52430
|
+
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, outcome, tool name, the command, and the rule that fired. Outcomes use the same words as the dashboard: Auto-allowed, Approved, Ran, Blocked, Denied (a human refused), Timed out (nobody answered), Would block (shadow mode let it through), Finding, Info. Use this to review what AI actions were taken, especially refused ones.",
|
|
51956
52431
|
inputSchema: {
|
|
51957
52432
|
type: "object",
|
|
51958
52433
|
properties: {
|
|
@@ -51962,8 +52437,8 @@ var TOOLS = [
|
|
|
51962
52437
|
},
|
|
51963
52438
|
filter: {
|
|
51964
52439
|
type: "string",
|
|
51965
|
-
enum: ["all", "
|
|
51966
|
-
description: 'Filter by
|
|
52440
|
+
enum: ["all", "allow", "deny", "observe", "info", "block"],
|
|
52441
|
+
description: 'Filter by outcome. "deny" covers every refusal (rule block, human denial, timeout); "observe" is shadow-mode would-blocks; "block" is an alias for "deny". Omit or use "all" for every entry.'
|
|
51967
52442
|
}
|
|
51968
52443
|
},
|
|
51969
52444
|
required: []
|
|
@@ -52145,10 +52620,10 @@ function handleStatus() {
|
|
|
52145
52620
|
const projectConfig = import_path56.default.join(process.cwd(), "node9.config.json");
|
|
52146
52621
|
const globalConfig = import_path56.default.join(import_os52.default.homedir(), ".node9", "config.json");
|
|
52147
52622
|
lines.push(
|
|
52148
|
-
`Project config (node9.config.json): ${
|
|
52623
|
+
`Project config (node9.config.json): ${import_fs59.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
52149
52624
|
);
|
|
52150
52625
|
lines.push(
|
|
52151
|
-
`Global config (~/.node9/config.json): ${
|
|
52626
|
+
`Global config (~/.node9/config.json): ${import_fs59.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
52152
52627
|
);
|
|
52153
52628
|
return lines.join("\n");
|
|
52154
52629
|
}
|
|
@@ -52258,8 +52733,8 @@ var GLOBAL_CONFIG_PATH = import_path56.default.join(import_os52.default.homedir(
|
|
|
52258
52733
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
52259
52734
|
function readGlobalConfigRaw() {
|
|
52260
52735
|
try {
|
|
52261
|
-
if (
|
|
52262
|
-
return JSON.parse(
|
|
52736
|
+
if (import_fs59.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
52737
|
+
return JSON.parse(import_fs59.default.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
|
|
52263
52738
|
}
|
|
52264
52739
|
} catch {
|
|
52265
52740
|
}
|
|
@@ -52267,8 +52742,8 @@ function readGlobalConfigRaw() {
|
|
|
52267
52742
|
}
|
|
52268
52743
|
function writeGlobalConfigRaw(data) {
|
|
52269
52744
|
const dir = import_path56.default.dirname(GLOBAL_CONFIG_PATH);
|
|
52270
|
-
if (!
|
|
52271
|
-
|
|
52745
|
+
if (!import_fs59.default.existsSync(dir)) import_fs59.default.mkdirSync(dir, { recursive: true });
|
|
52746
|
+
import_fs59.default.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(data, null, 2) + "\n");
|
|
52272
52747
|
}
|
|
52273
52748
|
function handleApproverList() {
|
|
52274
52749
|
const config = getConfig();
|
|
@@ -52313,35 +52788,37 @@ function handleAuditGet(args) {
|
|
|
52313
52788
|
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
52314
52789
|
const filter = typeof args.filter === "string" && args.filter !== "all" ? args.filter : null;
|
|
52315
52790
|
const auditPath = import_path56.default.join(import_os52.default.homedir(), ".node9", "audit.log");
|
|
52316
|
-
if (!
|
|
52317
|
-
const rawLines =
|
|
52791
|
+
if (!import_fs59.default.existsSync(auditPath)) return "No audit log found.";
|
|
52792
|
+
const rawLines = import_fs59.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
52793
|
+
const wanted = filter === "block" ? "deny" : filter;
|
|
52318
52794
|
const parsed = [];
|
|
52319
52795
|
for (const line of rawLines) {
|
|
52320
52796
|
try {
|
|
52321
52797
|
const e = JSON.parse(line);
|
|
52322
|
-
const
|
|
52323
|
-
if (
|
|
52798
|
+
const view = classifyDecision(e);
|
|
52799
|
+
if (wanted && view.outcome !== wanted) continue;
|
|
52324
52800
|
const argsObj = e.args;
|
|
52325
52801
|
let detail = "";
|
|
52326
52802
|
if (argsObj) {
|
|
52327
52803
|
const cmd = argsObj.command ?? argsObj.file_path ?? argsObj.path ?? argsObj.sql;
|
|
52328
|
-
if (typeof cmd === "string" && cmd)
|
|
52329
|
-
detail = cmd.length > 80 ? cmd.slice(0, 80) + "\u2026" : cmd;
|
|
52330
|
-
}
|
|
52804
|
+
if (typeof cmd === "string" && cmd) detail = cmd;
|
|
52331
52805
|
}
|
|
52332
|
-
|
|
52806
|
+
if (!detail && typeof e.argsPreview === "string") detail = e.argsPreview;
|
|
52807
|
+
detail = detail.replace(/\s+/g, " ").trim();
|
|
52808
|
+
if (detail.length > 80) detail = detail.slice(0, 80) + "\u2026";
|
|
52809
|
+
const why = typeof e.ruleName === "string" && e.ruleName ? ` (${e.ruleName})` : "";
|
|
52333
52810
|
const toolPad = String(e.tool ?? "").padEnd(20);
|
|
52334
|
-
const line2 = `${e.ts} ${
|
|
52335
|
-
parsed.push({ raw: line,
|
|
52811
|
+
const line2 = `${e.ts} ${decisionTag(view)} ${toolPad} ${detail}${why}`;
|
|
52812
|
+
parsed.push({ raw: line, outcome: view.outcome, formatted: line2 });
|
|
52336
52813
|
} catch {
|
|
52337
|
-
parsed.push({ raw: line,
|
|
52814
|
+
parsed.push({ raw: line, outcome: "unknown", formatted: `[? unparseable] ${line}` });
|
|
52338
52815
|
}
|
|
52339
52816
|
}
|
|
52340
52817
|
const recent = parsed.slice(-limit);
|
|
52341
52818
|
if (recent.length === 0) {
|
|
52342
|
-
return filter ? `No ${
|
|
52819
|
+
return filter ? `No ${wanted} entries found in audit log.` : "Audit log is empty.";
|
|
52343
52820
|
}
|
|
52344
|
-
const header = filter ? `Last ${recent.length} ${
|
|
52821
|
+
const header = filter ? `Last ${recent.length} ${String(wanted).toUpperCase()} entries:` : `Last ${recent.length} audit entries:`;
|
|
52345
52822
|
return `${header}
|
|
52346
52823
|
|
|
52347
52824
|
${recent.map((e) => e.formatted).join("\n")}`;
|
|
@@ -52688,7 +53165,7 @@ function registerTrustCommand(program2) {
|
|
|
52688
53165
|
// src/cli/commands/mcp-pin.ts
|
|
52689
53166
|
var import_chalk24 = __toESM(require("chalk"));
|
|
52690
53167
|
init_mcp_pin();
|
|
52691
|
-
var
|
|
53168
|
+
var import_fs60 = __toESM(require("fs"));
|
|
52692
53169
|
|
|
52693
53170
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
52694
53171
|
var import_chalk23 = __toESM(require("chalk"));
|
|
@@ -52884,6 +53361,7 @@ Restart ${[...agents].join(", ")}`) + import_chalk23.default.gray(" to activate.
|
|
|
52884
53361
|
}
|
|
52885
53362
|
|
|
52886
53363
|
// src/cli/commands/mcp-pin.ts
|
|
53364
|
+
init_mcp_wrap();
|
|
52887
53365
|
function registerMcpPinCommand(program2) {
|
|
52888
53366
|
const pinCmd = program2.command("mcp").description("Manage MCP servers \u2014 governance (gateway) + tool-definition pinning");
|
|
52889
53367
|
registerMcpGatewayCommand2(pinCmd);
|
|
@@ -52895,7 +53373,7 @@ function registerMcpPinCommand(program2) {
|
|
|
52895
53373
|
let repoCorrupt = false;
|
|
52896
53374
|
if (found.source === "repo") {
|
|
52897
53375
|
try {
|
|
52898
|
-
const raw =
|
|
53376
|
+
const raw = import_fs60.default.readFileSync(found.path, "utf-8");
|
|
52899
53377
|
const parsed = JSON.parse(raw);
|
|
52900
53378
|
repoEntries = parsed.servers ?? {};
|
|
52901
53379
|
} catch {
|
|
@@ -53009,6 +53487,88 @@ function registerMcpPinCommand(program2) {
|
|
|
53009
53487
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
53010
53488
|
console.log(import_chalk24.default.gray(" Next connection to each server will re-pin.\n"));
|
|
53011
53489
|
});
|
|
53490
|
+
pinCmd.command("forget [serverKey]").option("--stale", "Remove all stale (orphaned) servers at once").description("Remove a server pin that is no longer configured in any agent").action((serverKey, opts) => {
|
|
53491
|
+
if (opts.stale) {
|
|
53492
|
+
forgetAllStale();
|
|
53493
|
+
return;
|
|
53494
|
+
}
|
|
53495
|
+
if (!serverKey) {
|
|
53496
|
+
console.error(
|
|
53497
|
+
import_chalk24.default.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
53498
|
+
);
|
|
53499
|
+
process.exit(1);
|
|
53500
|
+
}
|
|
53501
|
+
let pins;
|
|
53502
|
+
try {
|
|
53503
|
+
pins = readMcpPins();
|
|
53504
|
+
} catch {
|
|
53505
|
+
console.error(import_chalk24.default.red("\n\u274C Pin file is corrupt."));
|
|
53506
|
+
console.error(import_chalk24.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
53507
|
+
process.exit(1);
|
|
53508
|
+
}
|
|
53509
|
+
if (!pins.servers[serverKey]) {
|
|
53510
|
+
console.error(import_chalk24.default.red(`
|
|
53511
|
+
\u274C No pin found for server key "${serverKey}"
|
|
53512
|
+
`));
|
|
53513
|
+
console.error(`Run ${import_chalk24.default.cyan("node9 mcp pin list")} to see pinned servers.
|
|
53514
|
+
`);
|
|
53515
|
+
process.exit(1);
|
|
53516
|
+
}
|
|
53517
|
+
const inv = inventoryMcp();
|
|
53518
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53519
|
+
if (liveKeys.has(serverKey)) {
|
|
53520
|
+
const agent = "an agent config";
|
|
53521
|
+
console.error(import_chalk24.default.red(`
|
|
53522
|
+
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
53523
|
+
console.error(
|
|
53524
|
+
import_chalk24.default.yellow(
|
|
53525
|
+
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
53526
|
+
`
|
|
53527
|
+
)
|
|
53528
|
+
);
|
|
53529
|
+
process.exit(1);
|
|
53530
|
+
}
|
|
53531
|
+
const label2 = pins.servers[serverKey].label;
|
|
53532
|
+
removePin(serverKey);
|
|
53533
|
+
console.log(import_chalk24.default.green(`
|
|
53534
|
+
\u2713 Forgot server ${import_chalk24.default.cyan(serverKey)}`));
|
|
53535
|
+
console.log(import_chalk24.default.gray(` Was: ${label2}`));
|
|
53536
|
+
console.log(import_chalk24.default.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
53537
|
+
});
|
|
53538
|
+
}
|
|
53539
|
+
function forgetAllStale() {
|
|
53540
|
+
let pins;
|
|
53541
|
+
try {
|
|
53542
|
+
pins = readMcpPins();
|
|
53543
|
+
} catch {
|
|
53544
|
+
console.error(import_chalk24.default.red("\n\u274C Pin file is corrupt."));
|
|
53545
|
+
console.error(import_chalk24.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
53546
|
+
process.exit(1);
|
|
53547
|
+
}
|
|
53548
|
+
const inv = inventoryMcp();
|
|
53549
|
+
const liveKeys = inventoryServerKeys(inv);
|
|
53550
|
+
if (liveKeys.size === 0) {
|
|
53551
|
+
console.error(import_chalk24.default.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
53552
|
+
console.error(
|
|
53553
|
+
import_chalk24.default.yellow(
|
|
53554
|
+
" This usually means an agent config is missing or unreadable, not that\n every server is gone. Check your agent configs, or remove one at a\n time: node9 mcp forget <key>\n"
|
|
53555
|
+
)
|
|
53556
|
+
);
|
|
53557
|
+
process.exit(1);
|
|
53558
|
+
}
|
|
53559
|
+
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
53560
|
+
if (stale.length === 0) {
|
|
53561
|
+
console.log(import_chalk24.default.gray("\nNo stale servers to remove.\n"));
|
|
53562
|
+
return;
|
|
53563
|
+
}
|
|
53564
|
+
for (const [sk, pin] of stale) {
|
|
53565
|
+
delete pins.servers[sk];
|
|
53566
|
+
console.log(import_chalk24.default.green(` \u2713 ${import_chalk24.default.cyan(sk)} ${import_chalk24.default.gray(pin.label)}`));
|
|
53567
|
+
}
|
|
53568
|
+
writeMcpPins(pins);
|
|
53569
|
+
console.log(import_chalk24.default.green(`
|
|
53570
|
+
Removed ${stale.length} stale server(s).
|
|
53571
|
+
`));
|
|
53012
53572
|
}
|
|
53013
53573
|
|
|
53014
53574
|
// src/cli/commands/sync.ts
|
|
@@ -53253,10 +53813,16 @@ var LABEL_WIDTH = 14;
|
|
|
53253
53813
|
function label(category) {
|
|
53254
53814
|
return import_chalk27.default.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
53255
53815
|
}
|
|
53256
|
-
function
|
|
53816
|
+
function guardedWhat(f) {
|
|
53817
|
+
if (f.detail.length === 0) return "this";
|
|
53818
|
+
const reads = f.coverageProbe?.kind === "fileRead" ? "reads of " : "";
|
|
53819
|
+
const more = f.detail.length > 1 ? ` and ${f.detail.length - 1} more` : "";
|
|
53820
|
+
return `${reads}${f.detail[0]}${more}`;
|
|
53821
|
+
}
|
|
53822
|
+
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
53257
53823
|
const lines = [];
|
|
53258
53824
|
const wt = showWeight && f.scoreWeight ? import_chalk27.default.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
53259
|
-
lines.push(` ${ICON[f.severity]} ${label(
|
|
53825
|
+
lines.push(` ${ICON[f.severity]} ${label(displayLabel)}${wt}${f.title}`);
|
|
53260
53826
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
53261
53827
|
const width = 80 - indent.length;
|
|
53262
53828
|
for (const s of [f.what, f.why, f.who]) {
|
|
@@ -53293,11 +53859,11 @@ function renderPosture(result) {
|
|
|
53293
53859
|
);
|
|
53294
53860
|
const headroom = openHeadroom(result.findings);
|
|
53295
53861
|
if (headroom > 0) {
|
|
53296
|
-
|
|
53297
|
-
"
|
|
53298
|
-
|
|
53299
|
-
|
|
53300
|
-
);
|
|
53862
|
+
const openExposures = result.findings.filter(
|
|
53863
|
+
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
53864
|
+
).length;
|
|
53865
|
+
const note = openExposures > 0 ? `Of the gap to 100: ${headroom} pts is optional hardening you can choose to turn on (the \u{1F512} tier below, each at some cost to flexibility); the rest is the open findings \u2014 the flagged rows below. Fix those first.` : `${headroom} pts of headroom \u2014 optional hardening you can choose to turn on (the \u{1F512} tier below), each at some cost to flexibility.`;
|
|
53866
|
+
for (const l of wrap(note, 76)) lines.push(" " + import_chalk27.default.gray(l));
|
|
53301
53867
|
}
|
|
53302
53868
|
lines.push("");
|
|
53303
53869
|
if (result.headline) {
|
|
@@ -53310,13 +53876,18 @@ function renderPosture(result) {
|
|
|
53310
53876
|
}
|
|
53311
53877
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
53312
53878
|
const open = result.findings.filter((f) => f.coverage?.state !== "covered");
|
|
53879
|
+
const collision = new Set(
|
|
53880
|
+
covered.map((f) => f.category).filter((c) => open.some((o) => o.category === c))
|
|
53881
|
+
);
|
|
53882
|
+
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
53313
53883
|
if (covered.length > 0) {
|
|
53314
53884
|
lines.push(" " + import_chalk27.default.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
53315
53885
|
for (const f of covered) {
|
|
53316
53886
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
53317
53887
|
const via = f.coverage?.via ?? "node9";
|
|
53888
|
+
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
53318
53889
|
lines.push(
|
|
53319
|
-
` ${import_chalk27.default.green("\u2705")} ${label(
|
|
53890
|
+
` ${import_chalk27.default.green("\u2705")} ${label(lbl)}${import_chalk27.default.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
53320
53891
|
);
|
|
53321
53892
|
}
|
|
53322
53893
|
lines.push("");
|
|
@@ -53326,17 +53897,17 @@ function renderPosture(result) {
|
|
|
53326
53897
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
53327
53898
|
if (node9Open.length > 0) {
|
|
53328
53899
|
lines.push(" " + import_chalk27.default.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
53329
|
-
for (const f of node9Open) lines.push(...renderFinding(f, true));
|
|
53900
|
+
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53330
53901
|
}
|
|
53331
53902
|
if (reduceOpen.length > 0) {
|
|
53332
53903
|
if (node9Open.length > 0) lines.push("");
|
|
53333
53904
|
lines.push(" " + import_chalk27.default.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
53334
|
-
for (const f of reduceOpen) lines.push(...renderFinding(f, true));
|
|
53905
|
+
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
53335
53906
|
}
|
|
53336
53907
|
if (osOpen.length > 0) {
|
|
53337
53908
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
53338
53909
|
lines.push(" " + import_chalk27.default.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
53339
|
-
for (const f of osOpen) lines.push(...renderFinding(f));
|
|
53910
|
+
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
53340
53911
|
}
|
|
53341
53912
|
for (const cat of result.passedCategories) {
|
|
53342
53913
|
lines.push(` ${import_chalk27.default.green("\u2705")} ${label(cat)}${import_chalk27.default.gray("no issues found")}`);
|
|
@@ -53395,7 +53966,7 @@ function registerPostureCommand(program2) {
|
|
|
53395
53966
|
var import_chalk30 = __toESM(require("chalk"));
|
|
53396
53967
|
|
|
53397
53968
|
// src/ci-check/fetch.ts
|
|
53398
|
-
var
|
|
53969
|
+
var import_fs61 = __toESM(require("fs"));
|
|
53399
53970
|
var import_path57 = __toESM(require("path"));
|
|
53400
53971
|
var import_node_child_process = require("child_process");
|
|
53401
53972
|
var import_undici = __toESM(require_undici());
|
|
@@ -53480,7 +54051,7 @@ function parseRepoUrl(input) {
|
|
|
53480
54051
|
function isLocalPath(input) {
|
|
53481
54052
|
if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) return true;
|
|
53482
54053
|
try {
|
|
53483
|
-
return
|
|
54054
|
+
return import_fs61.default.existsSync(input) && import_fs61.default.statSync(input).isDirectory();
|
|
53484
54055
|
} catch {
|
|
53485
54056
|
return false;
|
|
53486
54057
|
}
|
|
@@ -53597,8 +54168,8 @@ function readLocalTree(dir) {
|
|
|
53597
54168
|
const add = (rel) => {
|
|
53598
54169
|
const abs = import_path57.default.join(root, rel);
|
|
53599
54170
|
try {
|
|
53600
|
-
if (
|
|
53601
|
-
files.push({ path: rel, content:
|
|
54171
|
+
if (import_fs61.default.existsSync(abs) && import_fs61.default.statSync(abs).isFile()) {
|
|
54172
|
+
files.push({ path: rel, content: import_fs61.default.readFileSync(abs, "utf8") });
|
|
53602
54173
|
}
|
|
53603
54174
|
} catch {
|
|
53604
54175
|
}
|
|
@@ -53618,7 +54189,7 @@ function readLocalTree(dir) {
|
|
|
53618
54189
|
dirsVisited++;
|
|
53619
54190
|
let entries;
|
|
53620
54191
|
try {
|
|
53621
|
-
entries =
|
|
54192
|
+
entries = import_fs61.default.readdirSync(import_path57.default.join(root, relDir), { withFileTypes: true });
|
|
53622
54193
|
} catch {
|
|
53623
54194
|
return;
|
|
53624
54195
|
}
|
|
@@ -53641,8 +54212,8 @@ function readLocalTree(dir) {
|
|
|
53641
54212
|
for (const rel of matches) collect(rel);
|
|
53642
54213
|
const wfDir = import_path57.default.join(root, WORKFLOW_DIR);
|
|
53643
54214
|
try {
|
|
53644
|
-
if (
|
|
53645
|
-
for (const name of
|
|
54215
|
+
if (import_fs61.default.existsSync(wfDir)) {
|
|
54216
|
+
for (const name of import_fs61.default.readdirSync(wfDir)) {
|
|
53646
54217
|
if (/\.ya?ml$/.test(name)) add(import_path57.default.join(WORKFLOW_DIR, name));
|
|
53647
54218
|
}
|
|
53648
54219
|
}
|
|
@@ -54781,7 +55352,7 @@ function registerEgressCommand(program2) {
|
|
|
54781
55352
|
var import_chalk32 = __toESM(require("chalk"));
|
|
54782
55353
|
|
|
54783
55354
|
// src/shields/jail.ts
|
|
54784
|
-
var
|
|
55355
|
+
var import_fs62 = __toESM(require("fs"));
|
|
54785
55356
|
var import_os53 = __toESM(require("os"));
|
|
54786
55357
|
var import_path58 = __toESM(require("path"));
|
|
54787
55358
|
init_build();
|
|
@@ -54793,7 +55364,7 @@ function jailStorePath() {
|
|
|
54793
55364
|
function readJailPaths() {
|
|
54794
55365
|
let text;
|
|
54795
55366
|
try {
|
|
54796
|
-
text =
|
|
55367
|
+
text = import_fs62.default.readFileSync(jailStorePath(), "utf8");
|
|
54797
55368
|
} catch (err2) {
|
|
54798
55369
|
if (err2.code === "ENOENT") return [];
|
|
54799
55370
|
throw err2;
|
|
@@ -54811,8 +55382,8 @@ function readJailPaths() {
|
|
|
54811
55382
|
}
|
|
54812
55383
|
function writeJailPaths(paths) {
|
|
54813
55384
|
const p = jailStorePath();
|
|
54814
|
-
|
|
54815
|
-
|
|
55385
|
+
import_fs62.default.mkdirSync(import_path58.default.dirname(p), { recursive: true });
|
|
55386
|
+
import_fs62.default.writeFileSync(p, JSON.stringify({ paths }, null, 2) + "\n", { mode: 384 });
|
|
54816
55387
|
}
|
|
54817
55388
|
function addJailPath(rawPath, verdict) {
|
|
54818
55389
|
const norm = rawPath.trim();
|
|
@@ -54841,7 +55412,7 @@ function regenerateUserJail(paths) {
|
|
|
54841
55412
|
writeActiveShields(active2.filter((s) => s !== USER_JAIL_SHIELD));
|
|
54842
55413
|
}
|
|
54843
55414
|
try {
|
|
54844
|
-
|
|
55415
|
+
import_fs62.default.rmSync(file, { force: true });
|
|
54845
55416
|
} catch {
|
|
54846
55417
|
}
|
|
54847
55418
|
return;
|
|
@@ -54955,13 +55526,13 @@ function registerJailCommand(program2) {
|
|
|
54955
55526
|
|
|
54956
55527
|
// src/cli/commands/sandbox.ts
|
|
54957
55528
|
var import_chalk33 = __toESM(require("chalk"));
|
|
54958
|
-
var
|
|
55529
|
+
var import_fs65 = __toESM(require("fs"));
|
|
54959
55530
|
var import_path61 = __toESM(require("path"));
|
|
54960
55531
|
var import_child_process13 = require("child_process");
|
|
54961
55532
|
init_config();
|
|
54962
55533
|
|
|
54963
55534
|
// src/sandbox/config.ts
|
|
54964
|
-
var
|
|
55535
|
+
var import_fs63 = __toESM(require("fs"));
|
|
54965
55536
|
var import_path59 = __toESM(require("path"));
|
|
54966
55537
|
var import_yaml2 = require("yaml");
|
|
54967
55538
|
var SANDBOX_CONFIG_FILE = "node9.sandbox.yaml";
|
|
@@ -55039,12 +55610,12 @@ function sandboxConfigPath(cwd = process.cwd()) {
|
|
|
55039
55610
|
}
|
|
55040
55611
|
function loadSandboxConfig(cwd = process.cwd(), fallbackAgent = "claude") {
|
|
55041
55612
|
const p = sandboxConfigPath(cwd);
|
|
55042
|
-
if (!
|
|
55613
|
+
if (!import_fs63.default.existsSync(p)) {
|
|
55043
55614
|
throw new Error(`sandbox: ${SANDBOX_CONFIG_FILE} not found \u2014 run \`node9 sandbox new\` first.`);
|
|
55044
55615
|
}
|
|
55045
55616
|
let raw;
|
|
55046
55617
|
try {
|
|
55047
|
-
raw = (0, import_yaml2.parse)(
|
|
55618
|
+
raw = (0, import_yaml2.parse)(import_fs63.default.readFileSync(p, "utf-8"));
|
|
55048
55619
|
} catch (err2) {
|
|
55049
55620
|
throw new Error(
|
|
55050
55621
|
`sandbox: ${SANDBOX_CONFIG_FILE} is not valid YAML \u2014 ${err2.message}`
|
|
@@ -55102,7 +55673,7 @@ function compileAllowlist(input) {
|
|
|
55102
55673
|
init_templates();
|
|
55103
55674
|
|
|
55104
55675
|
// src/sandbox/runtime.ts
|
|
55105
|
-
var
|
|
55676
|
+
var import_fs64 = __toESM(require("fs"));
|
|
55106
55677
|
var import_os54 = __toESM(require("os"));
|
|
55107
55678
|
var import_path60 = __toESM(require("path"));
|
|
55108
55679
|
var import_crypto14 = __toESM(require("crypto"));
|
|
@@ -55130,7 +55701,7 @@ function buildRunArgs(opts) {
|
|
|
55130
55701
|
args.push("-v", `${allowlistHostPath}:${ALLOWED_DOMAINS_PATH}:ro`);
|
|
55131
55702
|
if (config.node9.mountAgentCredentials) {
|
|
55132
55703
|
const creds = agentCredentialsMount(config.agent);
|
|
55133
|
-
if (
|
|
55704
|
+
if (import_fs64.default.existsSync(creds.hostPath)) {
|
|
55134
55705
|
args.push("-v", `${creds.hostPath}:${creds.target}`);
|
|
55135
55706
|
}
|
|
55136
55707
|
}
|
|
@@ -55152,16 +55723,16 @@ function sandboxBuildDir(cwd = process.cwd()) {
|
|
|
55152
55723
|
}
|
|
55153
55724
|
function writeBuildContext(cwd, dockerfile, entrypoint) {
|
|
55154
55725
|
const dir = sandboxBuildDir(cwd);
|
|
55155
|
-
|
|
55156
|
-
|
|
55157
|
-
|
|
55726
|
+
import_fs64.default.mkdirSync(dir, { recursive: true });
|
|
55727
|
+
import_fs64.default.writeFileSync(import_path60.default.join(dir, "Dockerfile"), dockerfile);
|
|
55728
|
+
import_fs64.default.writeFileSync(import_path60.default.join(dir, "entrypoint.sh"), entrypoint);
|
|
55158
55729
|
return dir;
|
|
55159
55730
|
}
|
|
55160
55731
|
function writeAllowlist(cwd, hosts) {
|
|
55161
55732
|
const dir = import_path60.default.join(cwd, ".node9", "sandbox");
|
|
55162
|
-
|
|
55733
|
+
import_fs64.default.mkdirSync(dir, { recursive: true });
|
|
55163
55734
|
const p = import_path60.default.join(dir, "allowed-domains.txt");
|
|
55164
|
-
|
|
55735
|
+
import_fs64.default.writeFileSync(p, hosts.join("\n") + "\n");
|
|
55165
55736
|
return p;
|
|
55166
55737
|
}
|
|
55167
55738
|
function resolveHomePath(p) {
|
|
@@ -55170,7 +55741,7 @@ function resolveHomePath(p) {
|
|
|
55170
55741
|
|
|
55171
55742
|
// src/cli/commands/sandbox.ts
|
|
55172
55743
|
function seedDataDirConfig(dataDir, sandbox) {
|
|
55173
|
-
|
|
55744
|
+
import_fs65.default.mkdirSync(dataDir, { recursive: true });
|
|
55174
55745
|
const configPath = import_path61.default.join(dataDir, "config.json");
|
|
55175
55746
|
const seed = {
|
|
55176
55747
|
settings: {
|
|
@@ -55182,7 +55753,7 @@ function seedDataDirConfig(dataDir, sandbox) {
|
|
|
55182
55753
|
}
|
|
55183
55754
|
}
|
|
55184
55755
|
};
|
|
55185
|
-
|
|
55756
|
+
import_fs65.default.writeFileSync(configPath, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
55186
55757
|
}
|
|
55187
55758
|
function registerSandboxCommand(program2, version2) {
|
|
55188
55759
|
const node9Version2 = pinnedNode9Version(version2);
|
|
@@ -55190,13 +55761,13 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55190
55761
|
cmd.command("new").description(`Scaffold ${SANDBOX_CONFIG_FILE} in this project`).option("--agent <agent>", "claude (default) or codex", "claude").action((opts) => {
|
|
55191
55762
|
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
55192
55763
|
const p = sandboxConfigPath();
|
|
55193
|
-
if (
|
|
55764
|
+
if (import_fs65.default.existsSync(p)) {
|
|
55194
55765
|
console.log(
|
|
55195
55766
|
import_chalk33.default.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
55196
55767
|
);
|
|
55197
55768
|
return;
|
|
55198
55769
|
}
|
|
55199
|
-
|
|
55770
|
+
import_fs65.default.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
55200
55771
|
console.log(
|
|
55201
55772
|
import_chalk33.default.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + import_chalk33.default.dim(` (agent: ${agent})`)
|
|
55202
55773
|
);
|
|
@@ -55237,7 +55808,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55237
55808
|
const hash = imageContentHash(dockerfile, entrypoint);
|
|
55238
55809
|
const image = sandbox.runtime.image;
|
|
55239
55810
|
const hashFile = import_path61.default.join(sandboxBuildDir(cwd), ".image-hash");
|
|
55240
|
-
const lastHash =
|
|
55811
|
+
const lastHash = import_fs65.default.existsSync(hashFile) ? import_fs65.default.readFileSync(hashFile, "utf-8").trim() : "";
|
|
55241
55812
|
const imageExists = (0, import_child_process13.spawnSync)(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
55242
55813
|
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
55243
55814
|
if (needBuild) {
|
|
@@ -55249,7 +55820,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55249
55820
|
console.error(import_chalk33.default.red(" build failed."));
|
|
55250
55821
|
process.exit(b.status ?? 1);
|
|
55251
55822
|
}
|
|
55252
|
-
|
|
55823
|
+
import_fs65.default.writeFileSync(hashFile, hash);
|
|
55253
55824
|
}
|
|
55254
55825
|
const dataDir = sandboxDataDir(cwd);
|
|
55255
55826
|
seedDataDirConfig(dataDir, sandbox);
|
|
@@ -55263,7 +55834,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55263
55834
|
});
|
|
55264
55835
|
if (sandbox.node9.mountAgentCredentials) {
|
|
55265
55836
|
const creds = agentCredentialsMount(sandbox.agent);
|
|
55266
|
-
if (
|
|
55837
|
+
if (import_fs65.default.existsSync(creds.hostPath)) {
|
|
55267
55838
|
console.log(import_chalk33.default.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
55268
55839
|
} else {
|
|
55269
55840
|
console.log(
|
|
@@ -55280,7 +55851,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55280
55851
|
});
|
|
55281
55852
|
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
55282
55853
|
const auditPath = import_path61.default.join(sandboxDataDir(), "audit.log");
|
|
55283
|
-
if (!
|
|
55854
|
+
if (!import_fs65.default.existsSync(auditPath)) {
|
|
55284
55855
|
console.log(import_chalk33.default.dim(" no sandbox audit yet."));
|
|
55285
55856
|
return;
|
|
55286
55857
|
}
|
|
@@ -55288,11 +55859,11 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55288
55859
|
});
|
|
55289
55860
|
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
55290
55861
|
const auditPath = import_path61.default.join(sandboxDataDir(), "audit.log");
|
|
55291
|
-
if (!
|
|
55862
|
+
if (!import_fs65.default.existsSync(auditPath)) {
|
|
55292
55863
|
console.log(import_chalk33.default.dim(" no sandbox audit yet."));
|
|
55293
55864
|
return;
|
|
55294
55865
|
}
|
|
55295
|
-
process.stdout.write(
|
|
55866
|
+
process.stdout.write(import_fs65.default.readFileSync(auditPath, "utf-8"));
|
|
55296
55867
|
});
|
|
55297
55868
|
cmd.command("clean").description("Remove the sandbox image, build context, and data").action(() => {
|
|
55298
55869
|
const cwd = process.cwd();
|
|
@@ -55306,15 +55877,16 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55306
55877
|
stdio: "ignore"
|
|
55307
55878
|
});
|
|
55308
55879
|
}
|
|
55309
|
-
|
|
55880
|
+
import_fs65.default.rmSync(import_path61.default.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
55310
55881
|
console.log(import_chalk33.default.green(" \u2713 sandbox image + build + data removed."));
|
|
55311
55882
|
});
|
|
55312
55883
|
}
|
|
55313
55884
|
|
|
55314
55885
|
// src/cli/commands/sessions.ts
|
|
55315
55886
|
var import_chalk34 = __toESM(require("chalk"));
|
|
55316
|
-
var
|
|
55887
|
+
var import_fs66 = __toESM(require("fs"));
|
|
55317
55888
|
var import_path62 = __toESM(require("path"));
|
|
55889
|
+
init_decision();
|
|
55318
55890
|
var import_os55 = __toESM(require("os"));
|
|
55319
55891
|
init_scan_summary();
|
|
55320
55892
|
init_litellm();
|
|
@@ -55411,7 +55983,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55411
55983
|
const aPath = auditPath ?? import_path62.default.join(import_os55.default.homedir(), ".node9", "audit.log");
|
|
55412
55984
|
let raw;
|
|
55413
55985
|
try {
|
|
55414
|
-
raw =
|
|
55986
|
+
raw = import_fs66.default.readFileSync(aPath, "utf-8");
|
|
55415
55987
|
} catch {
|
|
55416
55988
|
return [];
|
|
55417
55989
|
}
|
|
@@ -55421,7 +55993,7 @@ function loadAuditEntries(auditPath) {
|
|
|
55421
55993
|
try {
|
|
55422
55994
|
const e = JSON.parse(line);
|
|
55423
55995
|
if (!e.ts || !e.tool || !e.decision) continue;
|
|
55424
|
-
if (e.
|
|
55996
|
+
if (classifyDecision(e).outcome === "allow") continue;
|
|
55425
55997
|
entries.push(e);
|
|
55426
55998
|
} catch {
|
|
55427
55999
|
}
|
|
@@ -55448,7 +56020,7 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
55448
56020
|
}
|
|
55449
56021
|
function buildGeminiSessions(days, allAuditEntries) {
|
|
55450
56022
|
const tmpDir = import_path62.default.join(import_os55.default.homedir(), ".gemini", "tmp");
|
|
55451
|
-
if (!
|
|
56023
|
+
if (!import_fs66.default.existsSync(tmpDir)) return [];
|
|
55452
56024
|
const cutoff = days !== null ? (() => {
|
|
55453
56025
|
const d = /* @__PURE__ */ new Date();
|
|
55454
56026
|
d.setDate(d.getDate() - days);
|
|
@@ -55457,7 +56029,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55457
56029
|
})() : null;
|
|
55458
56030
|
let slugDirs;
|
|
55459
56031
|
try {
|
|
55460
|
-
slugDirs =
|
|
56032
|
+
slugDirs = import_fs66.default.readdirSync(tmpDir);
|
|
55461
56033
|
} catch {
|
|
55462
56034
|
return [];
|
|
55463
56035
|
}
|
|
@@ -55465,27 +56037,27 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55465
56037
|
for (const slug2 of slugDirs) {
|
|
55466
56038
|
const slugPath = import_path62.default.join(tmpDir, slug2);
|
|
55467
56039
|
try {
|
|
55468
|
-
if (!
|
|
56040
|
+
if (!import_fs66.default.statSync(slugPath).isDirectory()) continue;
|
|
55469
56041
|
} catch {
|
|
55470
56042
|
continue;
|
|
55471
56043
|
}
|
|
55472
56044
|
let projectRoot = import_path62.default.join(import_os55.default.homedir(), slug2);
|
|
55473
56045
|
try {
|
|
55474
|
-
projectRoot =
|
|
56046
|
+
projectRoot = import_fs66.default.readFileSync(import_path62.default.join(slugPath, ".project_root"), "utf-8").trim();
|
|
55475
56047
|
} catch {
|
|
55476
56048
|
}
|
|
55477
56049
|
const chatsDir = import_path62.default.join(slugPath, "chats");
|
|
55478
|
-
if (!
|
|
56050
|
+
if (!import_fs66.default.existsSync(chatsDir)) continue;
|
|
55479
56051
|
let chatFiles;
|
|
55480
56052
|
try {
|
|
55481
|
-
chatFiles =
|
|
56053
|
+
chatFiles = import_fs66.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
55482
56054
|
} catch {
|
|
55483
56055
|
continue;
|
|
55484
56056
|
}
|
|
55485
56057
|
for (const chatFile of chatFiles) {
|
|
55486
56058
|
let raw;
|
|
55487
56059
|
try {
|
|
55488
|
-
raw =
|
|
56060
|
+
raw = import_fs66.default.readFileSync(import_path62.default.join(chatsDir, chatFile), "utf-8");
|
|
55489
56061
|
} catch {
|
|
55490
56062
|
continue;
|
|
55491
56063
|
}
|
|
@@ -55566,7 +56138,7 @@ function buildGeminiSessions(days, allAuditEntries) {
|
|
|
55566
56138
|
}
|
|
55567
56139
|
function buildCodexSessions(days, allAuditEntries) {
|
|
55568
56140
|
const sessionsBase = import_path62.default.join(import_os55.default.homedir(), ".codex", "sessions");
|
|
55569
|
-
if (!
|
|
56141
|
+
if (!import_fs66.default.existsSync(sessionsBase)) return [];
|
|
55570
56142
|
const cutoff = days !== null ? (() => {
|
|
55571
56143
|
const d = /* @__PURE__ */ new Date();
|
|
55572
56144
|
d.setDate(d.getDate() - days);
|
|
@@ -55575,28 +56147,28 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55575
56147
|
})() : null;
|
|
55576
56148
|
const jsonlFiles = [];
|
|
55577
56149
|
try {
|
|
55578
|
-
for (const year of
|
|
56150
|
+
for (const year of import_fs66.default.readdirSync(sessionsBase)) {
|
|
55579
56151
|
const yearPath = import_path62.default.join(sessionsBase, year);
|
|
55580
56152
|
try {
|
|
55581
|
-
if (!
|
|
56153
|
+
if (!import_fs66.default.statSync(yearPath).isDirectory()) continue;
|
|
55582
56154
|
} catch {
|
|
55583
56155
|
continue;
|
|
55584
56156
|
}
|
|
55585
|
-
for (const month of
|
|
56157
|
+
for (const month of import_fs66.default.readdirSync(yearPath)) {
|
|
55586
56158
|
const monthPath = import_path62.default.join(yearPath, month);
|
|
55587
56159
|
try {
|
|
55588
|
-
if (!
|
|
56160
|
+
if (!import_fs66.default.statSync(monthPath).isDirectory()) continue;
|
|
55589
56161
|
} catch {
|
|
55590
56162
|
continue;
|
|
55591
56163
|
}
|
|
55592
|
-
for (const day of
|
|
56164
|
+
for (const day of import_fs66.default.readdirSync(monthPath)) {
|
|
55593
56165
|
const dayPath = import_path62.default.join(monthPath, day);
|
|
55594
56166
|
try {
|
|
55595
|
-
if (!
|
|
56167
|
+
if (!import_fs66.default.statSync(dayPath).isDirectory()) continue;
|
|
55596
56168
|
} catch {
|
|
55597
56169
|
continue;
|
|
55598
56170
|
}
|
|
55599
|
-
for (const file of
|
|
56171
|
+
for (const file of import_fs66.default.readdirSync(dayPath)) {
|
|
55600
56172
|
if (file.endsWith(".jsonl")) jsonlFiles.push(import_path62.default.join(dayPath, file));
|
|
55601
56173
|
}
|
|
55602
56174
|
}
|
|
@@ -55609,7 +56181,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
55609
56181
|
for (const filePath of jsonlFiles) {
|
|
55610
56182
|
let lines;
|
|
55611
56183
|
try {
|
|
55612
|
-
lines =
|
|
56184
|
+
lines = import_fs66.default.readFileSync(filePath, "utf-8").split("\n");
|
|
55613
56185
|
} catch {
|
|
55614
56186
|
continue;
|
|
55615
56187
|
}
|
|
@@ -55698,7 +56270,7 @@ function buildSessions(days, historyPath) {
|
|
|
55698
56270
|
const hPath = historyPath ?? import_path62.default.join(import_os55.default.homedir(), ".claude", "history.jsonl");
|
|
55699
56271
|
let historyRaw = "";
|
|
55700
56272
|
try {
|
|
55701
|
-
historyRaw =
|
|
56273
|
+
historyRaw = import_fs66.default.readFileSync(hPath, "utf-8");
|
|
55702
56274
|
} catch {
|
|
55703
56275
|
}
|
|
55704
56276
|
const cutoff = days !== null ? (() => {
|
|
@@ -55722,7 +56294,7 @@ function buildSessions(days, historyPath) {
|
|
|
55722
56294
|
const jsonlFile = sessionJsonlPath(entry.project, entry.sessionId);
|
|
55723
56295
|
let sessionLines = [];
|
|
55724
56296
|
try {
|
|
55725
|
-
sessionLines =
|
|
56297
|
+
sessionLines = import_fs66.default.readFileSync(jsonlFile, "utf-8").split("\n");
|
|
55726
56298
|
} catch {
|
|
55727
56299
|
}
|
|
55728
56300
|
const { toolCalls, costUSD, hasSnapshot, modifiedFiles } = parseSessionLines(sessionLines);
|
|
@@ -56116,12 +56688,12 @@ function registerSessionTaintCommand(program2) {
|
|
|
56116
56688
|
|
|
56117
56689
|
// src/cli/commands/skill-pin.ts
|
|
56118
56690
|
var import_chalk36 = __toESM(require("chalk"));
|
|
56119
|
-
var
|
|
56691
|
+
var import_fs67 = __toESM(require("fs"));
|
|
56120
56692
|
var import_os56 = __toESM(require("os"));
|
|
56121
56693
|
var import_path63 = __toESM(require("path"));
|
|
56122
56694
|
function wipeSkillSessions() {
|
|
56123
56695
|
try {
|
|
56124
|
-
|
|
56696
|
+
import_fs67.default.rmSync(import_path63.default.join(import_os56.default.homedir(), ".node9", "skill-sessions"), {
|
|
56125
56697
|
recursive: true,
|
|
56126
56698
|
force: true
|
|
56127
56699
|
});
|
|
@@ -56203,15 +56775,15 @@ function registerSkillPinCommand(program2) {
|
|
|
56203
56775
|
}
|
|
56204
56776
|
|
|
56205
56777
|
// src/cli/commands/decisions.ts
|
|
56206
|
-
var
|
|
56778
|
+
var import_fs68 = __toESM(require("fs"));
|
|
56207
56779
|
var import_os57 = __toESM(require("os"));
|
|
56208
56780
|
var import_path64 = __toESM(require("path"));
|
|
56209
56781
|
var import_chalk37 = __toESM(require("chalk"));
|
|
56210
56782
|
var DECISIONS_FILE2 = import_path64.default.join(import_os57.default.homedir(), ".node9", "decisions.json");
|
|
56211
56783
|
function readDecisions() {
|
|
56212
56784
|
try {
|
|
56213
|
-
if (!
|
|
56214
|
-
const raw =
|
|
56785
|
+
if (!import_fs68.default.existsSync(DECISIONS_FILE2)) return {};
|
|
56786
|
+
const raw = import_fs68.default.readFileSync(DECISIONS_FILE2, "utf-8");
|
|
56215
56787
|
const parsed = JSON.parse(raw);
|
|
56216
56788
|
const out = {};
|
|
56217
56789
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -56224,10 +56796,10 @@ function readDecisions() {
|
|
|
56224
56796
|
}
|
|
56225
56797
|
function writeDecisions(d) {
|
|
56226
56798
|
const dir = import_path64.default.dirname(DECISIONS_FILE2);
|
|
56227
|
-
if (!
|
|
56799
|
+
if (!import_fs68.default.existsSync(dir)) import_fs68.default.mkdirSync(dir, { recursive: true });
|
|
56228
56800
|
const tmp = `${DECISIONS_FILE2}.${process.pid}.tmp`;
|
|
56229
|
-
|
|
56230
|
-
|
|
56801
|
+
import_fs68.default.writeFileSync(tmp, JSON.stringify(d, null, 2));
|
|
56802
|
+
import_fs68.default.renameSync(tmp, DECISIONS_FILE2);
|
|
56231
56803
|
}
|
|
56232
56804
|
function registerDecisionsCommand(program2) {
|
|
56233
56805
|
const cmd = program2.command("decisions").description('Manage persistent "Always Allow" / "Always Deny" tool decisions');
|
|
@@ -56284,7 +56856,7 @@ Persistent decisions (${entries.length})
|
|
|
56284
56856
|
|
|
56285
56857
|
// src/cli/commands/dlp.ts
|
|
56286
56858
|
var import_chalk38 = __toESM(require("chalk"));
|
|
56287
|
-
var
|
|
56859
|
+
var import_fs69 = __toESM(require("fs"));
|
|
56288
56860
|
var import_path65 = __toESM(require("path"));
|
|
56289
56861
|
var import_os58 = __toESM(require("os"));
|
|
56290
56862
|
var AUDIT_LOG = import_path65.default.join(import_os58.default.homedir(), ".node9", "audit.log");
|
|
@@ -56295,7 +56867,7 @@ function stripAnsi(s) {
|
|
|
56295
56867
|
}
|
|
56296
56868
|
function loadResolved() {
|
|
56297
56869
|
try {
|
|
56298
|
-
const raw = JSON.parse(
|
|
56870
|
+
const raw = JSON.parse(import_fs69.default.readFileSync(RESOLVED_FILE, "utf-8"));
|
|
56299
56871
|
return new Set(raw);
|
|
56300
56872
|
} catch {
|
|
56301
56873
|
return /* @__PURE__ */ new Set();
|
|
@@ -56303,13 +56875,13 @@ function loadResolved() {
|
|
|
56303
56875
|
}
|
|
56304
56876
|
function saveResolved(resolved) {
|
|
56305
56877
|
try {
|
|
56306
|
-
|
|
56878
|
+
import_fs69.default.writeFileSync(RESOLVED_FILE, JSON.stringify([...resolved], null, 2), { mode: 384 });
|
|
56307
56879
|
} catch {
|
|
56308
56880
|
}
|
|
56309
56881
|
}
|
|
56310
56882
|
function loadDlpFindings() {
|
|
56311
|
-
if (!
|
|
56312
|
-
return
|
|
56883
|
+
if (!import_fs69.default.existsSync(AUDIT_LOG)) return [];
|
|
56884
|
+
return import_fs69.default.readFileSync(AUDIT_LOG, "utf-8").split("\n").flatMap((line) => {
|
|
56313
56885
|
if (!line.trim()) return [];
|
|
56314
56886
|
try {
|
|
56315
56887
|
const e = JSON.parse(line);
|
|
@@ -56407,14 +56979,14 @@ function registerDlpCommand(program2) {
|
|
|
56407
56979
|
|
|
56408
56980
|
// src/cli/commands/mask.ts
|
|
56409
56981
|
var import_chalk39 = __toESM(require("chalk"));
|
|
56410
|
-
var
|
|
56982
|
+
var import_fs70 = __toESM(require("fs"));
|
|
56411
56983
|
var import_path66 = __toESM(require("path"));
|
|
56412
56984
|
var import_os59 = __toESM(require("os"));
|
|
56413
56985
|
init_dlp();
|
|
56414
56986
|
function findJsonlFiles(dir) {
|
|
56415
56987
|
const results = [];
|
|
56416
|
-
if (!
|
|
56417
|
-
for (const entry of
|
|
56988
|
+
if (!import_fs70.default.existsSync(dir)) return results;
|
|
56989
|
+
for (const entry of import_fs70.default.readdirSync(dir, { withFileTypes: true })) {
|
|
56418
56990
|
const full = import_path66.default.join(dir, entry.name);
|
|
56419
56991
|
if (entry.isDirectory()) results.push(...findJsonlFiles(full));
|
|
56420
56992
|
else if (entry.isFile() && entry.name.endsWith(".jsonl")) results.push(full);
|
|
@@ -56458,7 +57030,7 @@ function redactJson(obj) {
|
|
|
56458
57030
|
function processFile(filePath, dryRun) {
|
|
56459
57031
|
let raw;
|
|
56460
57032
|
try {
|
|
56461
|
-
raw =
|
|
57033
|
+
raw = import_fs70.default.readFileSync(filePath, "utf-8");
|
|
56462
57034
|
} catch {
|
|
56463
57035
|
return { redactedLines: 0, patterns: [] };
|
|
56464
57036
|
}
|
|
@@ -56490,14 +57062,14 @@ function processFile(filePath, dryRun) {
|
|
|
56490
57062
|
}
|
|
56491
57063
|
}
|
|
56492
57064
|
if (!dryRun && redactedLines > 0) {
|
|
56493
|
-
|
|
57065
|
+
import_fs70.default.writeFileSync(filePath, newLines.join("\n"), "utf-8");
|
|
56494
57066
|
}
|
|
56495
57067
|
return { redactedLines, patterns };
|
|
56496
57068
|
}
|
|
56497
57069
|
function processJsonFile(filePath, dryRun) {
|
|
56498
57070
|
let raw;
|
|
56499
57071
|
try {
|
|
56500
|
-
raw =
|
|
57072
|
+
raw = import_fs70.default.readFileSync(filePath, "utf-8");
|
|
56501
57073
|
} catch {
|
|
56502
57074
|
return { redactedLines: 0, patterns: [] };
|
|
56503
57075
|
}
|
|
@@ -56510,14 +57082,14 @@ function processJsonFile(filePath, dryRun) {
|
|
|
56510
57082
|
const { value, modified, found } = redactJson(parsed);
|
|
56511
57083
|
if (!modified) return { redactedLines: 0, patterns: [] };
|
|
56512
57084
|
if (!dryRun) {
|
|
56513
|
-
|
|
57085
|
+
import_fs70.default.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
56514
57086
|
}
|
|
56515
57087
|
return { redactedLines: 1, patterns: found };
|
|
56516
57088
|
}
|
|
56517
57089
|
function findJsonFiles(dir) {
|
|
56518
57090
|
const results = [];
|
|
56519
|
-
if (!
|
|
56520
|
-
for (const entry of
|
|
57091
|
+
if (!import_fs70.default.existsSync(dir)) return results;
|
|
57092
|
+
for (const entry of import_fs70.default.readdirSync(dir, { withFileTypes: true })) {
|
|
56521
57093
|
const full = import_path66.default.join(dir, entry.name);
|
|
56522
57094
|
if (entry.isDirectory()) results.push(...findJsonFiles(full));
|
|
56523
57095
|
else if (entry.isFile() && entry.name.endsWith(".json")) results.push(full);
|
|
@@ -56537,7 +57109,7 @@ function registerMaskCommand(program2) {
|
|
|
56537
57109
|
const cutoff = options.all ? null : new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
56538
57110
|
const filtered = cutoff ? allFiles.filter((f) => {
|
|
56539
57111
|
try {
|
|
56540
|
-
return
|
|
57112
|
+
return import_fs70.default.statSync(f.path).mtime >= cutoff;
|
|
56541
57113
|
} catch {
|
|
56542
57114
|
return false;
|
|
56543
57115
|
}
|
|
@@ -56593,7 +57165,7 @@ function registerMaskCommand(program2) {
|
|
|
56593
57165
|
// src/cli.ts
|
|
56594
57166
|
init_blast();
|
|
56595
57167
|
var { version } = JSON.parse(
|
|
56596
|
-
|
|
57168
|
+
import_fs73.default.readFileSync(import_path69.default.join(__dirname, "../package.json"), "utf-8")
|
|
56597
57169
|
);
|
|
56598
57170
|
var program = new import_commander.Command();
|
|
56599
57171
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
@@ -56773,14 +57345,14 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
56773
57345
|
}
|
|
56774
57346
|
if (options.purge) {
|
|
56775
57347
|
const node9Dir = import_path69.default.join(import_os62.default.homedir(), ".node9");
|
|
56776
|
-
if (
|
|
57348
|
+
if (import_fs73.default.existsSync(node9Dir)) {
|
|
56777
57349
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
56778
57350
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
56779
57351
|
default: false
|
|
56780
57352
|
});
|
|
56781
57353
|
if (confirmed) {
|
|
56782
|
-
|
|
56783
|
-
if (
|
|
57354
|
+
import_fs73.default.rmSync(node9Dir, { recursive: true });
|
|
57355
|
+
if (import_fs73.default.existsSync(node9Dir)) {
|
|
56784
57356
|
console.error(
|
|
56785
57357
|
import_chalk41.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
56786
57358
|
);
|
|
@@ -56945,12 +57517,12 @@ Run "node9 addto claude" to register it as the statusLine.`
|
|
|
56945
57517
|
if (subcommand === "debug") {
|
|
56946
57518
|
const flagFile = import_path69.default.join(import_os62.default.homedir(), ".node9", "hud-debug");
|
|
56947
57519
|
if (state === "on") {
|
|
56948
|
-
|
|
56949
|
-
|
|
57520
|
+
import_fs73.default.mkdirSync(import_path69.default.dirname(flagFile), { recursive: true });
|
|
57521
|
+
import_fs73.default.writeFileSync(flagFile, "");
|
|
56950
57522
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
56951
57523
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
56952
57524
|
} else if (state === "off") {
|
|
56953
|
-
if (
|
|
57525
|
+
if (import_fs73.default.existsSync(flagFile)) import_fs73.default.unlinkSync(flagFile);
|
|
56954
57526
|
console.log("HUD debug logging disabled.");
|
|
56955
57527
|
} else {
|
|
56956
57528
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -57075,7 +57647,7 @@ if (process.argv[2] !== "daemon") {
|
|
|
57075
57647
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
57076
57648
|
const logPath = import_path69.default.join(import_os62.default.homedir(), ".node9", "hook-debug.log");
|
|
57077
57649
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
57078
|
-
|
|
57650
|
+
import_fs73.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
57079
57651
|
`);
|
|
57080
57652
|
}
|
|
57081
57653
|
process.exit(0);
|