@node9/proxy 1.0.19 → 1.1.1
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/README.md +3 -1
- package/dist/cli.js +523 -251
- package/dist/cli.mjs +529 -257
- package/dist/index.js +178 -59
- package/dist/index.mjs +178 -59
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -380,8 +380,8 @@ function sanitizeConfig(raw) {
|
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
const lines = result.error.issues.map((issue) => {
|
|
383
|
-
const
|
|
384
|
-
return ` \u2022 ${
|
|
383
|
+
const path11 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
384
|
+
return ` \u2022 ${path11}: ${issue.message}`;
|
|
385
385
|
});
|
|
386
386
|
return {
|
|
387
387
|
sanitized,
|
|
@@ -696,6 +696,39 @@ var init_shields = __esm({
|
|
|
696
696
|
});
|
|
697
697
|
|
|
698
698
|
// src/dlp.ts
|
|
699
|
+
function scanFilePath(filePath, cwd = process.cwd()) {
|
|
700
|
+
if (!filePath) return null;
|
|
701
|
+
let resolved;
|
|
702
|
+
try {
|
|
703
|
+
const absolute = import_path4.default.resolve(cwd, filePath);
|
|
704
|
+
resolved = import_fs2.default.realpathSync.native(absolute);
|
|
705
|
+
} catch (err) {
|
|
706
|
+
const code = err.code;
|
|
707
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
708
|
+
resolved = import_path4.default.resolve(cwd, filePath);
|
|
709
|
+
} else {
|
|
710
|
+
return {
|
|
711
|
+
patternName: "Sensitive File Path",
|
|
712
|
+
fieldPath: "file_path",
|
|
713
|
+
redactedSample: filePath,
|
|
714
|
+
severity: "block"
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const normalised = resolved.replace(/\\/g, "/");
|
|
719
|
+
for (const pattern of SENSITIVE_PATH_PATTERNS) {
|
|
720
|
+
if (pattern.test(normalised)) {
|
|
721
|
+
return {
|
|
722
|
+
patternName: "Sensitive File Path",
|
|
723
|
+
fieldPath: "file_path",
|
|
724
|
+
redactedSample: filePath,
|
|
725
|
+
// show original path in alert, not resolved
|
|
726
|
+
severity: "block"
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
699
732
|
function maskSecret(raw, pattern) {
|
|
700
733
|
const match = raw.match(pattern);
|
|
701
734
|
if (!match) return "****";
|
|
@@ -748,14 +781,18 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
|
748
781
|
}
|
|
749
782
|
return null;
|
|
750
783
|
}
|
|
751
|
-
var DLP_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
784
|
+
var import_fs2, import_path4, DLP_PATTERNS, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES;
|
|
752
785
|
var init_dlp = __esm({
|
|
753
786
|
"src/dlp.ts"() {
|
|
754
787
|
"use strict";
|
|
788
|
+
import_fs2 = __toESM(require("fs"));
|
|
789
|
+
import_path4 = __toESM(require("path"));
|
|
755
790
|
DLP_PATTERNS = [
|
|
756
791
|
{ name: "AWS Access Key ID", regex: /\bAKIA[0-9A-Z]{16}\b/, severity: "block" },
|
|
757
792
|
{ name: "GitHub Token", regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/, severity: "block" },
|
|
758
|
-
|
|
793
|
+
// Slack bot tokens: xoxb- + variable segment. Real tokens are ~50–80 chars;
|
|
794
|
+
// lower bound 20 avoids false negatives on partial tokens, upper 100 caps scan cost.
|
|
795
|
+
{ name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]{20,100}\b/, severity: "block" },
|
|
759
796
|
{ name: "OpenAI API Key", regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/, severity: "block" },
|
|
760
797
|
{ name: "Stripe Secret Key", regex: /\bsk_(?:live|test)_[0-9a-zA-Z]{24}\b/, severity: "block" },
|
|
761
798
|
{
|
|
@@ -763,8 +800,43 @@ var init_dlp = __esm({
|
|
|
763
800
|
regex: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/,
|
|
764
801
|
severity: "block"
|
|
765
802
|
},
|
|
803
|
+
// GCP service account JSON (detects the type field that uniquely identifies it)
|
|
804
|
+
{
|
|
805
|
+
name: "GCP Service Account",
|
|
806
|
+
regex: /"type"\s*:\s*"service_account"/,
|
|
807
|
+
severity: "block"
|
|
808
|
+
},
|
|
809
|
+
// NPM auth token in .npmrc format
|
|
810
|
+
{
|
|
811
|
+
name: "NPM Auth Token",
|
|
812
|
+
regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
|
|
813
|
+
severity: "block"
|
|
814
|
+
},
|
|
766
815
|
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]+=*/i, severity: "review" }
|
|
767
816
|
];
|
|
817
|
+
SENSITIVE_PATH_PATTERNS = [
|
|
818
|
+
/[/\\]\.ssh[/\\]/i,
|
|
819
|
+
/[/\\]\.aws[/\\]/i,
|
|
820
|
+
/[/\\]\.config[/\\]gcloud[/\\]/i,
|
|
821
|
+
/[/\\]\.azure[/\\]/i,
|
|
822
|
+
/[/\\]\.kube[/\\]config$/i,
|
|
823
|
+
/[/\\]\.env($|\.)/i,
|
|
824
|
+
// .env, .env.local, .env.production — not .envoy
|
|
825
|
+
/[/\\]\.git-credentials$/i,
|
|
826
|
+
/[/\\]\.npmrc$/i,
|
|
827
|
+
/[/\\]\.docker[/\\]config\.json$/i,
|
|
828
|
+
/[/\\][^/\\]+\.pem$/i,
|
|
829
|
+
/[/\\][^/\\]+\.key$/i,
|
|
830
|
+
/[/\\][^/\\]+\.p12$/i,
|
|
831
|
+
/[/\\][^/\\]+\.pfx$/i,
|
|
832
|
+
/^\/etc\/passwd$/,
|
|
833
|
+
/^\/etc\/shadow$/,
|
|
834
|
+
/^\/etc\/sudoers$/,
|
|
835
|
+
/[/\\]credentials\.json$/i,
|
|
836
|
+
/[/\\]id_rsa$/i,
|
|
837
|
+
/[/\\]id_ed25519$/i,
|
|
838
|
+
/[/\\]id_ecdsa$/i
|
|
839
|
+
];
|
|
768
840
|
MAX_DEPTH = 5;
|
|
769
841
|
MAX_STRING_BYTES = 1e5;
|
|
770
842
|
MAX_JSON_PARSE_BYTES = 1e4;
|
|
@@ -774,11 +846,11 @@ var init_dlp = __esm({
|
|
|
774
846
|
// src/core.ts
|
|
775
847
|
function checkPause() {
|
|
776
848
|
try {
|
|
777
|
-
if (!
|
|
778
|
-
const state = JSON.parse(
|
|
849
|
+
if (!import_fs3.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
850
|
+
const state = JSON.parse(import_fs3.default.readFileSync(PAUSED_FILE, "utf-8"));
|
|
779
851
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
780
852
|
try {
|
|
781
|
-
|
|
853
|
+
import_fs3.default.unlinkSync(PAUSED_FILE);
|
|
782
854
|
} catch {
|
|
783
855
|
}
|
|
784
856
|
return { paused: false };
|
|
@@ -789,11 +861,11 @@ function checkPause() {
|
|
|
789
861
|
}
|
|
790
862
|
}
|
|
791
863
|
function atomicWriteSync(filePath, data, options) {
|
|
792
|
-
const dir =
|
|
793
|
-
if (!
|
|
864
|
+
const dir = import_path5.default.dirname(filePath);
|
|
865
|
+
if (!import_fs3.default.existsSync(dir)) import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
794
866
|
const tmpPath = `${filePath}.${import_os2.default.hostname()}.${process.pid}.tmp`;
|
|
795
|
-
|
|
796
|
-
|
|
867
|
+
import_fs3.default.writeFileSync(tmpPath, data, options);
|
|
868
|
+
import_fs3.default.renameSync(tmpPath, filePath);
|
|
797
869
|
}
|
|
798
870
|
function pauseNode9(durationMs, durationStr) {
|
|
799
871
|
const state = { expiry: Date.now() + durationMs, duration: durationStr };
|
|
@@ -801,18 +873,61 @@ function pauseNode9(durationMs, durationStr) {
|
|
|
801
873
|
}
|
|
802
874
|
function resumeNode9() {
|
|
803
875
|
try {
|
|
804
|
-
if (
|
|
876
|
+
if (import_fs3.default.existsSync(PAUSED_FILE)) import_fs3.default.unlinkSync(PAUSED_FILE);
|
|
805
877
|
} catch {
|
|
806
878
|
}
|
|
807
879
|
}
|
|
880
|
+
function validateRegex(pattern) {
|
|
881
|
+
if (!pattern) return "Pattern is required";
|
|
882
|
+
if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
|
|
883
|
+
try {
|
|
884
|
+
new RegExp(pattern);
|
|
885
|
+
} catch (e) {
|
|
886
|
+
return `Invalid regex syntax: ${e.message}`;
|
|
887
|
+
}
|
|
888
|
+
if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
|
|
889
|
+
if (!(0, import_safe_regex2.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
function getCompiledRegex(pattern, flags = "") {
|
|
893
|
+
if (flags && !/^[gimsuy]+$/.test(flags)) {
|
|
894
|
+
if (process.env.NODE9_DEBUG === "1") console.error(`[Node9] Invalid regex flags: "${flags}"`);
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
const key = `${pattern}\0${flags}`;
|
|
898
|
+
if (regexCache.has(key)) {
|
|
899
|
+
const cached = regexCache.get(key);
|
|
900
|
+
regexCache.delete(key);
|
|
901
|
+
regexCache.set(key, cached);
|
|
902
|
+
return cached;
|
|
903
|
+
}
|
|
904
|
+
const err = validateRegex(pattern);
|
|
905
|
+
if (err) {
|
|
906
|
+
if (process.env.NODE9_DEBUG === "1")
|
|
907
|
+
console.error(`[Node9] Regex blocked: ${err} \u2014 pattern: "${pattern}"`);
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
try {
|
|
911
|
+
const re = new RegExp(pattern, flags);
|
|
912
|
+
if (regexCache.size >= REGEX_CACHE_MAX) {
|
|
913
|
+
const oldest = regexCache.keys().next().value;
|
|
914
|
+
if (oldest) regexCache.delete(oldest);
|
|
915
|
+
}
|
|
916
|
+
regexCache.set(key, re);
|
|
917
|
+
return re;
|
|
918
|
+
} catch (e) {
|
|
919
|
+
if (process.env.NODE9_DEBUG === "1") console.error(`[Node9] Regex compile failed:`, e);
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
808
923
|
function getActiveTrustSession(toolName) {
|
|
809
924
|
try {
|
|
810
|
-
if (!
|
|
811
|
-
const trust = JSON.parse(
|
|
925
|
+
if (!import_fs3.default.existsSync(TRUST_FILE)) return false;
|
|
926
|
+
const trust = JSON.parse(import_fs3.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
812
927
|
const now = Date.now();
|
|
813
928
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
814
929
|
if (active.length !== trust.entries.length) {
|
|
815
|
-
|
|
930
|
+
import_fs3.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
816
931
|
}
|
|
817
932
|
return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
|
|
818
933
|
} catch {
|
|
@@ -823,8 +938,8 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
823
938
|
try {
|
|
824
939
|
let trust = { entries: [] };
|
|
825
940
|
try {
|
|
826
|
-
if (
|
|
827
|
-
trust = JSON.parse(
|
|
941
|
+
if (import_fs3.default.existsSync(TRUST_FILE)) {
|
|
942
|
+
trust = JSON.parse(import_fs3.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
828
943
|
}
|
|
829
944
|
} catch {
|
|
830
945
|
}
|
|
@@ -840,9 +955,9 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
840
955
|
}
|
|
841
956
|
function appendToLog(logPath, entry) {
|
|
842
957
|
try {
|
|
843
|
-
const dir =
|
|
844
|
-
if (!
|
|
845
|
-
|
|
958
|
+
const dir = import_path5.default.dirname(logPath);
|
|
959
|
+
if (!import_fs3.default.existsSync(dir)) import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
960
|
+
import_fs3.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
846
961
|
} catch {
|
|
847
962
|
}
|
|
848
963
|
}
|
|
@@ -884,9 +999,9 @@ function matchesPattern(text, patterns) {
|
|
|
884
999
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
885
1000
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
886
1001
|
}
|
|
887
|
-
function getNestedValue(obj,
|
|
1002
|
+
function getNestedValue(obj, path11) {
|
|
888
1003
|
if (!obj || typeof obj !== "object") return null;
|
|
889
|
-
return
|
|
1004
|
+
return path11.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
890
1005
|
}
|
|
891
1006
|
function shouldSnapshot(toolName, args, config) {
|
|
892
1007
|
if (!config.settings.enableUndo) return false;
|
|
@@ -917,19 +1032,16 @@ function evaluateSmartConditions(args, rule) {
|
|
|
917
1032
|
return val !== null && cond.value ? !val.includes(cond.value) : true;
|
|
918
1033
|
case "matches": {
|
|
919
1034
|
if (val === null || !cond.value) return false;
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
1035
|
+
const reM = getCompiledRegex(cond.value, cond.flags ?? "");
|
|
1036
|
+
if (!reM) return false;
|
|
1037
|
+
return reM.test(val);
|
|
925
1038
|
}
|
|
926
1039
|
case "notMatches": {
|
|
927
|
-
if (
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
}
|
|
1040
|
+
if (!cond.value) return false;
|
|
1041
|
+
if (val === null) return true;
|
|
1042
|
+
const reN = getCompiledRegex(cond.value, cond.flags ?? "");
|
|
1043
|
+
if (!reN) return false;
|
|
1044
|
+
return !reN.test(val);
|
|
933
1045
|
}
|
|
934
1046
|
case "matchesGlob":
|
|
935
1047
|
return val !== null && cond.value ? import_picomatch.default.isMatch(val, cond.value) : false;
|
|
@@ -1042,9 +1154,9 @@ function _resetConfigCache() {
|
|
|
1042
1154
|
}
|
|
1043
1155
|
function getGlobalSettings() {
|
|
1044
1156
|
try {
|
|
1045
|
-
const globalConfigPath =
|
|
1046
|
-
if (
|
|
1047
|
-
const parsed = JSON.parse(
|
|
1157
|
+
const globalConfigPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
|
|
1158
|
+
if (import_fs3.default.existsSync(globalConfigPath)) {
|
|
1159
|
+
const parsed = JSON.parse(import_fs3.default.readFileSync(globalConfigPath, "utf-8"));
|
|
1048
1160
|
const settings = parsed.settings || {};
|
|
1049
1161
|
return {
|
|
1050
1162
|
mode: settings.mode || "audit",
|
|
@@ -1066,9 +1178,9 @@ function getGlobalSettings() {
|
|
|
1066
1178
|
}
|
|
1067
1179
|
function getInternalToken() {
|
|
1068
1180
|
try {
|
|
1069
|
-
const pidFile =
|
|
1070
|
-
if (!
|
|
1071
|
-
const data = JSON.parse(
|
|
1181
|
+
const pidFile = import_path5.default.join(import_os2.default.homedir(), ".node9", "daemon.pid");
|
|
1182
|
+
if (!import_fs3.default.existsSync(pidFile)) return null;
|
|
1183
|
+
const data = JSON.parse(import_fs3.default.readFileSync(pidFile, "utf-8"));
|
|
1072
1184
|
process.kill(data.pid, 0);
|
|
1073
1185
|
return data.internalToken ?? null;
|
|
1074
1186
|
} catch {
|
|
@@ -1183,9 +1295,9 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
1183
1295
|
}
|
|
1184
1296
|
async function explainPolicy(toolName, args) {
|
|
1185
1297
|
const steps = [];
|
|
1186
|
-
const globalPath =
|
|
1187
|
-
const projectPath =
|
|
1188
|
-
const credsPath =
|
|
1298
|
+
const globalPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
|
|
1299
|
+
const projectPath = import_path5.default.join(process.cwd(), "node9.config.json");
|
|
1300
|
+
const credsPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "credentials.json");
|
|
1189
1301
|
const waterfall = [
|
|
1190
1302
|
{
|
|
1191
1303
|
tier: 1,
|
|
@@ -1196,19 +1308,19 @@ async function explainPolicy(toolName, args) {
|
|
|
1196
1308
|
{
|
|
1197
1309
|
tier: 2,
|
|
1198
1310
|
label: "Cloud policy",
|
|
1199
|
-
status:
|
|
1200
|
-
note:
|
|
1311
|
+
status: import_fs3.default.existsSync(credsPath) ? "active" : "missing",
|
|
1312
|
+
note: import_fs3.default.existsSync(credsPath) ? "credentials found (not evaluated in explain mode)" : "not connected \u2014 run: node9 login"
|
|
1201
1313
|
},
|
|
1202
1314
|
{
|
|
1203
1315
|
tier: 3,
|
|
1204
1316
|
label: "Project config",
|
|
1205
|
-
status:
|
|
1317
|
+
status: import_fs3.default.existsSync(projectPath) ? "active" : "missing",
|
|
1206
1318
|
path: projectPath
|
|
1207
1319
|
},
|
|
1208
1320
|
{
|
|
1209
1321
|
tier: 4,
|
|
1210
1322
|
label: "Global config",
|
|
1211
|
-
status:
|
|
1323
|
+
status: import_fs3.default.existsSync(globalPath) ? "active" : "missing",
|
|
1212
1324
|
path: globalPath
|
|
1213
1325
|
},
|
|
1214
1326
|
{
|
|
@@ -1221,7 +1333,9 @@ async function explainPolicy(toolName, args) {
|
|
|
1221
1333
|
const config = getConfig();
|
|
1222
1334
|
const wouldBeIgnored = matchesPattern(toolName, config.policy.ignoredTools);
|
|
1223
1335
|
if (config.policy.dlp.enabled && (!wouldBeIgnored || config.policy.dlp.scanIgnoredTools)) {
|
|
1224
|
-
const
|
|
1336
|
+
const argsObjE = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
1337
|
+
const filePathE = String(argsObjE.file_path ?? argsObjE.path ?? argsObjE.filename ?? "");
|
|
1338
|
+
const dlpMatch = (filePathE ? scanFilePath(filePathE) : null) ?? (args !== void 0 ? scanArgs(args) : null);
|
|
1225
1339
|
if (dlpMatch) {
|
|
1226
1340
|
steps.push({
|
|
1227
1341
|
name: "DLP Content Scanner",
|
|
@@ -1444,10 +1558,10 @@ function isIgnoredTool(toolName) {
|
|
|
1444
1558
|
return matchesPattern(toolName, config.policy.ignoredTools);
|
|
1445
1559
|
}
|
|
1446
1560
|
function isDaemonRunning() {
|
|
1447
|
-
const pidFile =
|
|
1448
|
-
if (
|
|
1561
|
+
const pidFile = import_path5.default.join(import_os2.default.homedir(), ".node9", "daemon.pid");
|
|
1562
|
+
if (import_fs3.default.existsSync(pidFile)) {
|
|
1449
1563
|
try {
|
|
1450
|
-
const { pid, port } = JSON.parse(
|
|
1564
|
+
const { pid, port } = JSON.parse(import_fs3.default.readFileSync(pidFile, "utf-8"));
|
|
1451
1565
|
if (port !== DAEMON_PORT) return false;
|
|
1452
1566
|
process.kill(pid, 0);
|
|
1453
1567
|
return true;
|
|
@@ -1467,9 +1581,9 @@ function isDaemonRunning() {
|
|
|
1467
1581
|
}
|
|
1468
1582
|
function getPersistentDecision(toolName) {
|
|
1469
1583
|
try {
|
|
1470
|
-
const file =
|
|
1471
|
-
if (!
|
|
1472
|
-
const decisions = JSON.parse(
|
|
1584
|
+
const file = import_path5.default.join(import_os2.default.homedir(), ".node9", "decisions.json");
|
|
1585
|
+
if (!import_fs3.default.existsSync(file)) return null;
|
|
1586
|
+
const decisions = JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
|
|
1473
1587
|
const d = decisions[toolName];
|
|
1474
1588
|
if (d === "allow" || d === "deny") return d;
|
|
1475
1589
|
} catch {
|
|
@@ -1612,7 +1726,9 @@ async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = fa
|
|
|
1612
1726
|
let policyMatchedWord;
|
|
1613
1727
|
let riskMetadata;
|
|
1614
1728
|
if (config.policy.dlp.enabled && (!isIgnoredTool(toolName) || config.policy.dlp.scanIgnoredTools)) {
|
|
1615
|
-
const
|
|
1729
|
+
const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
1730
|
+
const filePath = String(argsObj.file_path ?? argsObj.path ?? argsObj.filename ?? "");
|
|
1731
|
+
const dlpMatch = (filePath ? scanFilePath(filePath) : null) ?? scanArgs(args);
|
|
1616
1732
|
if (dlpMatch) {
|
|
1617
1733
|
const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
|
|
1618
1734
|
if (dlpMatch.severity === "block") {
|
|
@@ -1984,10 +2100,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
1984
2100
|
}
|
|
1985
2101
|
return finalResult;
|
|
1986
2102
|
}
|
|
1987
|
-
function getConfig() {
|
|
1988
|
-
if (cachedConfig) return cachedConfig;
|
|
1989
|
-
const globalPath =
|
|
1990
|
-
const projectPath =
|
|
2103
|
+
function getConfig(cwd) {
|
|
2104
|
+
if (!cwd && cachedConfig) return cachedConfig;
|
|
2105
|
+
const globalPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
|
|
2106
|
+
const projectPath = import_path5.default.join(cwd ?? process.cwd(), "node9.config.json");
|
|
1991
2107
|
const globalConfig = tryLoadConfig(globalPath);
|
|
1992
2108
|
const projectConfig = tryLoadConfig(projectPath);
|
|
1993
2109
|
const mergedSettings = {
|
|
@@ -2074,18 +2190,19 @@ function getConfig() {
|
|
|
2074
2190
|
mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
|
|
2075
2191
|
mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
|
|
2076
2192
|
mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
|
|
2077
|
-
|
|
2193
|
+
const result = {
|
|
2078
2194
|
settings: mergedSettings,
|
|
2079
2195
|
policy: mergedPolicy,
|
|
2080
2196
|
environments: mergedEnvironments
|
|
2081
2197
|
};
|
|
2082
|
-
|
|
2198
|
+
if (!cwd) cachedConfig = result;
|
|
2199
|
+
return result;
|
|
2083
2200
|
}
|
|
2084
2201
|
function tryLoadConfig(filePath) {
|
|
2085
|
-
if (!
|
|
2202
|
+
if (!import_fs3.default.existsSync(filePath)) return null;
|
|
2086
2203
|
let raw;
|
|
2087
2204
|
try {
|
|
2088
|
-
raw = JSON.parse(
|
|
2205
|
+
raw = JSON.parse(import_fs3.default.readFileSync(filePath, "utf-8"));
|
|
2089
2206
|
} catch (err) {
|
|
2090
2207
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2091
2208
|
process.stderr.write(
|
|
@@ -2147,9 +2264,9 @@ function getCredentials() {
|
|
|
2147
2264
|
};
|
|
2148
2265
|
}
|
|
2149
2266
|
try {
|
|
2150
|
-
const credPath =
|
|
2151
|
-
if (
|
|
2152
|
-
const creds = JSON.parse(
|
|
2267
|
+
const credPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "credentials.json");
|
|
2268
|
+
if (import_fs3.default.existsSync(credPath)) {
|
|
2269
|
+
const creds = JSON.parse(import_fs3.default.readFileSync(credPath, "utf-8"));
|
|
2153
2270
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
2154
2271
|
const profile = creds[profileName];
|
|
2155
2272
|
if (profile?.apiKey) {
|
|
@@ -2262,29 +2379,33 @@ async function resolveNode9SaaS(requestId, creds, approved) {
|
|
|
2262
2379
|
} catch {
|
|
2263
2380
|
}
|
|
2264
2381
|
}
|
|
2265
|
-
var import_chalk2, import_prompts,
|
|
2382
|
+
var import_chalk2, import_prompts, import_fs3, import_path5, import_os2, import_net, import_crypto2, import_child_process2, import_picomatch, import_safe_regex2, import_sh_syntax, PAUSED_FILE, TRUST_FILE, LOCAL_AUDIT_LOG, HOOK_DEBUG_LOG, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, SQL_DML_KEYWORDS, DANGEROUS_WORDS, DEFAULT_CONFIG, ADVISORY_SMART_RULES, cachedConfig, DAEMON_PORT, DAEMON_HOST, ACTIVITY_SOCKET_PATH;
|
|
2266
2383
|
var init_core = __esm({
|
|
2267
2384
|
"src/core.ts"() {
|
|
2268
2385
|
"use strict";
|
|
2269
2386
|
import_chalk2 = __toESM(require("chalk"));
|
|
2270
2387
|
import_prompts = require("@inquirer/prompts");
|
|
2271
|
-
|
|
2272
|
-
|
|
2388
|
+
import_fs3 = __toESM(require("fs"));
|
|
2389
|
+
import_path5 = __toESM(require("path"));
|
|
2273
2390
|
import_os2 = __toESM(require("os"));
|
|
2274
2391
|
import_net = __toESM(require("net"));
|
|
2275
2392
|
import_crypto2 = require("crypto");
|
|
2276
2393
|
import_child_process2 = require("child_process");
|
|
2277
2394
|
import_picomatch = __toESM(require("picomatch"));
|
|
2395
|
+
import_safe_regex2 = __toESM(require("safe-regex2"));
|
|
2278
2396
|
import_sh_syntax = require("sh-syntax");
|
|
2279
2397
|
init_native();
|
|
2280
2398
|
init_context_sniper();
|
|
2281
2399
|
init_config_schema();
|
|
2282
2400
|
init_shields();
|
|
2283
2401
|
init_dlp();
|
|
2284
|
-
PAUSED_FILE =
|
|
2285
|
-
TRUST_FILE =
|
|
2286
|
-
LOCAL_AUDIT_LOG =
|
|
2287
|
-
HOOK_DEBUG_LOG =
|
|
2402
|
+
PAUSED_FILE = import_path5.default.join(import_os2.default.homedir(), ".node9", "PAUSED");
|
|
2403
|
+
TRUST_FILE = import_path5.default.join(import_os2.default.homedir(), ".node9", "trust.json");
|
|
2404
|
+
LOCAL_AUDIT_LOG = import_path5.default.join(import_os2.default.homedir(), ".node9", "audit.log");
|
|
2405
|
+
HOOK_DEBUG_LOG = import_path5.default.join(import_os2.default.homedir(), ".node9", "hook-debug.log");
|
|
2406
|
+
MAX_REGEX_LENGTH = 100;
|
|
2407
|
+
REGEX_CACHE_MAX = 500;
|
|
2408
|
+
regexCache = /* @__PURE__ */ new Map();
|
|
2288
2409
|
SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
2289
2410
|
DANGEROUS_WORDS = [
|
|
2290
2411
|
"mkfs",
|
|
@@ -2499,7 +2620,7 @@ var init_core = __esm({
|
|
|
2499
2620
|
cachedConfig = null;
|
|
2500
2621
|
DAEMON_PORT = 7391;
|
|
2501
2622
|
DAEMON_HOST = "127.0.0.1";
|
|
2502
|
-
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
2623
|
+
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path5.default.join(import_os2.default.tmpdir(), "node9-activity.sock");
|
|
2503
2624
|
}
|
|
2504
2625
|
});
|
|
2505
2626
|
|
|
@@ -3957,18 +4078,18 @@ var init_ui2 = __esm({
|
|
|
3957
4078
|
|
|
3958
4079
|
// src/daemon/index.ts
|
|
3959
4080
|
function atomicWriteSync2(filePath, data, options) {
|
|
3960
|
-
const dir =
|
|
3961
|
-
if (!
|
|
4081
|
+
const dir = import_path7.default.dirname(filePath);
|
|
4082
|
+
if (!import_fs5.default.existsSync(dir)) import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
3962
4083
|
const tmpPath = `${filePath}.${(0, import_crypto3.randomUUID)()}.tmp`;
|
|
3963
|
-
|
|
3964
|
-
|
|
4084
|
+
import_fs5.default.writeFileSync(tmpPath, data, options);
|
|
4085
|
+
import_fs5.default.renameSync(tmpPath, filePath);
|
|
3965
4086
|
}
|
|
3966
4087
|
function writeTrustEntry(toolName, durationMs) {
|
|
3967
4088
|
try {
|
|
3968
4089
|
let trust = { entries: [] };
|
|
3969
4090
|
try {
|
|
3970
|
-
if (
|
|
3971
|
-
trust = JSON.parse(
|
|
4091
|
+
if (import_fs5.default.existsSync(TRUST_FILE2))
|
|
4092
|
+
trust = JSON.parse(import_fs5.default.readFileSync(TRUST_FILE2, "utf-8"));
|
|
3972
4093
|
} catch {
|
|
3973
4094
|
}
|
|
3974
4095
|
trust.entries = trust.entries.filter((e) => e.tool !== toolName && e.expiry > Date.now());
|
|
@@ -3995,16 +4116,16 @@ function appendAuditLog(data) {
|
|
|
3995
4116
|
decision: data.decision,
|
|
3996
4117
|
source: "daemon"
|
|
3997
4118
|
};
|
|
3998
|
-
const dir =
|
|
3999
|
-
if (!
|
|
4000
|
-
|
|
4119
|
+
const dir = import_path7.default.dirname(AUDIT_LOG_FILE);
|
|
4120
|
+
if (!import_fs5.default.existsSync(dir)) import_fs5.default.mkdirSync(dir, { recursive: true });
|
|
4121
|
+
import_fs5.default.appendFileSync(AUDIT_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
4001
4122
|
} catch {
|
|
4002
4123
|
}
|
|
4003
4124
|
}
|
|
4004
4125
|
function getAuditHistory(limit = 20) {
|
|
4005
4126
|
try {
|
|
4006
|
-
if (!
|
|
4007
|
-
const lines =
|
|
4127
|
+
if (!import_fs5.default.existsSync(AUDIT_LOG_FILE)) return [];
|
|
4128
|
+
const lines = import_fs5.default.readFileSync(AUDIT_LOG_FILE, "utf-8").trim().split("\n");
|
|
4008
4129
|
if (lines.length === 1 && lines[0] === "") return [];
|
|
4009
4130
|
return lines.slice(-limit).map((l) => JSON.parse(l)).reverse();
|
|
4010
4131
|
} catch {
|
|
@@ -4013,7 +4134,7 @@ function getAuditHistory(limit = 20) {
|
|
|
4013
4134
|
}
|
|
4014
4135
|
function getOrgName() {
|
|
4015
4136
|
try {
|
|
4016
|
-
if (
|
|
4137
|
+
if (import_fs5.default.existsSync(CREDENTIALS_FILE)) {
|
|
4017
4138
|
return "Node9 Cloud";
|
|
4018
4139
|
}
|
|
4019
4140
|
} catch {
|
|
@@ -4021,13 +4142,13 @@ function getOrgName() {
|
|
|
4021
4142
|
return null;
|
|
4022
4143
|
}
|
|
4023
4144
|
function hasStoredSlackKey() {
|
|
4024
|
-
return
|
|
4145
|
+
return import_fs5.default.existsSync(CREDENTIALS_FILE);
|
|
4025
4146
|
}
|
|
4026
4147
|
function writeGlobalSetting(key, value) {
|
|
4027
4148
|
let config = {};
|
|
4028
4149
|
try {
|
|
4029
|
-
if (
|
|
4030
|
-
config = JSON.parse(
|
|
4150
|
+
if (import_fs5.default.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
4151
|
+
config = JSON.parse(import_fs5.default.readFileSync(GLOBAL_CONFIG_FILE, "utf-8"));
|
|
4031
4152
|
}
|
|
4032
4153
|
} catch {
|
|
4033
4154
|
}
|
|
@@ -4046,7 +4167,7 @@ function abandonPending() {
|
|
|
4046
4167
|
});
|
|
4047
4168
|
if (autoStarted) {
|
|
4048
4169
|
try {
|
|
4049
|
-
|
|
4170
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4050
4171
|
} catch {
|
|
4051
4172
|
}
|
|
4052
4173
|
setTimeout(() => {
|
|
@@ -4096,8 +4217,8 @@ function readBody(req) {
|
|
|
4096
4217
|
}
|
|
4097
4218
|
function readPersistentDecisions() {
|
|
4098
4219
|
try {
|
|
4099
|
-
if (
|
|
4100
|
-
return JSON.parse(
|
|
4220
|
+
if (import_fs5.default.existsSync(DECISIONS_FILE)) {
|
|
4221
|
+
return JSON.parse(import_fs5.default.readFileSync(DECISIONS_FILE, "utf-8"));
|
|
4101
4222
|
}
|
|
4102
4223
|
} catch {
|
|
4103
4224
|
}
|
|
@@ -4126,7 +4247,7 @@ function startDaemon() {
|
|
|
4126
4247
|
idleTimer = setTimeout(() => {
|
|
4127
4248
|
if (autoStarted) {
|
|
4128
4249
|
try {
|
|
4129
|
-
|
|
4250
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4130
4251
|
} catch {
|
|
4131
4252
|
}
|
|
4132
4253
|
}
|
|
@@ -4511,14 +4632,14 @@ data: ${JSON.stringify(item.data)}
|
|
|
4511
4632
|
server.on("error", (e) => {
|
|
4512
4633
|
if (e.code === "EADDRINUSE") {
|
|
4513
4634
|
try {
|
|
4514
|
-
if (
|
|
4515
|
-
const { pid } = JSON.parse(
|
|
4635
|
+
if (import_fs5.default.existsSync(DAEMON_PID_FILE)) {
|
|
4636
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4516
4637
|
process.kill(pid, 0);
|
|
4517
4638
|
return process.exit(0);
|
|
4518
4639
|
}
|
|
4519
4640
|
} catch {
|
|
4520
4641
|
try {
|
|
4521
|
-
|
|
4642
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4522
4643
|
} catch {
|
|
4523
4644
|
}
|
|
4524
4645
|
server.listen(DAEMON_PORT2, DAEMON_HOST2);
|
|
@@ -4569,7 +4690,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
4569
4690
|
console.log(import_chalk4.default.cyan("\u{1F6F0}\uFE0F Flight Recorder active \u2014 daemon will not idle-timeout"));
|
|
4570
4691
|
}
|
|
4571
4692
|
try {
|
|
4572
|
-
|
|
4693
|
+
import_fs5.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4573
4694
|
} catch {
|
|
4574
4695
|
}
|
|
4575
4696
|
const ACTIVITY_MAX_BYTES = 1024 * 1024;
|
|
@@ -4611,30 +4732,30 @@ data: ${JSON.stringify(item.data)}
|
|
|
4611
4732
|
unixServer.listen(ACTIVITY_SOCKET_PATH2);
|
|
4612
4733
|
process.on("exit", () => {
|
|
4613
4734
|
try {
|
|
4614
|
-
|
|
4735
|
+
import_fs5.default.unlinkSync(ACTIVITY_SOCKET_PATH2);
|
|
4615
4736
|
} catch {
|
|
4616
4737
|
}
|
|
4617
4738
|
});
|
|
4618
4739
|
}
|
|
4619
4740
|
function stopDaemon() {
|
|
4620
|
-
if (!
|
|
4741
|
+
if (!import_fs5.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk4.default.yellow("Not running."));
|
|
4621
4742
|
try {
|
|
4622
|
-
const { pid } = JSON.parse(
|
|
4743
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4623
4744
|
process.kill(pid, "SIGTERM");
|
|
4624
4745
|
console.log(import_chalk4.default.green("\u2705 Stopped."));
|
|
4625
4746
|
} catch {
|
|
4626
4747
|
console.log(import_chalk4.default.gray("Cleaned up stale PID file."));
|
|
4627
4748
|
} finally {
|
|
4628
4749
|
try {
|
|
4629
|
-
|
|
4750
|
+
import_fs5.default.unlinkSync(DAEMON_PID_FILE);
|
|
4630
4751
|
} catch {
|
|
4631
4752
|
}
|
|
4632
4753
|
}
|
|
4633
4754
|
}
|
|
4634
4755
|
function daemonStatus() {
|
|
4635
|
-
if (
|
|
4756
|
+
if (import_fs5.default.existsSync(DAEMON_PID_FILE)) {
|
|
4636
4757
|
try {
|
|
4637
|
-
const { pid } = JSON.parse(
|
|
4758
|
+
const { pid } = JSON.parse(import_fs5.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
|
|
4638
4759
|
process.kill(pid, 0);
|
|
4639
4760
|
console.log(import_chalk4.default.green("Node9 daemon: running"));
|
|
4640
4761
|
return;
|
|
@@ -4653,31 +4774,31 @@ function daemonStatus() {
|
|
|
4653
4774
|
console.log(import_chalk4.default.yellow("Node9 daemon: not running"));
|
|
4654
4775
|
}
|
|
4655
4776
|
}
|
|
4656
|
-
var import_http, import_net2,
|
|
4777
|
+
var import_http, import_net2, import_fs5, import_path7, import_os4, import_child_process3, import_crypto3, import_chalk4, ACTIVITY_SOCKET_PATH2, DAEMON_PORT2, DAEMON_HOST2, homeDir, DAEMON_PID_FILE, DECISIONS_FILE, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, TRUST_DURATIONS, SECRET_KEY_RE, AUTO_DENY_MS, autoStarted, pending, sseClients, abandonTimer, daemonServer, hadBrowserClient, ACTIVITY_RING_SIZE, activityRing;
|
|
4657
4778
|
var init_daemon = __esm({
|
|
4658
4779
|
"src/daemon/index.ts"() {
|
|
4659
4780
|
"use strict";
|
|
4660
4781
|
init_ui2();
|
|
4661
4782
|
import_http = __toESM(require("http"));
|
|
4662
4783
|
import_net2 = __toESM(require("net"));
|
|
4663
|
-
|
|
4664
|
-
|
|
4784
|
+
import_fs5 = __toESM(require("fs"));
|
|
4785
|
+
import_path7 = __toESM(require("path"));
|
|
4665
4786
|
import_os4 = __toESM(require("os"));
|
|
4666
4787
|
import_child_process3 = require("child_process");
|
|
4667
4788
|
import_crypto3 = require("crypto");
|
|
4668
4789
|
import_chalk4 = __toESM(require("chalk"));
|
|
4669
4790
|
init_core();
|
|
4670
4791
|
init_shields();
|
|
4671
|
-
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" :
|
|
4792
|
+
ACTIVITY_SOCKET_PATH2 = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path7.default.join(import_os4.default.tmpdir(), "node9-activity.sock");
|
|
4672
4793
|
DAEMON_PORT2 = 7391;
|
|
4673
4794
|
DAEMON_HOST2 = "127.0.0.1";
|
|
4674
4795
|
homeDir = import_os4.default.homedir();
|
|
4675
|
-
DAEMON_PID_FILE =
|
|
4676
|
-
DECISIONS_FILE =
|
|
4677
|
-
GLOBAL_CONFIG_FILE =
|
|
4678
|
-
CREDENTIALS_FILE =
|
|
4679
|
-
AUDIT_LOG_FILE =
|
|
4680
|
-
TRUST_FILE2 =
|
|
4796
|
+
DAEMON_PID_FILE = import_path7.default.join(homeDir, ".node9", "daemon.pid");
|
|
4797
|
+
DECISIONS_FILE = import_path7.default.join(homeDir, ".node9", "decisions.json");
|
|
4798
|
+
GLOBAL_CONFIG_FILE = import_path7.default.join(homeDir, ".node9", "config.json");
|
|
4799
|
+
CREDENTIALS_FILE = import_path7.default.join(homeDir, ".node9", "credentials.json");
|
|
4800
|
+
AUDIT_LOG_FILE = import_path7.default.join(homeDir, ".node9", "audit.log");
|
|
4801
|
+
TRUST_FILE2 = import_path7.default.join(homeDir, ".node9", "trust.json");
|
|
4681
4802
|
TRUST_DURATIONS = {
|
|
4682
4803
|
"30m": 30 * 6e4,
|
|
4683
4804
|
"1h": 60 * 6e4,
|
|
@@ -4738,9 +4859,9 @@ function renderPending(activity) {
|
|
|
4738
4859
|
}
|
|
4739
4860
|
async function ensureDaemon() {
|
|
4740
4861
|
let pidPort = null;
|
|
4741
|
-
if (
|
|
4862
|
+
if (import_fs7.default.existsSync(PID_FILE)) {
|
|
4742
4863
|
try {
|
|
4743
|
-
const { port } = JSON.parse(
|
|
4864
|
+
const { port } = JSON.parse(import_fs7.default.readFileSync(PID_FILE, "utf-8"));
|
|
4744
4865
|
pidPort = port;
|
|
4745
4866
|
} catch {
|
|
4746
4867
|
console.error(import_chalk5.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
@@ -4895,19 +5016,19 @@ async function startTail(options = {}) {
|
|
|
4895
5016
|
process.exit(1);
|
|
4896
5017
|
});
|
|
4897
5018
|
}
|
|
4898
|
-
var import_http2, import_chalk5,
|
|
5019
|
+
var import_http2, import_chalk5, import_fs7, import_os6, import_path9, import_readline, import_child_process5, PID_FILE, ICONS;
|
|
4899
5020
|
var init_tail = __esm({
|
|
4900
5021
|
"src/tui/tail.ts"() {
|
|
4901
5022
|
"use strict";
|
|
4902
5023
|
import_http2 = __toESM(require("http"));
|
|
4903
5024
|
import_chalk5 = __toESM(require("chalk"));
|
|
4904
|
-
|
|
5025
|
+
import_fs7 = __toESM(require("fs"));
|
|
4905
5026
|
import_os6 = __toESM(require("os"));
|
|
4906
|
-
|
|
5027
|
+
import_path9 = __toESM(require("path"));
|
|
4907
5028
|
import_readline = __toESM(require("readline"));
|
|
4908
5029
|
import_child_process5 = require("child_process");
|
|
4909
5030
|
init_daemon();
|
|
4910
|
-
PID_FILE =
|
|
5031
|
+
PID_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "daemon.pid");
|
|
4911
5032
|
ICONS = {
|
|
4912
5033
|
bash: "\u{1F4BB}",
|
|
4913
5034
|
shell: "\u{1F4BB}",
|
|
@@ -4933,8 +5054,8 @@ var import_commander = require("commander");
|
|
|
4933
5054
|
init_core();
|
|
4934
5055
|
|
|
4935
5056
|
// src/setup.ts
|
|
4936
|
-
var
|
|
4937
|
-
var
|
|
5057
|
+
var import_fs4 = __toESM(require("fs"));
|
|
5058
|
+
var import_path6 = __toESM(require("path"));
|
|
4938
5059
|
var import_os3 = __toESM(require("os"));
|
|
4939
5060
|
var import_chalk3 = __toESM(require("chalk"));
|
|
4940
5061
|
var import_prompts2 = require("@inquirer/prompts");
|
|
@@ -4952,17 +5073,17 @@ function fullPathCommand(subcommand) {
|
|
|
4952
5073
|
}
|
|
4953
5074
|
function readJson(filePath) {
|
|
4954
5075
|
try {
|
|
4955
|
-
if (
|
|
4956
|
-
return JSON.parse(
|
|
5076
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
5077
|
+
return JSON.parse(import_fs4.default.readFileSync(filePath, "utf-8"));
|
|
4957
5078
|
}
|
|
4958
5079
|
} catch {
|
|
4959
5080
|
}
|
|
4960
5081
|
return null;
|
|
4961
5082
|
}
|
|
4962
5083
|
function writeJson(filePath, data) {
|
|
4963
|
-
const dir =
|
|
4964
|
-
if (!
|
|
4965
|
-
|
|
5084
|
+
const dir = import_path6.default.dirname(filePath);
|
|
5085
|
+
if (!import_fs4.default.existsSync(dir)) import_fs4.default.mkdirSync(dir, { recursive: true });
|
|
5086
|
+
import_fs4.default.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
4966
5087
|
}
|
|
4967
5088
|
function isNode9Hook(cmd) {
|
|
4968
5089
|
if (!cmd) return false;
|
|
@@ -4970,8 +5091,8 @@ function isNode9Hook(cmd) {
|
|
|
4970
5091
|
}
|
|
4971
5092
|
function teardownClaude() {
|
|
4972
5093
|
const homeDir2 = import_os3.default.homedir();
|
|
4973
|
-
const hooksPath =
|
|
4974
|
-
const mcpPath =
|
|
5094
|
+
const hooksPath = import_path6.default.join(homeDir2, ".claude", "settings.json");
|
|
5095
|
+
const mcpPath = import_path6.default.join(homeDir2, ".claude.json");
|
|
4975
5096
|
let changed = false;
|
|
4976
5097
|
const settings = readJson(hooksPath);
|
|
4977
5098
|
if (settings?.hooks) {
|
|
@@ -5020,7 +5141,7 @@ function teardownClaude() {
|
|
|
5020
5141
|
}
|
|
5021
5142
|
function teardownGemini() {
|
|
5022
5143
|
const homeDir2 = import_os3.default.homedir();
|
|
5023
|
-
const settingsPath =
|
|
5144
|
+
const settingsPath = import_path6.default.join(homeDir2, ".gemini", "settings.json");
|
|
5024
5145
|
const settings = readJson(settingsPath);
|
|
5025
5146
|
if (!settings) {
|
|
5026
5147
|
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.gemini/settings.json not found \u2014 nothing to remove"));
|
|
@@ -5059,7 +5180,7 @@ function teardownGemini() {
|
|
|
5059
5180
|
}
|
|
5060
5181
|
function teardownCursor() {
|
|
5061
5182
|
const homeDir2 = import_os3.default.homedir();
|
|
5062
|
-
const mcpPath =
|
|
5183
|
+
const mcpPath = import_path6.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5063
5184
|
const mcpConfig = readJson(mcpPath);
|
|
5064
5185
|
if (!mcpConfig?.mcpServers) {
|
|
5065
5186
|
console.log(import_chalk3.default.blue(" \u2139\uFE0F ~/.cursor/mcp.json not found \u2014 nothing to remove"));
|
|
@@ -5086,8 +5207,8 @@ function teardownCursor() {
|
|
|
5086
5207
|
}
|
|
5087
5208
|
async function setupClaude() {
|
|
5088
5209
|
const homeDir2 = import_os3.default.homedir();
|
|
5089
|
-
const mcpPath =
|
|
5090
|
-
const hooksPath =
|
|
5210
|
+
const mcpPath = import_path6.default.join(homeDir2, ".claude.json");
|
|
5211
|
+
const hooksPath = import_path6.default.join(homeDir2, ".claude", "settings.json");
|
|
5091
5212
|
const claudeConfig = readJson(mcpPath) ?? {};
|
|
5092
5213
|
const settings = readJson(hooksPath) ?? {};
|
|
5093
5214
|
const servers = claudeConfig.mcpServers ?? {};
|
|
@@ -5162,7 +5283,7 @@ async function setupClaude() {
|
|
|
5162
5283
|
}
|
|
5163
5284
|
async function setupGemini() {
|
|
5164
5285
|
const homeDir2 = import_os3.default.homedir();
|
|
5165
|
-
const settingsPath =
|
|
5286
|
+
const settingsPath = import_path6.default.join(homeDir2, ".gemini", "settings.json");
|
|
5166
5287
|
const settings = readJson(settingsPath) ?? {};
|
|
5167
5288
|
const servers = settings.mcpServers ?? {};
|
|
5168
5289
|
let anythingChanged = false;
|
|
@@ -5245,7 +5366,7 @@ async function setupGemini() {
|
|
|
5245
5366
|
}
|
|
5246
5367
|
async function setupCursor() {
|
|
5247
5368
|
const homeDir2 = import_os3.default.homedir();
|
|
5248
|
-
const mcpPath =
|
|
5369
|
+
const mcpPath = import_path6.default.join(homeDir2, ".cursor", "mcp.json");
|
|
5249
5370
|
const mcpConfig = readJson(mcpPath) ?? {};
|
|
5250
5371
|
const servers = mcpConfig.mcpServers ?? {};
|
|
5251
5372
|
let anythingChanged = false;
|
|
@@ -5306,30 +5427,32 @@ var import_execa = require("execa");
|
|
|
5306
5427
|
var import_execa2 = require("execa");
|
|
5307
5428
|
var import_chalk6 = __toESM(require("chalk"));
|
|
5308
5429
|
var import_readline2 = __toESM(require("readline"));
|
|
5309
|
-
var
|
|
5310
|
-
var
|
|
5430
|
+
var import_fs8 = __toESM(require("fs"));
|
|
5431
|
+
var import_path10 = __toESM(require("path"));
|
|
5311
5432
|
var import_os7 = __toESM(require("os"));
|
|
5312
5433
|
|
|
5313
5434
|
// src/undo.ts
|
|
5314
5435
|
var import_child_process4 = require("child_process");
|
|
5315
|
-
var
|
|
5316
|
-
var
|
|
5436
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
5437
|
+
var import_fs6 = __toESM(require("fs"));
|
|
5438
|
+
var import_path8 = __toESM(require("path"));
|
|
5317
5439
|
var import_os5 = __toESM(require("os"));
|
|
5318
|
-
var SNAPSHOT_STACK_PATH =
|
|
5319
|
-
var UNDO_LATEST_PATH =
|
|
5440
|
+
var SNAPSHOT_STACK_PATH = import_path8.default.join(import_os5.default.homedir(), ".node9", "snapshots.json");
|
|
5441
|
+
var UNDO_LATEST_PATH = import_path8.default.join(import_os5.default.homedir(), ".node9", "undo_latest.txt");
|
|
5320
5442
|
var MAX_SNAPSHOTS = 10;
|
|
5443
|
+
var GIT_TIMEOUT = 15e3;
|
|
5321
5444
|
function readStack() {
|
|
5322
5445
|
try {
|
|
5323
|
-
if (
|
|
5324
|
-
return JSON.parse(
|
|
5446
|
+
if (import_fs6.default.existsSync(SNAPSHOT_STACK_PATH))
|
|
5447
|
+
return JSON.parse(import_fs6.default.readFileSync(SNAPSHOT_STACK_PATH, "utf-8"));
|
|
5325
5448
|
} catch {
|
|
5326
5449
|
}
|
|
5327
5450
|
return [];
|
|
5328
5451
|
}
|
|
5329
5452
|
function writeStack(stack) {
|
|
5330
|
-
const dir =
|
|
5331
|
-
if (!
|
|
5332
|
-
|
|
5453
|
+
const dir = import_path8.default.dirname(SNAPSHOT_STACK_PATH);
|
|
5454
|
+
if (!import_fs6.default.existsSync(dir)) import_fs6.default.mkdirSync(dir, { recursive: true });
|
|
5455
|
+
import_fs6.default.writeFileSync(SNAPSHOT_STACK_PATH, JSON.stringify(stack, null, 2));
|
|
5333
5456
|
}
|
|
5334
5457
|
function buildArgsSummary(tool, args) {
|
|
5335
5458
|
if (!args || typeof args !== "object") return "";
|
|
@@ -5342,54 +5465,177 @@ function buildArgsSummary(tool, args) {
|
|
|
5342
5465
|
if (typeof sql === "string") return sql.slice(0, 80);
|
|
5343
5466
|
return tool;
|
|
5344
5467
|
}
|
|
5345
|
-
|
|
5468
|
+
function normalizeCwdForHash(cwd) {
|
|
5469
|
+
let normalized;
|
|
5470
|
+
try {
|
|
5471
|
+
normalized = import_fs6.default.realpathSync(cwd);
|
|
5472
|
+
} catch {
|
|
5473
|
+
normalized = cwd;
|
|
5474
|
+
}
|
|
5475
|
+
normalized = normalized.replace(/\\/g, "/");
|
|
5476
|
+
if (process.platform === "win32") normalized = normalized.toLowerCase();
|
|
5477
|
+
return normalized;
|
|
5478
|
+
}
|
|
5479
|
+
function getShadowRepoDir(cwd) {
|
|
5480
|
+
const hash = import_crypto4.default.createHash("sha256").update(normalizeCwdForHash(cwd)).digest("hex").slice(0, 16);
|
|
5481
|
+
return import_path8.default.join(import_os5.default.homedir(), ".node9", "snapshots", hash);
|
|
5482
|
+
}
|
|
5483
|
+
function cleanOrphanedIndexFiles(shadowDir) {
|
|
5484
|
+
try {
|
|
5485
|
+
const cutoff = Date.now() - 6e4;
|
|
5486
|
+
for (const f of import_fs6.default.readdirSync(shadowDir)) {
|
|
5487
|
+
if (f.startsWith("index_")) {
|
|
5488
|
+
const fp = import_path8.default.join(shadowDir, f);
|
|
5489
|
+
try {
|
|
5490
|
+
if (import_fs6.default.statSync(fp).mtimeMs < cutoff) import_fs6.default.unlinkSync(fp);
|
|
5491
|
+
} catch {
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
} catch {
|
|
5496
|
+
}
|
|
5497
|
+
}
|
|
5498
|
+
function writeShadowExcludes(shadowDir, ignorePaths) {
|
|
5499
|
+
const hardcoded = [".git", ".node9"];
|
|
5500
|
+
const lines = [...hardcoded, ...ignorePaths].join("\n");
|
|
5501
|
+
try {
|
|
5502
|
+
import_fs6.default.writeFileSync(import_path8.default.join(shadowDir, "info", "exclude"), lines + "\n", "utf8");
|
|
5503
|
+
} catch {
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
function ensureShadowRepo(shadowDir, cwd) {
|
|
5507
|
+
cleanOrphanedIndexFiles(shadowDir);
|
|
5508
|
+
const normalizedCwd = normalizeCwdForHash(cwd);
|
|
5509
|
+
const shadowEnvBase = { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
5510
|
+
const check = (0, import_child_process4.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
5511
|
+
env: shadowEnvBase,
|
|
5512
|
+
timeout: 3e3
|
|
5513
|
+
});
|
|
5514
|
+
if (check.status === 0) {
|
|
5515
|
+
const ptPath = import_path8.default.join(shadowDir, "project-path.txt");
|
|
5516
|
+
try {
|
|
5517
|
+
const stored = import_fs6.default.readFileSync(ptPath, "utf8").trim();
|
|
5518
|
+
if (stored === normalizedCwd) return true;
|
|
5519
|
+
if (process.env.NODE9_DEBUG === "1")
|
|
5520
|
+
console.error(
|
|
5521
|
+
`[Node9] Shadow repo path mismatch: stored="${stored}" expected="${normalizedCwd}" \u2014 reinitializing`
|
|
5522
|
+
);
|
|
5523
|
+
import_fs6.default.rmSync(shadowDir, { recursive: true, force: true });
|
|
5524
|
+
} catch {
|
|
5525
|
+
try {
|
|
5526
|
+
import_fs6.default.writeFileSync(ptPath, normalizedCwd, "utf8");
|
|
5527
|
+
} catch {
|
|
5528
|
+
}
|
|
5529
|
+
return true;
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
try {
|
|
5533
|
+
import_fs6.default.mkdirSync(shadowDir, { recursive: true });
|
|
5534
|
+
} catch {
|
|
5535
|
+
}
|
|
5536
|
+
const init = (0, import_child_process4.spawnSync)("git", ["init", "--bare", shadowDir], { timeout: 5e3 });
|
|
5537
|
+
if (init.status !== 0) {
|
|
5538
|
+
if (process.env.NODE9_DEBUG === "1")
|
|
5539
|
+
console.error("[Node9] git init --bare failed:", init.stderr?.toString());
|
|
5540
|
+
return false;
|
|
5541
|
+
}
|
|
5542
|
+
const configFile = import_path8.default.join(shadowDir, "config");
|
|
5543
|
+
(0, import_child_process4.spawnSync)("git", ["config", "--file", configFile, "core.untrackedCache", "true"], {
|
|
5544
|
+
timeout: 3e3
|
|
5545
|
+
});
|
|
5546
|
+
(0, import_child_process4.spawnSync)("git", ["config", "--file", configFile, "core.fsmonitor", "true"], {
|
|
5547
|
+
timeout: 3e3
|
|
5548
|
+
});
|
|
5549
|
+
try {
|
|
5550
|
+
import_fs6.default.writeFileSync(import_path8.default.join(shadowDir, "project-path.txt"), normalizedCwd, "utf8");
|
|
5551
|
+
} catch {
|
|
5552
|
+
}
|
|
5553
|
+
return true;
|
|
5554
|
+
}
|
|
5555
|
+
function buildGitEnv(cwd) {
|
|
5556
|
+
const shadowDir = getShadowRepoDir(cwd);
|
|
5557
|
+
const check = (0, import_child_process4.spawnSync)("git", ["rev-parse", "--git-dir"], {
|
|
5558
|
+
env: { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd },
|
|
5559
|
+
timeout: 2e3
|
|
5560
|
+
});
|
|
5561
|
+
if (check.status === 0) {
|
|
5562
|
+
return { ...process.env, GIT_DIR: shadowDir, GIT_WORK_TREE: cwd };
|
|
5563
|
+
}
|
|
5564
|
+
return { ...process.env };
|
|
5565
|
+
}
|
|
5566
|
+
async function createShadowSnapshot(tool = "unknown", args = {}, ignorePaths = []) {
|
|
5567
|
+
let indexFile = null;
|
|
5346
5568
|
try {
|
|
5347
5569
|
const cwd = process.cwd();
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
const
|
|
5353
|
-
|
|
5354
|
-
|
|
5570
|
+
const shadowDir = getShadowRepoDir(cwd);
|
|
5571
|
+
if (!ensureShadowRepo(shadowDir, cwd)) return null;
|
|
5572
|
+
writeShadowExcludes(shadowDir, ignorePaths);
|
|
5573
|
+
indexFile = import_path8.default.join(shadowDir, `index_${process.pid}_${Date.now()}`);
|
|
5574
|
+
const shadowEnv = {
|
|
5575
|
+
...process.env,
|
|
5576
|
+
GIT_DIR: shadowDir,
|
|
5577
|
+
GIT_WORK_TREE: cwd,
|
|
5578
|
+
GIT_INDEX_FILE: indexFile
|
|
5579
|
+
};
|
|
5580
|
+
(0, import_child_process4.spawnSync)("git", ["add", "-A"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
5581
|
+
const treeRes = (0, import_child_process4.spawnSync)("git", ["write-tree"], { env: shadowEnv, timeout: GIT_TIMEOUT });
|
|
5582
|
+
const treeHash = treeRes.stdout?.toString().trim();
|
|
5355
5583
|
if (!treeHash || treeRes.status !== 0) return null;
|
|
5356
|
-
const commitRes = (0, import_child_process4.spawnSync)(
|
|
5357
|
-
"
|
|
5358
|
-
treeHash,
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
const commitHash = commitRes.stdout.toString().trim();
|
|
5584
|
+
const commitRes = (0, import_child_process4.spawnSync)(
|
|
5585
|
+
"git",
|
|
5586
|
+
["commit-tree", treeHash, "-m", `Node9 AI Snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`],
|
|
5587
|
+
{ env: shadowEnv, timeout: GIT_TIMEOUT }
|
|
5588
|
+
);
|
|
5589
|
+
const commitHash = commitRes.stdout?.toString().trim();
|
|
5363
5590
|
if (!commitHash || commitRes.status !== 0) return null;
|
|
5364
5591
|
const stack = readStack();
|
|
5365
|
-
|
|
5592
|
+
stack.push({
|
|
5366
5593
|
hash: commitHash,
|
|
5367
5594
|
tool,
|
|
5368
5595
|
argsSummary: buildArgsSummary(tool, args),
|
|
5369
5596
|
cwd,
|
|
5370
5597
|
timestamp: Date.now()
|
|
5371
|
-
};
|
|
5372
|
-
stack.
|
|
5598
|
+
});
|
|
5599
|
+
const shouldGc = stack.length % 5 === 0;
|
|
5373
5600
|
if (stack.length > MAX_SNAPSHOTS) stack.splice(0, stack.length - MAX_SNAPSHOTS);
|
|
5374
5601
|
writeStack(stack);
|
|
5375
|
-
|
|
5602
|
+
import_fs6.default.writeFileSync(UNDO_LATEST_PATH, commitHash);
|
|
5603
|
+
if (shouldGc) {
|
|
5604
|
+
(0, import_child_process4.spawn)("git", ["gc", "--auto"], { env: shadowEnv, detached: true, stdio: "ignore" }).unref();
|
|
5605
|
+
}
|
|
5376
5606
|
return commitHash;
|
|
5377
5607
|
} catch (err) {
|
|
5378
5608
|
if (process.env.NODE9_DEBUG === "1") console.error("[Node9 Undo Engine Error]:", err);
|
|
5609
|
+
return null;
|
|
5610
|
+
} finally {
|
|
5611
|
+
if (indexFile) {
|
|
5612
|
+
try {
|
|
5613
|
+
import_fs6.default.unlinkSync(indexFile);
|
|
5614
|
+
} catch {
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5379
5617
|
}
|
|
5380
|
-
return null;
|
|
5381
5618
|
}
|
|
5382
5619
|
function getSnapshotHistory() {
|
|
5383
5620
|
return readStack();
|
|
5384
5621
|
}
|
|
5385
5622
|
function computeUndoDiff(hash, cwd) {
|
|
5386
5623
|
try {
|
|
5387
|
-
const
|
|
5388
|
-
const
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5624
|
+
const env = buildGitEnv(cwd);
|
|
5625
|
+
const statRes = (0, import_child_process4.spawnSync)("git", ["diff", hash, "--stat", "--", "."], {
|
|
5626
|
+
cwd,
|
|
5627
|
+
env,
|
|
5628
|
+
timeout: GIT_TIMEOUT
|
|
5629
|
+
});
|
|
5630
|
+
const stat = statRes.stdout?.toString().trim();
|
|
5631
|
+
if (!stat || statRes.status !== 0) return null;
|
|
5632
|
+
const diffRes = (0, import_child_process4.spawnSync)("git", ["diff", hash, "--", "."], {
|
|
5633
|
+
cwd,
|
|
5634
|
+
env,
|
|
5635
|
+
timeout: GIT_TIMEOUT
|
|
5636
|
+
});
|
|
5637
|
+
const raw = diffRes.stdout?.toString();
|
|
5638
|
+
if (!raw || diffRes.status !== 0) return null;
|
|
5393
5639
|
const lines = raw.split("\n").filter(
|
|
5394
5640
|
(l) => !l.startsWith("diff --git") && !l.startsWith("index ") && !l.startsWith("Binary")
|
|
5395
5641
|
);
|
|
@@ -5401,18 +5647,41 @@ function computeUndoDiff(hash, cwd) {
|
|
|
5401
5647
|
function applyUndo(hash, cwd) {
|
|
5402
5648
|
try {
|
|
5403
5649
|
const dir = cwd ?? process.cwd();
|
|
5650
|
+
const env = buildGitEnv(dir);
|
|
5404
5651
|
const restore = (0, import_child_process4.spawnSync)("git", ["restore", "--source", hash, "--staged", "--worktree", "."], {
|
|
5405
|
-
cwd: dir
|
|
5652
|
+
cwd: dir,
|
|
5653
|
+
env,
|
|
5654
|
+
timeout: GIT_TIMEOUT
|
|
5406
5655
|
});
|
|
5407
5656
|
if (restore.status !== 0) return false;
|
|
5408
|
-
const lsTree = (0, import_child_process4.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5657
|
+
const lsTree = (0, import_child_process4.spawnSync)("git", ["ls-tree", "-r", "--name-only", hash], {
|
|
5658
|
+
cwd: dir,
|
|
5659
|
+
env,
|
|
5660
|
+
timeout: GIT_TIMEOUT
|
|
5661
|
+
});
|
|
5662
|
+
if (lsTree.status !== 0) {
|
|
5663
|
+
process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}
|
|
5664
|
+
`);
|
|
5665
|
+
return false;
|
|
5666
|
+
}
|
|
5667
|
+
const snapshotFiles = new Set(
|
|
5668
|
+
lsTree.stdout?.toString().trim().split("\n").filter(Boolean) ?? []
|
|
5669
|
+
);
|
|
5670
|
+
if (snapshotFiles.size === 0) {
|
|
5671
|
+
process.stderr.write(`[Node9] applyUndo: ls-tree returned no files for hash ${hash}
|
|
5672
|
+
`);
|
|
5673
|
+
return false;
|
|
5674
|
+
}
|
|
5675
|
+
const tracked = (0, import_child_process4.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
5676
|
+
const untracked = (0, import_child_process4.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
5677
|
+
cwd: dir,
|
|
5678
|
+
env,
|
|
5679
|
+
timeout: GIT_TIMEOUT
|
|
5680
|
+
}).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
|
|
5412
5681
|
for (const file of [...tracked, ...untracked]) {
|
|
5413
|
-
const fullPath =
|
|
5414
|
-
if (!snapshotFiles.has(file) &&
|
|
5415
|
-
|
|
5682
|
+
const fullPath = import_path8.default.join(dir, file);
|
|
5683
|
+
if (!snapshotFiles.has(file) && import_fs6.default.existsSync(fullPath)) {
|
|
5684
|
+
import_fs6.default.unlinkSync(fullPath);
|
|
5416
5685
|
}
|
|
5417
5686
|
}
|
|
5418
5687
|
return true;
|
|
@@ -5425,7 +5694,7 @@ function applyUndo(hash, cwd) {
|
|
|
5425
5694
|
init_shields();
|
|
5426
5695
|
var import_prompts3 = require("@inquirer/prompts");
|
|
5427
5696
|
var { version } = JSON.parse(
|
|
5428
|
-
|
|
5697
|
+
import_fs8.default.readFileSync(import_path10.default.join(__dirname, "../package.json"), "utf-8")
|
|
5429
5698
|
);
|
|
5430
5699
|
function parseDuration(str) {
|
|
5431
5700
|
const m = str.trim().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
|
|
@@ -5557,7 +5826,7 @@ async function runProxy(targetCommand) {
|
|
|
5557
5826
|
if (stdout) executable = stdout.trim();
|
|
5558
5827
|
} catch {
|
|
5559
5828
|
}
|
|
5560
|
-
console.
|
|
5829
|
+
console.error(import_chalk6.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
|
|
5561
5830
|
const child = (0, import_child_process6.spawn)(executable, args, {
|
|
5562
5831
|
stdio: ["pipe", "pipe", "inherit"],
|
|
5563
5832
|
// We control STDIN and STDOUT
|
|
@@ -5627,14 +5896,14 @@ async function runProxy(targetCommand) {
|
|
|
5627
5896
|
}
|
|
5628
5897
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
5629
5898
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
5630
|
-
const credPath =
|
|
5631
|
-
if (!
|
|
5632
|
-
|
|
5899
|
+
const credPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "credentials.json");
|
|
5900
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(credPath)))
|
|
5901
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(credPath), { recursive: true });
|
|
5633
5902
|
const profileName = options.profile || "default";
|
|
5634
5903
|
let existingCreds = {};
|
|
5635
5904
|
try {
|
|
5636
|
-
if (
|
|
5637
|
-
const raw = JSON.parse(
|
|
5905
|
+
if (import_fs8.default.existsSync(credPath)) {
|
|
5906
|
+
const raw = JSON.parse(import_fs8.default.readFileSync(credPath, "utf-8"));
|
|
5638
5907
|
if (raw.apiKey) {
|
|
5639
5908
|
existingCreds = {
|
|
5640
5909
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -5646,13 +5915,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
5646
5915
|
} catch {
|
|
5647
5916
|
}
|
|
5648
5917
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
5649
|
-
|
|
5918
|
+
import_fs8.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
5650
5919
|
if (profileName === "default") {
|
|
5651
|
-
const configPath =
|
|
5920
|
+
const configPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
5652
5921
|
let config = {};
|
|
5653
5922
|
try {
|
|
5654
|
-
if (
|
|
5655
|
-
config = JSON.parse(
|
|
5923
|
+
if (import_fs8.default.existsSync(configPath))
|
|
5924
|
+
config = JSON.parse(import_fs8.default.readFileSync(configPath, "utf-8"));
|
|
5656
5925
|
} catch {
|
|
5657
5926
|
}
|
|
5658
5927
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -5667,9 +5936,9 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
5667
5936
|
approvers.cloud = false;
|
|
5668
5937
|
}
|
|
5669
5938
|
s.approvers = approvers;
|
|
5670
|
-
if (!
|
|
5671
|
-
|
|
5672
|
-
|
|
5939
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(configPath)))
|
|
5940
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(configPath), { recursive: true });
|
|
5941
|
+
import_fs8.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
5673
5942
|
}
|
|
5674
5943
|
if (options.profile && profileName !== "default") {
|
|
5675
5944
|
console.log(import_chalk6.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
@@ -5755,15 +6024,15 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
5755
6024
|
}
|
|
5756
6025
|
}
|
|
5757
6026
|
if (options.purge) {
|
|
5758
|
-
const node9Dir =
|
|
5759
|
-
if (
|
|
6027
|
+
const node9Dir = import_path10.default.join(import_os7.default.homedir(), ".node9");
|
|
6028
|
+
if (import_fs8.default.existsSync(node9Dir)) {
|
|
5760
6029
|
const confirmed = await (0, import_prompts3.confirm)({
|
|
5761
6030
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
5762
6031
|
default: false
|
|
5763
6032
|
});
|
|
5764
6033
|
if (confirmed) {
|
|
5765
|
-
|
|
5766
|
-
if (
|
|
6034
|
+
import_fs8.default.rmSync(node9Dir, { recursive: true });
|
|
6035
|
+
if (import_fs8.default.existsSync(node9Dir)) {
|
|
5767
6036
|
console.error(
|
|
5768
6037
|
import_chalk6.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
5769
6038
|
);
|
|
@@ -5835,10 +6104,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5835
6104
|
);
|
|
5836
6105
|
}
|
|
5837
6106
|
section("Configuration");
|
|
5838
|
-
const globalConfigPath =
|
|
5839
|
-
if (
|
|
6107
|
+
const globalConfigPath = import_path10.default.join(homeDir2, ".node9", "config.json");
|
|
6108
|
+
if (import_fs8.default.existsSync(globalConfigPath)) {
|
|
5840
6109
|
try {
|
|
5841
|
-
JSON.parse(
|
|
6110
|
+
JSON.parse(import_fs8.default.readFileSync(globalConfigPath, "utf-8"));
|
|
5842
6111
|
pass("~/.node9/config.json found and valid");
|
|
5843
6112
|
} catch {
|
|
5844
6113
|
fail("~/.node9/config.json is invalid JSON", "Run: node9 init --force");
|
|
@@ -5846,17 +6115,17 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5846
6115
|
} else {
|
|
5847
6116
|
warn("~/.node9/config.json not found (using defaults)", "Run: node9 init");
|
|
5848
6117
|
}
|
|
5849
|
-
const projectConfigPath =
|
|
5850
|
-
if (
|
|
6118
|
+
const projectConfigPath = import_path10.default.join(process.cwd(), "node9.config.json");
|
|
6119
|
+
if (import_fs8.default.existsSync(projectConfigPath)) {
|
|
5851
6120
|
try {
|
|
5852
|
-
JSON.parse(
|
|
6121
|
+
JSON.parse(import_fs8.default.readFileSync(projectConfigPath, "utf-8"));
|
|
5853
6122
|
pass("node9.config.json found and valid (project)");
|
|
5854
6123
|
} catch {
|
|
5855
6124
|
fail("node9.config.json is invalid JSON", "Fix the JSON or delete it and run: node9 init");
|
|
5856
6125
|
}
|
|
5857
6126
|
}
|
|
5858
|
-
const credsPath =
|
|
5859
|
-
if (
|
|
6127
|
+
const credsPath = import_path10.default.join(homeDir2, ".node9", "credentials.json");
|
|
6128
|
+
if (import_fs8.default.existsSync(credsPath)) {
|
|
5860
6129
|
pass("Cloud credentials found (~/.node9/credentials.json)");
|
|
5861
6130
|
} else {
|
|
5862
6131
|
warn(
|
|
@@ -5865,10 +6134,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5865
6134
|
);
|
|
5866
6135
|
}
|
|
5867
6136
|
section("Agent Hooks");
|
|
5868
|
-
const claudeSettingsPath =
|
|
5869
|
-
if (
|
|
6137
|
+
const claudeSettingsPath = import_path10.default.join(homeDir2, ".claude", "settings.json");
|
|
6138
|
+
if (import_fs8.default.existsSync(claudeSettingsPath)) {
|
|
5870
6139
|
try {
|
|
5871
|
-
const cs = JSON.parse(
|
|
6140
|
+
const cs = JSON.parse(import_fs8.default.readFileSync(claudeSettingsPath, "utf-8"));
|
|
5872
6141
|
const hasHook = cs.hooks?.PreToolUse?.some(
|
|
5873
6142
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
5874
6143
|
);
|
|
@@ -5881,10 +6150,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5881
6150
|
} else {
|
|
5882
6151
|
warn("Claude Code \u2014 not configured", "Run: node9 setup claude");
|
|
5883
6152
|
}
|
|
5884
|
-
const geminiSettingsPath =
|
|
5885
|
-
if (
|
|
6153
|
+
const geminiSettingsPath = import_path10.default.join(homeDir2, ".gemini", "settings.json");
|
|
6154
|
+
if (import_fs8.default.existsSync(geminiSettingsPath)) {
|
|
5886
6155
|
try {
|
|
5887
|
-
const gs = JSON.parse(
|
|
6156
|
+
const gs = JSON.parse(import_fs8.default.readFileSync(geminiSettingsPath, "utf-8"));
|
|
5888
6157
|
const hasHook = gs.hooks?.BeforeTool?.some(
|
|
5889
6158
|
(m) => m.hooks.some((h) => h.command?.includes("node9") || h.command?.includes("cli.js"))
|
|
5890
6159
|
);
|
|
@@ -5897,10 +6166,10 @@ program.command("doctor").description("Check that Node9 is installed and configu
|
|
|
5897
6166
|
} else {
|
|
5898
6167
|
warn("Gemini CLI \u2014 not configured", "Run: node9 setup gemini (skip if not using Gemini)");
|
|
5899
6168
|
}
|
|
5900
|
-
const cursorHooksPath =
|
|
5901
|
-
if (
|
|
6169
|
+
const cursorHooksPath = import_path10.default.join(homeDir2, ".cursor", "hooks.json");
|
|
6170
|
+
if (import_fs8.default.existsSync(cursorHooksPath)) {
|
|
5902
6171
|
try {
|
|
5903
|
-
const cur = JSON.parse(
|
|
6172
|
+
const cur = JSON.parse(import_fs8.default.readFileSync(cursorHooksPath, "utf-8"));
|
|
5904
6173
|
const hasHook = cur.hooks?.preToolUse?.some(
|
|
5905
6174
|
(h) => h.command?.includes("node9") || h.command?.includes("cli.js")
|
|
5906
6175
|
);
|
|
@@ -6002,8 +6271,8 @@ program.command("explain").description(
|
|
|
6002
6271
|
console.log("");
|
|
6003
6272
|
});
|
|
6004
6273
|
program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
|
|
6005
|
-
const configPath =
|
|
6006
|
-
if (
|
|
6274
|
+
const configPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
6275
|
+
if (import_fs8.default.existsSync(configPath) && !options.force) {
|
|
6007
6276
|
console.log(import_chalk6.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
|
|
6008
6277
|
console.log(import_chalk6.default.gray(` Run with --force to overwrite.`));
|
|
6009
6278
|
return;
|
|
@@ -6017,9 +6286,9 @@ program.command("init").description("Create ~/.node9/config.json with default po
|
|
|
6017
6286
|
mode: safeMode
|
|
6018
6287
|
}
|
|
6019
6288
|
};
|
|
6020
|
-
const dir =
|
|
6021
|
-
if (!
|
|
6022
|
-
|
|
6289
|
+
const dir = import_path10.default.dirname(configPath);
|
|
6290
|
+
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
6291
|
+
import_fs8.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
|
|
6023
6292
|
console.log(import_chalk6.default.green(`\u2705 Global config created: ${configPath}`));
|
|
6024
6293
|
console.log(import_chalk6.default.cyan(` Mode set to: ${safeMode}`));
|
|
6025
6294
|
console.log(
|
|
@@ -6037,14 +6306,14 @@ function formatRelativeTime(timestamp) {
|
|
|
6037
6306
|
return new Date(timestamp).toLocaleDateString();
|
|
6038
6307
|
}
|
|
6039
6308
|
program.command("audit").description("View local execution audit log").option("--tail <n>", "Number of entries to show", "20").option("--tool <pattern>", "Filter by tool name (substring match)").option("--deny", "Show only denied actions").option("--json", "Output raw JSON").action((options) => {
|
|
6040
|
-
const logPath =
|
|
6041
|
-
if (!
|
|
6309
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "audit.log");
|
|
6310
|
+
if (!import_fs8.default.existsSync(logPath)) {
|
|
6042
6311
|
console.log(
|
|
6043
6312
|
import_chalk6.default.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
6044
6313
|
);
|
|
6045
6314
|
return;
|
|
6046
6315
|
}
|
|
6047
|
-
const raw =
|
|
6316
|
+
const raw = import_fs8.default.readFileSync(logPath, "utf-8");
|
|
6048
6317
|
const lines = raw.split("\n").filter((l) => l.trim() !== "");
|
|
6049
6318
|
let entries = lines.flatMap((line) => {
|
|
6050
6319
|
try {
|
|
@@ -6127,13 +6396,13 @@ program.command("status").description("Show current Node9 mode, policy source, a
|
|
|
6127
6396
|
console.log("");
|
|
6128
6397
|
const modeLabel = settings.mode === "audit" ? import_chalk6.default.blue("audit") : settings.mode === "strict" ? import_chalk6.default.red("strict") : import_chalk6.default.white("standard");
|
|
6129
6398
|
console.log(` Mode: ${modeLabel}`);
|
|
6130
|
-
const projectConfig =
|
|
6131
|
-
const globalConfig =
|
|
6399
|
+
const projectConfig = import_path10.default.join(process.cwd(), "node9.config.json");
|
|
6400
|
+
const globalConfig = import_path10.default.join(import_os7.default.homedir(), ".node9", "config.json");
|
|
6132
6401
|
console.log(
|
|
6133
|
-
` Local: ${
|
|
6402
|
+
` Local: ${import_fs8.default.existsSync(projectConfig) ? import_chalk6.default.green("Active (node9.config.json)") : import_chalk6.default.gray("Not present")}`
|
|
6134
6403
|
);
|
|
6135
6404
|
console.log(
|
|
6136
|
-
` Global: ${
|
|
6405
|
+
` Global: ${import_fs8.default.existsSync(globalConfig) ? import_chalk6.default.green("Active (~/.node9/config.json)") : import_chalk6.default.gray("Not present")}`
|
|
6137
6406
|
);
|
|
6138
6407
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
6139
6408
|
console.log(
|
|
@@ -6217,9 +6486,9 @@ program.command("check").description("Hook handler \u2014 evaluates a tool call
|
|
|
6217
6486
|
} catch (err) {
|
|
6218
6487
|
const tempConfig = getConfig();
|
|
6219
6488
|
if (process.env.NODE9_DEBUG === "1" || tempConfig.settings.enableHookLogDebug) {
|
|
6220
|
-
const logPath =
|
|
6489
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6221
6490
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6222
|
-
|
|
6491
|
+
import_fs8.default.appendFileSync(
|
|
6223
6492
|
logPath,
|
|
6224
6493
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] JSON_PARSE_ERROR: ${errMsg}
|
|
6225
6494
|
RAW: ${raw}
|
|
@@ -6228,19 +6497,12 @@ RAW: ${raw}
|
|
|
6228
6497
|
}
|
|
6229
6498
|
process.exit(0);
|
|
6230
6499
|
}
|
|
6231
|
-
|
|
6232
|
-
try {
|
|
6233
|
-
process.chdir(payload.cwd);
|
|
6234
|
-
_resetConfigCache();
|
|
6235
|
-
} catch {
|
|
6236
|
-
}
|
|
6237
|
-
}
|
|
6238
|
-
const config = getConfig();
|
|
6500
|
+
const config = getConfig(payload.cwd || void 0);
|
|
6239
6501
|
if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
|
|
6240
|
-
const logPath =
|
|
6241
|
-
if (!
|
|
6242
|
-
|
|
6243
|
-
|
|
6502
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6503
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(logPath)))
|
|
6504
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(logPath), { recursive: true });
|
|
6505
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
6244
6506
|
`);
|
|
6245
6507
|
}
|
|
6246
6508
|
const toolName = sanitize(payload.tool_name ?? payload.name ?? "");
|
|
@@ -6284,7 +6546,7 @@ RAW: ${raw}
|
|
|
6284
6546
|
}
|
|
6285
6547
|
const meta = { agent, mcpServer };
|
|
6286
6548
|
if (shouldSnapshot(toolName, toolInput, config)) {
|
|
6287
|
-
await createShadowSnapshot(toolName, toolInput);
|
|
6549
|
+
await createShadowSnapshot(toolName, toolInput, config.policy.snapshot.ignorePaths);
|
|
6288
6550
|
}
|
|
6289
6551
|
const result = await authorizeHeadless(toolName, toolInput, false, meta);
|
|
6290
6552
|
if (result.approved) {
|
|
@@ -6317,9 +6579,9 @@ RAW: ${raw}
|
|
|
6317
6579
|
});
|
|
6318
6580
|
} catch (err) {
|
|
6319
6581
|
if (process.env.NODE9_DEBUG === "1") {
|
|
6320
|
-
const logPath =
|
|
6582
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6321
6583
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6322
|
-
|
|
6584
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ERROR: ${errMsg}
|
|
6323
6585
|
`);
|
|
6324
6586
|
}
|
|
6325
6587
|
process.exit(0);
|
|
@@ -6364,15 +6626,25 @@ program.command("log").description("PostToolUse hook \u2014 records executed too
|
|
|
6364
6626
|
decision: "allowed",
|
|
6365
6627
|
source: "post-hook"
|
|
6366
6628
|
};
|
|
6367
|
-
const logPath =
|
|
6368
|
-
if (!
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
const
|
|
6629
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "audit.log");
|
|
6630
|
+
if (!import_fs8.default.existsSync(import_path10.default.dirname(logPath)))
|
|
6631
|
+
import_fs8.default.mkdirSync(import_path10.default.dirname(logPath), { recursive: true });
|
|
6632
|
+
import_fs8.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
6633
|
+
const safeCwd = typeof payload.cwd === "string" && import_path10.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
|
|
6634
|
+
const config = getConfig(safeCwd);
|
|
6372
6635
|
if (shouldSnapshot(tool, {}, config)) {
|
|
6373
|
-
await createShadowSnapshot();
|
|
6636
|
+
await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
|
|
6637
|
+
}
|
|
6638
|
+
} catch (err) {
|
|
6639
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6640
|
+
process.stderr.write(`[Node9] audit log error: ${msg}
|
|
6641
|
+
`);
|
|
6642
|
+
const debugPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6643
|
+
try {
|
|
6644
|
+
import_fs8.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
|
|
6645
|
+
`);
|
|
6646
|
+
} catch {
|
|
6374
6647
|
}
|
|
6375
|
-
} catch {
|
|
6376
6648
|
}
|
|
6377
6649
|
process.exit(0);
|
|
6378
6650
|
};
|
|
@@ -6643,9 +6915,9 @@ process.on("unhandledRejection", (reason) => {
|
|
|
6643
6915
|
const isCheckHook = process.argv[2] === "check";
|
|
6644
6916
|
if (isCheckHook) {
|
|
6645
6917
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
6646
|
-
const logPath =
|
|
6918
|
+
const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
|
|
6647
6919
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
6648
|
-
|
|
6920
|
+
import_fs8.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
6649
6921
|
`);
|
|
6650
6922
|
}
|
|
6651
6923
|
process.exit(0);
|