@node9/proxy 2.8.5 → 2.9.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/dist/cli.js +3766 -1865
- package/dist/cli.mjs +3702 -1801
- package/dist/dashboard.mjs +854 -165
- package/dist/index.js +968 -113
- package/dist/index.mjs +964 -109
- package/dist/scan-ink.mjs +213 -99
- package/package.json +1 -1
package/dist/dashboard.mjs
CHANGED
|
@@ -89,10 +89,47 @@ var init_types = __esm({
|
|
|
89
89
|
|
|
90
90
|
// packages/policy-engine/dist/index.mjs
|
|
91
91
|
import safeRegex from "safe-regex2";
|
|
92
|
+
import { createHash } from "crypto";
|
|
92
93
|
import mvdanSh from "mvdan-sh";
|
|
93
94
|
import pm from "picomatch";
|
|
94
95
|
import safeRegex2 from "safe-regex2";
|
|
95
96
|
import safeRegex3 from "safe-regex2";
|
|
97
|
+
function validateBase58Check(s) {
|
|
98
|
+
if (!s) return null;
|
|
99
|
+
let n = 0n;
|
|
100
|
+
for (const c of s) {
|
|
101
|
+
const v = B58_INDEX[c];
|
|
102
|
+
if (v === void 0) return null;
|
|
103
|
+
n = n * 58n + BigInt(v);
|
|
104
|
+
}
|
|
105
|
+
let hex = n.toString(16);
|
|
106
|
+
if (hex.length % 2) hex = "0" + hex;
|
|
107
|
+
let zeros = 0;
|
|
108
|
+
for (const c of s) {
|
|
109
|
+
if (c !== "1") break;
|
|
110
|
+
zeros++;
|
|
111
|
+
}
|
|
112
|
+
const bytes = Buffer.concat([
|
|
113
|
+
Buffer.alloc(zeros),
|
|
114
|
+
n === 0n ? Buffer.alloc(0) : Buffer.from(hex, "hex")
|
|
115
|
+
]);
|
|
116
|
+
if (bytes.length < 5) return null;
|
|
117
|
+
const body = bytes.subarray(0, bytes.length - 4);
|
|
118
|
+
const check = bytes.subarray(bytes.length - 4);
|
|
119
|
+
const h = createHash("sha256").update(createHash("sha256").update(body).digest()).digest();
|
|
120
|
+
return h.subarray(0, 4).equals(check) ? body : null;
|
|
121
|
+
}
|
|
122
|
+
function validateWif(s) {
|
|
123
|
+
const p = validateBase58Check(s);
|
|
124
|
+
if (!p || p[0] !== 128) return false;
|
|
125
|
+
return p.length === 33 || p.length === 34 && p[33] === 1;
|
|
126
|
+
}
|
|
127
|
+
function validateXprv(s) {
|
|
128
|
+
const p = validateBase58Check(s);
|
|
129
|
+
if (!p || p.length !== 78) return false;
|
|
130
|
+
const version = p.readUInt32BE(0);
|
|
131
|
+
return XPRV_VERSIONS.has(version) && p[45] === 0;
|
|
132
|
+
}
|
|
96
133
|
function isAssignmentContext(text) {
|
|
97
134
|
return ASSIGNMENT_CONTEXT_RE.test(text);
|
|
98
135
|
}
|
|
@@ -133,6 +170,33 @@ function maskSecret(raw, pattern) {
|
|
|
133
170
|
const stars = "*".repeat(Math.min(secret.length - 8, 12));
|
|
134
171
|
return `${prefix}${stars}${suffix}`;
|
|
135
172
|
}
|
|
173
|
+
function suppressed(pattern, raw) {
|
|
174
|
+
if (pattern.validate) {
|
|
175
|
+
let ok;
|
|
176
|
+
try {
|
|
177
|
+
ok = pattern.validate(raw);
|
|
178
|
+
} catch {
|
|
179
|
+
ok = true;
|
|
180
|
+
}
|
|
181
|
+
return !ok;
|
|
182
|
+
}
|
|
183
|
+
if (DLP_STOPWORDS.some((sw) => raw.toLowerCase().includes(sw))) return true;
|
|
184
|
+
if (pattern.minEntropy !== void 0 && shannonEntropy(raw) < pattern.minEntropy) return true;
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
function firstAcceptedMatch(pattern, text) {
|
|
188
|
+
const flags = pattern.regex.flags.includes("g") ? pattern.regex.flags : pattern.regex.flags + "g";
|
|
189
|
+
const re = new RegExp(pattern.regex.source, flags);
|
|
190
|
+
let m;
|
|
191
|
+
while ((m = re.exec(text)) !== null) {
|
|
192
|
+
if (m[0].length === 0) {
|
|
193
|
+
re.lastIndex = m.index + 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!suppressed(pattern, m[0])) return m[0];
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
136
200
|
function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
137
201
|
if (depth > MAX_DEPTH || args === null || args === void 0) return null;
|
|
138
202
|
if (Array.isArray(args)) {
|
|
@@ -157,18 +221,16 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
|
157
221
|
if (pattern.keywords && !pattern.keywords.some((kw) => textLower.includes(kw.toLowerCase()))) {
|
|
158
222
|
continue;
|
|
159
223
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
171
|
-
}
|
|
224
|
+
const raw = firstAcceptedMatch(pattern, text);
|
|
225
|
+
if (raw === null) continue;
|
|
226
|
+
const severity = pattern.contextBoost && assignmentCtx ? "block" : pattern.severity;
|
|
227
|
+
return {
|
|
228
|
+
patternName: pattern.name,
|
|
229
|
+
fieldPath,
|
|
230
|
+
// Mask the ACCEPTED token, not the first regex hit in the field.
|
|
231
|
+
redactedSample: maskSecret(raw, pattern.regex),
|
|
232
|
+
severity
|
|
233
|
+
};
|
|
172
234
|
}
|
|
173
235
|
if (text.length < MAX_JSON_PARSE_BYTES) {
|
|
174
236
|
const trimmed = text.trim();
|
|
@@ -228,13 +290,13 @@ function matchesPattern(text, patterns) {
|
|
|
228
290
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
229
291
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
230
292
|
}
|
|
231
|
-
function getNestedValue(obj,
|
|
293
|
+
function getNestedValue(obj, path14) {
|
|
232
294
|
if (!obj || typeof obj !== "object") return null;
|
|
233
|
-
const
|
|
234
|
-
for (const seg of
|
|
295
|
+
const segments2 = path14.split(".");
|
|
296
|
+
for (const seg of segments2) {
|
|
235
297
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
236
298
|
}
|
|
237
|
-
return
|
|
299
|
+
return segments2.reduce((prev, curr) => prev?.[curr], obj);
|
|
238
300
|
}
|
|
239
301
|
function evaluateSmartConditions(args, rule) {
|
|
240
302
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
@@ -659,6 +721,162 @@ function analyzeFsOperationImpl(command) {
|
|
|
659
721
|
return null;
|
|
660
722
|
}
|
|
661
723
|
}
|
|
724
|
+
function parseComponent(s) {
|
|
725
|
+
if (!s) return null;
|
|
726
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(s)) return parseInt(s.slice(2), 16);
|
|
727
|
+
if (s === "0") return 0;
|
|
728
|
+
if (/^0[0-7]+$/.test(s)) return parseInt(s.slice(1), 8);
|
|
729
|
+
if (/^[1-9][0-9]*$/.test(s)) return Number(s);
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
function parseIpv4(input) {
|
|
733
|
+
let s = input;
|
|
734
|
+
if (s.endsWith(".")) s = s.slice(0, -1);
|
|
735
|
+
if (!s) return null;
|
|
736
|
+
const parts = s.split(".");
|
|
737
|
+
if (parts.length > 4) return null;
|
|
738
|
+
const vals = [];
|
|
739
|
+
for (const p of parts) {
|
|
740
|
+
const v = parseComponent(p);
|
|
741
|
+
if (v === null || !Number.isFinite(v) || v < 0) return null;
|
|
742
|
+
vals.push(v);
|
|
743
|
+
}
|
|
744
|
+
const n = vals.length;
|
|
745
|
+
for (let i = 0; i < n - 1; i++) if (vals[i] > 255) return null;
|
|
746
|
+
const last = vals[n - 1];
|
|
747
|
+
const remainingBytes = 4 - (n - 1);
|
|
748
|
+
const limit = Math.pow(256, remainingBytes);
|
|
749
|
+
if (last >= limit) return null;
|
|
750
|
+
let value = last;
|
|
751
|
+
for (let i = 0; i < n - 1; i++) value += vals[i] * Math.pow(256, 3 - i);
|
|
752
|
+
if (value > 4294967295) return null;
|
|
753
|
+
return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
754
|
+
}
|
|
755
|
+
function expandIpv6(input) {
|
|
756
|
+
const s = input.toLowerCase();
|
|
757
|
+
if (!/^[0-9a-f:.]+$/.test(s)) return null;
|
|
758
|
+
if ((s.match(/::/g) ?? []).length > 1) return null;
|
|
759
|
+
let head = s;
|
|
760
|
+
let tailV4 = null;
|
|
761
|
+
const lastColon = s.lastIndexOf(":");
|
|
762
|
+
const afterLast = s.slice(lastColon + 1);
|
|
763
|
+
if (afterLast.includes(".")) {
|
|
764
|
+
const dotted = parseIpv4(afterLast);
|
|
765
|
+
if (!dotted) return null;
|
|
766
|
+
const o = dotted.split(".").map(Number);
|
|
767
|
+
tailV4 = [o[0] << 8 | o[1], o[2] << 8 | o[3]];
|
|
768
|
+
head = s.slice(0, lastColon + 1) + "0";
|
|
769
|
+
}
|
|
770
|
+
const [lhs, rhs] = head.includes("::") ? head.split("::") : [head, null];
|
|
771
|
+
const toGroups = (part) => {
|
|
772
|
+
if (!part) return [];
|
|
773
|
+
const out = [];
|
|
774
|
+
for (const g of part.split(":")) {
|
|
775
|
+
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
776
|
+
out.push(parseInt(g, 16));
|
|
777
|
+
}
|
|
778
|
+
return out;
|
|
779
|
+
};
|
|
780
|
+
const left = toGroups(lhs);
|
|
781
|
+
if (left === null) return null;
|
|
782
|
+
let right = [];
|
|
783
|
+
if (rhs !== null) {
|
|
784
|
+
const r = toGroups(rhs);
|
|
785
|
+
if (r === null) return null;
|
|
786
|
+
right = r;
|
|
787
|
+
}
|
|
788
|
+
if (tailV4) {
|
|
789
|
+
if (rhs !== null) right = right.slice(0, -1).concat(tailV4);
|
|
790
|
+
else left.splice(left.length - 1, 1, ...tailV4);
|
|
791
|
+
}
|
|
792
|
+
const groups = rhs === null ? left : left.concat(new Array(8 - left.length - right.length).fill(0), right);
|
|
793
|
+
if (rhs === null && groups.length !== 8) return null;
|
|
794
|
+
if (rhs !== null && left.length + right.length > 8) return null;
|
|
795
|
+
if (groups.length !== 8) return null;
|
|
796
|
+
return groups;
|
|
797
|
+
}
|
|
798
|
+
function compressIpv6(g) {
|
|
799
|
+
let bestStart = -1;
|
|
800
|
+
let bestLen = 0;
|
|
801
|
+
let i = 0;
|
|
802
|
+
while (i < 8) {
|
|
803
|
+
if (g[i] !== 0) {
|
|
804
|
+
i++;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
let j = i;
|
|
808
|
+
while (j < 8 && g[j] === 0) j++;
|
|
809
|
+
if (j - i > bestLen) {
|
|
810
|
+
bestLen = j - i;
|
|
811
|
+
bestStart = i;
|
|
812
|
+
}
|
|
813
|
+
i = j;
|
|
814
|
+
}
|
|
815
|
+
const hex = g.map((x) => x.toString(16));
|
|
816
|
+
if (bestLen < 2) return hex.join(":");
|
|
817
|
+
return hex.slice(0, bestStart).join(":") + "::" + hex.slice(bestStart + bestLen).join(":");
|
|
818
|
+
}
|
|
819
|
+
function normalizeIpLiteral(host) {
|
|
820
|
+
try {
|
|
821
|
+
if (typeof host !== "string") return null;
|
|
822
|
+
let s = host.trim();
|
|
823
|
+
if (!s || s.length > SSRF_MAX_HOST) return null;
|
|
824
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
825
|
+
const zone = s.indexOf("%");
|
|
826
|
+
if (zone >= 0) s = s.slice(0, zone);
|
|
827
|
+
if (!s) return null;
|
|
828
|
+
if (s.includes(":")) {
|
|
829
|
+
const g = expandIpv6(s);
|
|
830
|
+
if (!g) return null;
|
|
831
|
+
const mapped = g.slice(0, 5).every((x) => x === 0) && g[5] === 65535;
|
|
832
|
+
if (mapped) {
|
|
833
|
+
return [g[6] >> 8 & 255, g[6] & 255, g[7] >> 8 & 255, g[7] & 255].join(".");
|
|
834
|
+
}
|
|
835
|
+
return compressIpv6(g);
|
|
836
|
+
}
|
|
837
|
+
return parseIpv4(s);
|
|
838
|
+
} catch {
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function classifySsrf(host) {
|
|
843
|
+
try {
|
|
844
|
+
if (typeof host !== "string" || !host) return null;
|
|
845
|
+
const lower = host.trim().toLowerCase().replace(/\.$/, "");
|
|
846
|
+
const ip = normalizeIpLiteral(host);
|
|
847
|
+
if (ip === null) {
|
|
848
|
+
return METADATA_HOSTNAMES.has(lower) ? { tier: "metadata", overridable: false, kind: "hostname" } : null;
|
|
849
|
+
}
|
|
850
|
+
const hit = (tier, overridable) => ({
|
|
851
|
+
tier,
|
|
852
|
+
overridable,
|
|
853
|
+
kind: "address",
|
|
854
|
+
normalized: ip
|
|
855
|
+
});
|
|
856
|
+
if (METADATA_ADDRESSES.has(ip)) return hit("metadata", false);
|
|
857
|
+
const o = v4Octets(ip);
|
|
858
|
+
if (o) {
|
|
859
|
+
if (o[0] === 0 && o[1] === 0 && o[2] === 0 && o[3] === 0) return hit("unspecified", false);
|
|
860
|
+
if (o[0] === 169 && o[1] === 254) return hit("link-local", false);
|
|
861
|
+
if (o[0] >= 224 && o[0] <= 239) return hit("multicast", false);
|
|
862
|
+
if (o[0] === 100 && o[1] >= 64 && o[1] <= 127) return hit("cgnat", true);
|
|
863
|
+
if (o[0] === 127) return hit("private", true);
|
|
864
|
+
if (o[0] === 10) return hit("private", true);
|
|
865
|
+
if (o[0] === 192 && o[1] === 168) return hit("private", true);
|
|
866
|
+
if (o[0] === 172 && o[1] >= 16 && o[1] <= 31) return hit("private", true);
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
const g = expandIpv6(ip);
|
|
870
|
+
if (!g) return null;
|
|
871
|
+
if (g.every((x) => x === 0)) return hit("unspecified", false);
|
|
872
|
+
if ((g[0] & 65472) === 65152) return hit("link-local", false);
|
|
873
|
+
if ((g[0] & 65280) === 65280) return hit("multicast", false);
|
|
874
|
+
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return hit("private", true);
|
|
875
|
+
return null;
|
|
876
|
+
} catch {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
662
880
|
function isShieldVerdict(v) {
|
|
663
881
|
return v === "allow" || v === "review" || v === "block";
|
|
664
882
|
}
|
|
@@ -712,10 +930,123 @@ function assertBuiltinShieldRegexesAreSafe() {
|
|
|
712
930
|
}
|
|
713
931
|
}
|
|
714
932
|
}
|
|
715
|
-
|
|
933
|
+
function percentDecodeOnce(s) {
|
|
934
|
+
try {
|
|
935
|
+
return decodeURIComponent(s);
|
|
936
|
+
} catch {
|
|
937
|
+
return s.replace(/%([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
function segments(s, alphabet) {
|
|
941
|
+
const out = /* @__PURE__ */ new Set();
|
|
942
|
+
if (alphabet.test(s)) out.add(s);
|
|
943
|
+
for (const seg of s.split(/[?&\s"'<>]+/)) {
|
|
944
|
+
if (seg.length >= MIN_SEGMENT && alphabet.test(seg)) out.add(seg);
|
|
945
|
+
for (const part of seg.split("=")) {
|
|
946
|
+
if (part.length >= MIN_SEGMENT && alphabet.test(part)) out.add(part);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return [...out];
|
|
950
|
+
}
|
|
951
|
+
function lowestOffset(cands, needles, stripped) {
|
|
952
|
+
let best = null;
|
|
953
|
+
for (const c of cands) {
|
|
954
|
+
for (const n of needles) {
|
|
955
|
+
const off = c.indexOf(stripped ? n.stripped : n.raw);
|
|
956
|
+
if (off >= 0 && (best === null || off < best.off)) best = { off, v: n.v };
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return best?.v ?? null;
|
|
960
|
+
}
|
|
961
|
+
function prepare(values) {
|
|
962
|
+
const out = [];
|
|
963
|
+
for (const v of values) {
|
|
964
|
+
if (typeof v.value !== "string" || v.value.length < CANARY_MIN_LENGTH) {
|
|
965
|
+
console.error(
|
|
966
|
+
`[node9 engine] canary ${v.id}: value shorter than ${CANARY_MIN_LENGTH}, skipped`
|
|
967
|
+
);
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
out.push({ v, raw: v.value, stripped: stripSeparators(v.value) });
|
|
971
|
+
}
|
|
972
|
+
return out;
|
|
973
|
+
}
|
|
974
|
+
function matchPrepared(text, needles) {
|
|
975
|
+
if (!text || needles.length === 0) return null;
|
|
976
|
+
const t = text.length > MAX_TEXT ? text.slice(0, MAX_TEXT) : text;
|
|
977
|
+
const raw = lowestOffset([t], needles, false);
|
|
978
|
+
if (raw) return { v: raw, view: "raw" };
|
|
979
|
+
for (const { view, decoder, stripped } of VIEWS) {
|
|
980
|
+
let cands;
|
|
981
|
+
try {
|
|
982
|
+
cands = CANARY_DECODERS[decoder](t);
|
|
983
|
+
} catch (e) {
|
|
984
|
+
console.error(
|
|
985
|
+
`[node9 engine] canary view ${view} failed, skipped:`,
|
|
986
|
+
e instanceof Error ? e.message : String(e)
|
|
987
|
+
);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
const hit = lowestOffset(cands, needles, stripped);
|
|
991
|
+
if (hit) return { v: hit, view };
|
|
992
|
+
}
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
function matchCanaryArgs(args, values) {
|
|
996
|
+
if (values.length === 0) return null;
|
|
997
|
+
const needles = prepare(values);
|
|
998
|
+
if (needles.length === 0) return null;
|
|
999
|
+
const walk = (v, depth, fieldPath) => {
|
|
1000
|
+
if (depth > MAX_DEPTH2) return null;
|
|
1001
|
+
if (typeof v === "string") {
|
|
1002
|
+
const hit = matchPrepared(v, needles);
|
|
1003
|
+
if (hit) return { id: hit.v.id, view: hit.view, fieldPath, retired: Boolean(hit.v.retired) };
|
|
1004
|
+
if (v.length < MAX_JSON_PARSE) {
|
|
1005
|
+
const trimmed = v.trim();
|
|
1006
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
1007
|
+
try {
|
|
1008
|
+
return walk(JSON.parse(v), depth + 1, fieldPath);
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
if (typeof v === "number" && Number.isFinite(v)) return walk(String(v), depth, fieldPath);
|
|
1016
|
+
if (Array.isArray(v)) {
|
|
1017
|
+
for (let i = 0; i < v.length; i++) {
|
|
1018
|
+
const h = walk(v[i], depth + 1, `${fieldPath}[${i}]`);
|
|
1019
|
+
if (h) return h;
|
|
1020
|
+
}
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
if (v && typeof v === "object") {
|
|
1024
|
+
for (const [k, child] of Object.entries(v)) {
|
|
1025
|
+
const h = walk(child, depth + 1, fieldPath ? `${fieldPath}.${k}` : k);
|
|
1026
|
+
if (h) return h;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return null;
|
|
1030
|
+
};
|
|
1031
|
+
try {
|
|
1032
|
+
return walk(args, 0, "");
|
|
1033
|
+
} catch (e) {
|
|
1034
|
+
console.error(
|
|
1035
|
+
"[node9 engine] canary args walk failed:",
|
|
1036
|
+
e instanceof Error ? e.message : String(e)
|
|
1037
|
+
);
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
var B58, B58_INDEX, XPRV_VERSIONS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
|
|
716
1042
|
var init_dist = __esm({
|
|
717
1043
|
"packages/policy-engine/dist/index.mjs"() {
|
|
718
1044
|
"use strict";
|
|
1045
|
+
B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1046
|
+
B58_INDEX = Object.fromEntries(
|
|
1047
|
+
[...B58].map((c, i) => [c, i])
|
|
1048
|
+
);
|
|
1049
|
+
XPRV_VERSIONS = /* @__PURE__ */ new Set([76066276, 77428856, 78791436]);
|
|
719
1050
|
ASSIGNMENT_CONTEXT_RE = /\b(?:password|passwd|secret|token|api[_-]?key|auth(?:_key|_token)?|credential|private[_-]?key|access[_-]?key|client[_-]?secret)\s*[=:]\s*/i;
|
|
720
1051
|
DLP_STOPWORDS = [
|
|
721
1052
|
"example",
|
|
@@ -894,6 +1225,27 @@ var init_dist = __esm({
|
|
|
894
1225
|
severity: "block",
|
|
895
1226
|
keywords: ["sg."]
|
|
896
1227
|
},
|
|
1228
|
+
// ── Cryptocurrency private keys (base58check-validated) ───────────────────
|
|
1229
|
+
// Both are anchored with \b on each side: unanchored, `[KL][base58]{51}`
|
|
1230
|
+
// matches INSIDE any longer base58 blob (an xprv, a Solana keypair, a
|
|
1231
|
+
// Monero address). Lookbehind fails safe-regex2; \b is the house style
|
|
1232
|
+
// (see the card regexes). Mainnet only, matching validateWif / validateXprv;
|
|
1233
|
+
// testnet (WIF 0xEF, tprv) is deferred. Cost was measured: the WIF regex
|
|
1234
|
+
// runs on every string (first keyword-less pattern) at 0.024 ms per 100 KB
|
|
1235
|
+
// of prose, so no prefilter is warranted.
|
|
1236
|
+
{
|
|
1237
|
+
name: "Bitcoin WIF Private Key",
|
|
1238
|
+
regex: /\b(?:5[1-9A-HJ-NP-Za-km-z]{50}|[KL][1-9A-HJ-NP-Za-km-z]{51})\b/,
|
|
1239
|
+
severity: "block",
|
|
1240
|
+
validate: validateWif
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
name: "Extended Private Key",
|
|
1244
|
+
regex: /\b[xyz]prv[1-9A-HJ-NP-Za-km-z]{107}\b/,
|
|
1245
|
+
severity: "block",
|
|
1246
|
+
keywords: ["xprv", "yprv", "zprv"],
|
|
1247
|
+
validate: validateXprv
|
|
1248
|
+
},
|
|
897
1249
|
// ── Private keys (PEM) ────────────────────────────────────────────────────
|
|
898
1250
|
{
|
|
899
1251
|
name: "Private Key (PEM)",
|
|
@@ -1399,6 +1751,22 @@ var init_dist = __esm({
|
|
|
1399
1751
|
deriveRedirOp("cat <<X\nX"),
|
|
1400
1752
|
deriveRedirOp("cat <<-X\nX")
|
|
1401
1753
|
]);
|
|
1754
|
+
SSRF_MAX_HOST = 253;
|
|
1755
|
+
METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
1756
|
+
"169.254.169.254",
|
|
1757
|
+
// AWS / Azure / DigitalOcean / OpenStack IMDS
|
|
1758
|
+
"169.254.170.2",
|
|
1759
|
+
// AWS ECS task role
|
|
1760
|
+
"168.63.129.16",
|
|
1761
|
+
// Azure WireServer
|
|
1762
|
+
"fd00:ec2::254"
|
|
1763
|
+
// AWS IMDS over IPv6 (inside fc00::/7, which is NOT a tier)
|
|
1764
|
+
]);
|
|
1765
|
+
METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
1766
|
+
v4Octets = (a) => {
|
|
1767
|
+
const p = a.split(".");
|
|
1768
|
+
return p.length === 4 ? p.map(Number) : null;
|
|
1769
|
+
};
|
|
1402
1770
|
aws_default = {
|
|
1403
1771
|
name: "aws",
|
|
1404
1772
|
description: "Protects AWS infrastructure from destructive AI operations",
|
|
@@ -2150,6 +2518,60 @@ var init_dist = __esm({
|
|
|
2150
2518
|
[redis_default.name]: redis_default
|
|
2151
2519
|
};
|
|
2152
2520
|
assertBuiltinShieldRegexesAreSafe();
|
|
2521
|
+
CANARY_MIN_LENGTH = 16;
|
|
2522
|
+
MAX_TEXT = 1e5;
|
|
2523
|
+
MAX_DEPTH2 = 6;
|
|
2524
|
+
MAX_JSON_PARSE = 1e4;
|
|
2525
|
+
URL_DEPTH = 4;
|
|
2526
|
+
B64_DEPTH = 3;
|
|
2527
|
+
MIN_SEGMENT = 16;
|
|
2528
|
+
SEPARATORS = /[./\\?&= \t\n\r:;,\-_@%+#]/g;
|
|
2529
|
+
stripSeparators = (s) => s.replace(SEPARATORS, "");
|
|
2530
|
+
looksText = (s) => s.length > 0 && !/[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(s);
|
|
2531
|
+
CANARY_DECODERS = {
|
|
2532
|
+
url: (s) => {
|
|
2533
|
+
const out = [];
|
|
2534
|
+
let cur = s;
|
|
2535
|
+
for (let i = 0; i < URL_DEPTH; i++) {
|
|
2536
|
+
const d = percentDecodeOnce(cur);
|
|
2537
|
+
if (d === cur) break;
|
|
2538
|
+
out.push(d);
|
|
2539
|
+
cur = d;
|
|
2540
|
+
}
|
|
2541
|
+
return out;
|
|
2542
|
+
},
|
|
2543
|
+
base64: (s) => {
|
|
2544
|
+
const out = [];
|
|
2545
|
+
let frontier = segments(s, /^[A-Za-z0-9+/\-_=]+$/);
|
|
2546
|
+
for (let depth = 0; depth < B64_DEPTH && frontier.length; depth++) {
|
|
2547
|
+
const next = [];
|
|
2548
|
+
for (const c of frontier) {
|
|
2549
|
+
const d = Buffer.from(c, "base64").toString("utf8");
|
|
2550
|
+
if (!looksText(d) || d.length < CANARY_MIN_LENGTH) continue;
|
|
2551
|
+
out.push(d);
|
|
2552
|
+
next.push(...segments(d, /^[A-Za-z0-9+/\-_=]+$/));
|
|
2553
|
+
}
|
|
2554
|
+
frontier = next;
|
|
2555
|
+
}
|
|
2556
|
+
return out;
|
|
2557
|
+
},
|
|
2558
|
+
hex: (s) => {
|
|
2559
|
+
const out = [];
|
|
2560
|
+
for (const c of segments(s, /^[0-9A-Fa-f]+$/)) {
|
|
2561
|
+
if (c.length % 2 !== 0 || c.length < CANARY_MIN_LENGTH * 2) continue;
|
|
2562
|
+
const d = Buffer.from(c, "hex").toString("utf8");
|
|
2563
|
+
if (looksText(d)) out.push(d);
|
|
2564
|
+
}
|
|
2565
|
+
return out;
|
|
2566
|
+
},
|
|
2567
|
+
separators: (s) => [stripSeparators(s)]
|
|
2568
|
+
};
|
|
2569
|
+
VIEWS = [
|
|
2570
|
+
{ view: "url-decoded", decoder: "url", stripped: false },
|
|
2571
|
+
{ view: "base64-decoded", decoder: "base64", stripped: false },
|
|
2572
|
+
{ view: "hex-decoded", decoder: "hex", stripped: false },
|
|
2573
|
+
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
2574
|
+
];
|
|
2153
2575
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
2154
2576
|
}
|
|
2155
2577
|
});
|
|
@@ -2332,8 +2754,8 @@ function sanitizeConfig(raw) {
|
|
|
2332
2754
|
}
|
|
2333
2755
|
}
|
|
2334
2756
|
const lines = result.error.issues.map((issue) => {
|
|
2335
|
-
const
|
|
2336
|
-
return ` \u2022 ${
|
|
2757
|
+
const path14 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
2758
|
+
return ` \u2022 ${path14}: ${issue.message}`;
|
|
2337
2759
|
});
|
|
2338
2760
|
return {
|
|
2339
2761
|
sanitized,
|
|
@@ -2478,7 +2900,11 @@ var init_config_schema = __esm({
|
|
|
2478
2900
|
mode: z.enum(["off", "review", "block"]).optional(),
|
|
2479
2901
|
allow: z.array(z.string()).optional(),
|
|
2480
2902
|
deny: z.array(z.string()).optional(),
|
|
2481
|
-
allowPrivate: z.boolean().optional()
|
|
2903
|
+
allowPrivate: z.boolean().optional(),
|
|
2904
|
+
// SSRF floor. `ssrfAllow` exempts OVERRIDABLE tiers only; a tier-1
|
|
2905
|
+
// entry is dropped with a warning at load, never silently honoured.
|
|
2906
|
+
ssrfAllow: z.array(z.string()).optional(),
|
|
2907
|
+
ssrfStrict: z.boolean().optional()
|
|
2482
2908
|
}).optional(),
|
|
2483
2909
|
loopDetection: z.object({
|
|
2484
2910
|
enabled: z.boolean().optional(),
|
|
@@ -2636,6 +3062,12 @@ function applyManagedEgress(local, managed, locked, localModeUserSet = true) {
|
|
|
2636
3062
|
if (Array.isArray(managed.deny) && managed.deny.length > 0) {
|
|
2637
3063
|
next.deny = [.../* @__PURE__ */ new Set([...local.deny ?? [], ...managed.deny])];
|
|
2638
3064
|
}
|
|
3065
|
+
if (typeof managed.ssrfStrict === "boolean") {
|
|
3066
|
+
next.ssrfStrict = managed.ssrfStrict;
|
|
3067
|
+
}
|
|
3068
|
+
if (Array.isArray(managed.ssrfAllow)) {
|
|
3069
|
+
next.ssrfAllow = [...managed.ssrfAllow];
|
|
3070
|
+
}
|
|
2639
3071
|
if (typeof managed.allowPrivate === "boolean") {
|
|
2640
3072
|
next.allowPrivate = locked.includes("egressAllowPrivate") ? managed.allowPrivate : (local.allowPrivate ?? true) && managed.allowPrivate;
|
|
2641
3073
|
}
|
|
@@ -2723,9 +3155,9 @@ function slug(s) {
|
|
|
2723
3155
|
}
|
|
2724
3156
|
function pathToRegexFragment(rawPath) {
|
|
2725
3157
|
const tail = rawPath.trim().replace(/^~[\\/]?/, "").replace(/^\$\{?HOME\}?[\\/]?/, "").replace(/^\/(?:home|Users)\/[^\\/]+[\\/]?/, "").replace(/^[A-Za-z]:[\\/]Users[\\/][^\\/]+[\\/]?/, "").replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
|
|
2726
|
-
const
|
|
2727
|
-
if (
|
|
2728
|
-
return `(^|${B})${
|
|
3158
|
+
const segments2 = tail.split(/[\\/]+/).filter(Boolean).map(escapeRegex);
|
|
3159
|
+
if (segments2.length === 0) return "";
|
|
3160
|
+
return `(^|${B})${segments2.join(SEP)}(${B}|$)`;
|
|
2729
3161
|
}
|
|
2730
3162
|
function pathRules(rawPath, verdict, reason) {
|
|
2731
3163
|
const value = pathToRegexFragment(rawPath);
|
|
@@ -2789,6 +3221,20 @@ var init_trusted_hosts = __esm({
|
|
|
2789
3221
|
import fs4 from "fs";
|
|
2790
3222
|
import path4 from "path";
|
|
2791
3223
|
import os4 from "os";
|
|
3224
|
+
function sanitizeSsrfAllow(entries, source) {
|
|
3225
|
+
const kept = [];
|
|
3226
|
+
for (const entry of entries) {
|
|
3227
|
+
const m = classifySsrf(entry);
|
|
3228
|
+
if (m && !m.overridable) {
|
|
3229
|
+
process.emitWarning(
|
|
3230
|
+
`[node9] ${source} ssrfAllow entry "${entry}" is a protected address (${m.tier}) and cannot be exempted; ignoring it.`
|
|
3231
|
+
);
|
|
3232
|
+
continue;
|
|
3233
|
+
}
|
|
3234
|
+
kept.push(entry);
|
|
3235
|
+
}
|
|
3236
|
+
return kept;
|
|
3237
|
+
}
|
|
2792
3238
|
function getCredentials() {
|
|
2793
3239
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
2794
3240
|
if (process.env.NODE9_API_KEY) {
|
|
@@ -2895,7 +3341,8 @@ function getConfig(cwd) {
|
|
|
2895
3341
|
egress: {
|
|
2896
3342
|
...DEFAULT_CONFIG.policy.egress,
|
|
2897
3343
|
allow: [...DEFAULT_CONFIG.policy.egress.allow],
|
|
2898
|
-
deny: [...DEFAULT_CONFIG.policy.egress.deny]
|
|
3344
|
+
deny: [...DEFAULT_CONFIG.policy.egress.deny],
|
|
3345
|
+
ssrfAllow: [...DEFAULT_CONFIG.policy.egress.ssrfAllow ?? []]
|
|
2899
3346
|
},
|
|
2900
3347
|
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
|
|
2901
3348
|
injectionScan: {
|
|
@@ -2921,6 +3368,7 @@ function getConfig(cwd) {
|
|
|
2921
3368
|
};
|
|
2922
3369
|
const pr2Creds = getCredentials();
|
|
2923
3370
|
const keyed = !!pr2Creds?.apiKey && pr2Creds.localOnly !== true;
|
|
3371
|
+
let ssrfStrictSource = "default";
|
|
2924
3372
|
const applyLayer = (source, isProject = false, isCloud = false) => {
|
|
2925
3373
|
if (!source) return;
|
|
2926
3374
|
const s = source.settings || {};
|
|
@@ -3003,6 +3451,15 @@ function getConfig(cwd) {
|
|
|
3003
3451
|
if (Array.isArray(e.deny)) mergedPolicy.egress.deny.push(...e.deny);
|
|
3004
3452
|
if (e.allowPrivate !== void 0 && !(isProject && e.allowPrivate === true))
|
|
3005
3453
|
mergedPolicy.egress.allowPrivate = e.allowPrivate;
|
|
3454
|
+
if (!isProject) {
|
|
3455
|
+
if (Array.isArray(e.ssrfAllow)) {
|
|
3456
|
+
mergedPolicy.egress.ssrfAllow = sanitizeSsrfAllow(e.ssrfAllow, "egress.");
|
|
3457
|
+
}
|
|
3458
|
+
if (e.ssrfStrict !== void 0) {
|
|
3459
|
+
mergedPolicy.egress.ssrfStrict = e.ssrfStrict;
|
|
3460
|
+
ssrfStrictSource = "local";
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3006
3463
|
}
|
|
3007
3464
|
if (p.loopDetection) {
|
|
3008
3465
|
const ld = p.loopDetection;
|
|
@@ -3109,6 +3566,13 @@ function getConfig(cwd) {
|
|
|
3109
3566
|
if (deny) mergedPolicy.egress.deny = deny;
|
|
3110
3567
|
if (typeof e.allowPrivate === "boolean")
|
|
3111
3568
|
mergedPolicy.egress.allowPrivate = e.allowPrivate;
|
|
3569
|
+
if (typeof e.ssrfStrict === "boolean") {
|
|
3570
|
+
mergedPolicy.egress.ssrfStrict = e.ssrfStrict;
|
|
3571
|
+
ssrfStrictSource = "workspace";
|
|
3572
|
+
}
|
|
3573
|
+
const ssrfAllow = hosts(e.ssrfAllow);
|
|
3574
|
+
if (ssrfAllow)
|
|
3575
|
+
mergedPolicy.egress.ssrfAllow = sanitizeSsrfAllow(ssrfAllow, "managed egress.");
|
|
3112
3576
|
} else {
|
|
3113
3577
|
mergedPolicy.egress = applyManagedEgress(
|
|
3114
3578
|
mergedPolicy.egress,
|
|
@@ -3117,7 +3581,16 @@ function getConfig(cwd) {
|
|
|
3117
3581
|
mode: typeof mc.egress.mode === "string" ? mc.egress.mode : void 0,
|
|
3118
3582
|
allow: hosts(mc.egress.allow),
|
|
3119
3583
|
deny: hosts(mc.egress.deny),
|
|
3120
|
-
allowPrivate: typeof mc.egress.allowPrivate === "boolean" ? mc.egress.allowPrivate : void 0
|
|
3584
|
+
allowPrivate: typeof mc.egress.allowPrivate === "boolean" ? mc.egress.allowPrivate : void 0,
|
|
3585
|
+
ssrfStrict: (() => {
|
|
3586
|
+
if (typeof mc.egress.ssrfStrict !== "boolean") return void 0;
|
|
3587
|
+
ssrfStrictSource = "workspace";
|
|
3588
|
+
return mc.egress.ssrfStrict;
|
|
3589
|
+
})(),
|
|
3590
|
+
ssrfAllow: (() => {
|
|
3591
|
+
const list = hosts(mc.egress.ssrfAllow);
|
|
3592
|
+
return list ? sanitizeSsrfAllow(list, "managed egress.") : void 0;
|
|
3593
|
+
})()
|
|
3121
3594
|
},
|
|
3122
3595
|
locked,
|
|
3123
3596
|
egressModeUserSet
|
|
@@ -3212,13 +3685,13 @@ function getConfig(cwd) {
|
|
|
3212
3685
|
}
|
|
3213
3686
|
if (Array.isArray(mc.jailPaths)) {
|
|
3214
3687
|
for (const jp of mc.jailPaths) {
|
|
3215
|
-
const
|
|
3216
|
-
if (!
|
|
3688
|
+
const path14 = typeof jp?.path === "string" ? jp.path.trim() : "";
|
|
3689
|
+
if (!path14) continue;
|
|
3217
3690
|
const verdict = jp?.verdict === "review" ? "review" : "block";
|
|
3218
|
-
for (const r of pathRules(
|
|
3691
|
+
for (const r of pathRules(path14, verdict, "org-managed jail")) {
|
|
3219
3692
|
mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
|
|
3220
3693
|
}
|
|
3221
|
-
mergedPolicy.managedJailPaths.push({ path:
|
|
3694
|
+
mergedPolicy.managedJailPaths.push({ path: path14, verdict });
|
|
3222
3695
|
}
|
|
3223
3696
|
}
|
|
3224
3697
|
if (Array.isArray(mc.trustedHosts)) {
|
|
@@ -3347,6 +3820,7 @@ function getConfig(cwd) {
|
|
|
3347
3820
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
3348
3821
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
3349
3822
|
mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
|
|
3823
|
+
const resolvedSsrfStrictSource = keyed && ssrfStrictSource === "local" ? "default" : ssrfStrictSource;
|
|
3350
3824
|
const result = {
|
|
3351
3825
|
settings: mergedSettings,
|
|
3352
3826
|
policy: mergedPolicy,
|
|
@@ -3354,7 +3828,10 @@ function getConfig(cwd) {
|
|
|
3354
3828
|
// PR-2 — the one truth introspection reads: which of the two working
|
|
3355
3829
|
// modes this machine is in. 'workspace' = keyed, policy from the cloud;
|
|
3356
3830
|
// 'local' = the local stack (incl. --local / named-profile keys).
|
|
3357
|
-
policySource: keyed ? "workspace" : "local"
|
|
3831
|
+
policySource: keyed ? "workspace" : "local",
|
|
3832
|
+
// A keyed machine drops the local policy layers wholesale, so a 'local'
|
|
3833
|
+
// provenance recorded before the fork cannot survive into the result.
|
|
3834
|
+
ssrfStrictSource: resolvedSsrfStrictSource
|
|
3358
3835
|
};
|
|
3359
3836
|
if (!cwd) cachedConfig = result;
|
|
3360
3837
|
return result;
|
|
@@ -3422,6 +3899,7 @@ var init_config = __esm({
|
|
|
3422
3899
|
init_build();
|
|
3423
3900
|
init_trusted_hosts();
|
|
3424
3901
|
init_dist();
|
|
3902
|
+
init_dist();
|
|
3425
3903
|
DANGEROUS_WORDS = [
|
|
3426
3904
|
"mkfs",
|
|
3427
3905
|
// formats/wipes a filesystem partition
|
|
@@ -3431,6 +3909,7 @@ var init_config = __esm({
|
|
|
3431
3909
|
DEFAULT_CONFIG = {
|
|
3432
3910
|
version: "1.0",
|
|
3433
3911
|
policySource: "local",
|
|
3912
|
+
ssrfStrictSource: "default",
|
|
3434
3913
|
settings: {
|
|
3435
3914
|
mode: "standard",
|
|
3436
3915
|
autoStartDaemon: true,
|
|
@@ -3614,7 +4093,17 @@ var init_config = __esm({
|
|
|
3614
4093
|
}
|
|
3615
4094
|
],
|
|
3616
4095
|
dlp: { enabled: true, scanIgnoredTools: true, pii: "off" },
|
|
3617
|
-
egress: {
|
|
4096
|
+
egress: {
|
|
4097
|
+
enabled: false,
|
|
4098
|
+
mode: "review",
|
|
4099
|
+
allow: [],
|
|
4100
|
+
deny: [],
|
|
4101
|
+
allowPrivate: true,
|
|
4102
|
+
// The SSRF floor is always on and needs no default; these two only
|
|
4103
|
+
// widen or narrow it. See doc/roadmap/active/ssrf-floor-design.md.
|
|
4104
|
+
ssrfAllow: [],
|
|
4105
|
+
ssrfStrict: false
|
|
4106
|
+
},
|
|
3618
4107
|
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
|
|
3619
4108
|
injectionScan: { enabled: false, minConfidence: "medium", allow: [] },
|
|
3620
4109
|
skillPinning: { enabled: false, mode: "warn", roots: [] },
|
|
@@ -4050,12 +4539,74 @@ var init_decision = __esm({
|
|
|
4050
4539
|
}
|
|
4051
4540
|
});
|
|
4052
4541
|
|
|
4542
|
+
// src/shields/jail.ts
|
|
4543
|
+
var init_jail = __esm({
|
|
4544
|
+
"src/shields/jail.ts"() {
|
|
4545
|
+
"use strict";
|
|
4546
|
+
init_build();
|
|
4547
|
+
init_dist();
|
|
4548
|
+
init_shields();
|
|
4549
|
+
}
|
|
4550
|
+
});
|
|
4551
|
+
|
|
4552
|
+
// src/canary/registry.ts
|
|
4553
|
+
import fs7 from "fs";
|
|
4554
|
+
import os7 from "os";
|
|
4555
|
+
import path8 from "path";
|
|
4556
|
+
function canaryStorePath() {
|
|
4557
|
+
return path8.join(os7.homedir(), ".node9", "canaries.json");
|
|
4558
|
+
}
|
|
4559
|
+
function isRecord(x) {
|
|
4560
|
+
if (!x || typeof x !== "object") return false;
|
|
4561
|
+
const r = x;
|
|
4562
|
+
return typeof r.id === "string" && typeof r.value === "string" && typeof r.valueHash === "string";
|
|
4563
|
+
}
|
|
4564
|
+
function loadCanaries(opts) {
|
|
4565
|
+
const p = canaryStorePath();
|
|
4566
|
+
let raw;
|
|
4567
|
+
try {
|
|
4568
|
+
raw = fs7.readFileSync(p, "utf-8");
|
|
4569
|
+
} catch (e) {
|
|
4570
|
+
if (e.code === "ENOENT") return [];
|
|
4571
|
+
throw e;
|
|
4572
|
+
}
|
|
4573
|
+
let parsed;
|
|
4574
|
+
try {
|
|
4575
|
+
parsed = JSON.parse(raw);
|
|
4576
|
+
} catch (e) {
|
|
4577
|
+
throw new Error(
|
|
4578
|
+
`[node9] ${p} is not valid JSON; refusing to touch it (${e instanceof Error ? e.message : String(e)})`
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
const recs = parsed?.records;
|
|
4582
|
+
if (!Array.isArray(recs)) return [];
|
|
4583
|
+
const out = recs.filter(isRecord);
|
|
4584
|
+
return opts?.includeRetired === false ? out.filter((r) => !r.retiredAt) : out;
|
|
4585
|
+
}
|
|
4586
|
+
function canaryCtxValues() {
|
|
4587
|
+
return loadCanaries().map((r) => ({
|
|
4588
|
+
id: r.id,
|
|
4589
|
+
value: r.value,
|
|
4590
|
+
retired: Boolean(r.retiredAt),
|
|
4591
|
+
kind: r.kind,
|
|
4592
|
+
path: r.path
|
|
4593
|
+
}));
|
|
4594
|
+
}
|
|
4595
|
+
var init_registry = __esm({
|
|
4596
|
+
"src/canary/registry.ts"() {
|
|
4597
|
+
"use strict";
|
|
4598
|
+
init_dist();
|
|
4599
|
+
init_jail();
|
|
4600
|
+
}
|
|
4601
|
+
});
|
|
4602
|
+
|
|
4053
4603
|
// src/daemon/scan-watermark.ts
|
|
4054
4604
|
var MAX_LINE_BYTES;
|
|
4055
4605
|
var init_scan_watermark = __esm({
|
|
4056
4606
|
"src/daemon/scan-watermark.ts"() {
|
|
4057
4607
|
"use strict";
|
|
4058
4608
|
init_dlp();
|
|
4609
|
+
init_registry();
|
|
4059
4610
|
init_config();
|
|
4060
4611
|
init_dist();
|
|
4061
4612
|
MAX_LINE_BYTES = 2 * 1024 * 1024;
|
|
@@ -4063,9 +4614,9 @@ var init_scan_watermark = __esm({
|
|
|
4063
4614
|
});
|
|
4064
4615
|
|
|
4065
4616
|
// src/cli/aggregate/report-audit.ts
|
|
4066
|
-
import
|
|
4067
|
-
import
|
|
4068
|
-
import
|
|
4617
|
+
import fs8 from "fs";
|
|
4618
|
+
import os8 from "os";
|
|
4619
|
+
import path9 from "path";
|
|
4069
4620
|
function buildTestTimestamps(allEntries) {
|
|
4070
4621
|
const testTs = /* @__PURE__ */ new Set();
|
|
4071
4622
|
for (const e of allEntries) {
|
|
@@ -4146,8 +4697,8 @@ function getDateRange(period, now) {
|
|
|
4146
4697
|
}
|
|
4147
4698
|
}
|
|
4148
4699
|
function parseAuditLog(logPath) {
|
|
4149
|
-
if (!
|
|
4150
|
-
const raw =
|
|
4700
|
+
if (!fs8.existsSync(logPath)) return [];
|
|
4701
|
+
const raw = fs8.readFileSync(logPath, "utf-8");
|
|
4151
4702
|
return raw.split("\n").flatMap((line) => {
|
|
4152
4703
|
if (!line.trim()) return [];
|
|
4153
4704
|
try {
|
|
@@ -4201,10 +4752,10 @@ function freezeClaudeCost(acc) {
|
|
|
4201
4752
|
};
|
|
4202
4753
|
}
|
|
4203
4754
|
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
4204
|
-
const projPath =
|
|
4755
|
+
const projPath = path9.join(projectsDir, proj);
|
|
4205
4756
|
let files;
|
|
4206
4757
|
try {
|
|
4207
|
-
const stat =
|
|
4758
|
+
const stat = fs8.statSync(projPath);
|
|
4208
4759
|
if (!stat.isDirectory()) return;
|
|
4209
4760
|
files = listSessionFiles(projPath);
|
|
4210
4761
|
} catch {
|
|
@@ -4212,14 +4763,14 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
|
4212
4763
|
}
|
|
4213
4764
|
const startMs = start.getTime();
|
|
4214
4765
|
for (const file of files) {
|
|
4215
|
-
const filePath =
|
|
4766
|
+
const filePath = path9.join(projPath, file);
|
|
4216
4767
|
try {
|
|
4217
|
-
if (
|
|
4768
|
+
if (fs8.statSync(filePath).mtimeMs < startMs) continue;
|
|
4218
4769
|
} catch {
|
|
4219
4770
|
continue;
|
|
4220
4771
|
}
|
|
4221
4772
|
try {
|
|
4222
|
-
const raw =
|
|
4773
|
+
const raw = fs8.readFileSync(filePath, "utf-8");
|
|
4223
4774
|
for (const line of raw.split("\n")) {
|
|
4224
4775
|
if (!line.trim()) continue;
|
|
4225
4776
|
let entry;
|
|
@@ -4269,10 +4820,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
|
4269
4820
|
}
|
|
4270
4821
|
function loadClaudeCost(start, end, projectsDir) {
|
|
4271
4822
|
const acc = emptyClaudeCostAccumulator();
|
|
4272
|
-
if (!
|
|
4823
|
+
if (!fs8.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
4273
4824
|
let dirs;
|
|
4274
4825
|
try {
|
|
4275
|
-
dirs =
|
|
4826
|
+
dirs = fs8.readdirSync(projectsDir);
|
|
4276
4827
|
} catch {
|
|
4277
4828
|
return freezeClaudeCost(acc);
|
|
4278
4829
|
}
|
|
@@ -4283,10 +4834,10 @@ function loadClaudeCost(start, end, projectsDir) {
|
|
|
4283
4834
|
}
|
|
4284
4835
|
async function loadClaudeCostAsync(start, end, projectsDir) {
|
|
4285
4836
|
const acc = emptyClaudeCostAccumulator();
|
|
4286
|
-
if (!
|
|
4837
|
+
if (!fs8.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
4287
4838
|
let dirs;
|
|
4288
4839
|
try {
|
|
4289
|
-
dirs =
|
|
4840
|
+
dirs = fs8.readdirSync(projectsDir);
|
|
4290
4841
|
} catch {
|
|
4291
4842
|
return freezeClaudeCost(acc);
|
|
4292
4843
|
}
|
|
@@ -4299,7 +4850,7 @@ async function loadClaudeCostAsync(start, end, projectsDir) {
|
|
|
4299
4850
|
function processCodexCostFile(filePath, start, end, acc) {
|
|
4300
4851
|
let lines;
|
|
4301
4852
|
try {
|
|
4302
|
-
lines =
|
|
4853
|
+
lines = fs8.readFileSync(filePath, "utf-8").split("\n");
|
|
4303
4854
|
} catch {
|
|
4304
4855
|
return;
|
|
4305
4856
|
}
|
|
@@ -4354,31 +4905,31 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
4354
4905
|
}
|
|
4355
4906
|
function listCodexSessionFiles(sessionsBase) {
|
|
4356
4907
|
const jsonlFiles = [];
|
|
4357
|
-
if (!
|
|
4908
|
+
if (!fs8.existsSync(sessionsBase)) return jsonlFiles;
|
|
4358
4909
|
try {
|
|
4359
|
-
for (const year of
|
|
4360
|
-
const yearPath =
|
|
4910
|
+
for (const year of fs8.readdirSync(sessionsBase)) {
|
|
4911
|
+
const yearPath = path9.join(sessionsBase, year);
|
|
4361
4912
|
try {
|
|
4362
|
-
if (!
|
|
4913
|
+
if (!fs8.statSync(yearPath).isDirectory()) continue;
|
|
4363
4914
|
} catch {
|
|
4364
4915
|
continue;
|
|
4365
4916
|
}
|
|
4366
|
-
for (const month of
|
|
4367
|
-
const monthPath =
|
|
4917
|
+
for (const month of fs8.readdirSync(yearPath)) {
|
|
4918
|
+
const monthPath = path9.join(yearPath, month);
|
|
4368
4919
|
try {
|
|
4369
|
-
if (!
|
|
4920
|
+
if (!fs8.statSync(monthPath).isDirectory()) continue;
|
|
4370
4921
|
} catch {
|
|
4371
4922
|
continue;
|
|
4372
4923
|
}
|
|
4373
|
-
for (const day of
|
|
4374
|
-
const dayPath =
|
|
4924
|
+
for (const day of fs8.readdirSync(monthPath)) {
|
|
4925
|
+
const dayPath = path9.join(monthPath, day);
|
|
4375
4926
|
try {
|
|
4376
|
-
if (!
|
|
4927
|
+
if (!fs8.statSync(dayPath).isDirectory()) continue;
|
|
4377
4928
|
} catch {
|
|
4378
4929
|
continue;
|
|
4379
4930
|
}
|
|
4380
|
-
for (const file of
|
|
4381
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
4931
|
+
for (const file of fs8.readdirSync(dayPath)) {
|
|
4932
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path9.join(dayPath, file));
|
|
4382
4933
|
}
|
|
4383
4934
|
}
|
|
4384
4935
|
}
|
|
@@ -4459,13 +5010,13 @@ function freezeGeminiCost(acc) {
|
|
|
4459
5010
|
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
4460
5011
|
const startMs = start.getTime();
|
|
4461
5012
|
try {
|
|
4462
|
-
if (
|
|
5013
|
+
if (fs8.statSync(filePath).mtimeMs < startMs) return;
|
|
4463
5014
|
} catch {
|
|
4464
5015
|
return;
|
|
4465
5016
|
}
|
|
4466
5017
|
let raw;
|
|
4467
5018
|
try {
|
|
4468
|
-
raw =
|
|
5019
|
+
raw = fs8.readFileSync(filePath, "utf-8");
|
|
4469
5020
|
} catch {
|
|
4470
5021
|
return;
|
|
4471
5022
|
}
|
|
@@ -4514,30 +5065,30 @@ function listGeminiSessionFiles(geminiTmpDir) {
|
|
|
4514
5065
|
const out = [];
|
|
4515
5066
|
let dirs;
|
|
4516
5067
|
try {
|
|
4517
|
-
if (!
|
|
4518
|
-
dirs =
|
|
5068
|
+
if (!fs8.statSync(geminiTmpDir).isDirectory()) return out;
|
|
5069
|
+
dirs = fs8.readdirSync(geminiTmpDir);
|
|
4519
5070
|
} catch {
|
|
4520
5071
|
return out;
|
|
4521
5072
|
}
|
|
4522
5073
|
for (const proj of dirs) {
|
|
4523
|
-
const chatsDir =
|
|
5074
|
+
const chatsDir = path9.join(geminiTmpDir, proj, "chats");
|
|
4524
5075
|
let files;
|
|
4525
5076
|
try {
|
|
4526
|
-
if (!
|
|
4527
|
-
files =
|
|
5077
|
+
if (!fs8.statSync(chatsDir).isDirectory()) continue;
|
|
5078
|
+
files = fs8.readdirSync(chatsDir);
|
|
4528
5079
|
} catch {
|
|
4529
5080
|
continue;
|
|
4530
5081
|
}
|
|
4531
5082
|
for (const f of files) {
|
|
4532
5083
|
if (!f.endsWith(".jsonl")) continue;
|
|
4533
|
-
out.push({ projectKey: proj, file:
|
|
5084
|
+
out.push({ projectKey: proj, file: path9.join(chatsDir, f) });
|
|
4534
5085
|
}
|
|
4535
5086
|
}
|
|
4536
5087
|
return out;
|
|
4537
5088
|
}
|
|
4538
5089
|
function loadGeminiCost(start, end, geminiTmpDir) {
|
|
4539
5090
|
const acc = emptyGeminiAccumulator();
|
|
4540
|
-
if (!
|
|
5091
|
+
if (!fs8.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
4541
5092
|
for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
|
|
4542
5093
|
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
4543
5094
|
}
|
|
@@ -4545,7 +5096,7 @@ function loadGeminiCost(start, end, geminiTmpDir) {
|
|
|
4545
5096
|
}
|
|
4546
5097
|
async function loadGeminiCostAsync(start, end, geminiTmpDir) {
|
|
4547
5098
|
const acc = emptyGeminiAccumulator();
|
|
4548
|
-
if (!
|
|
5099
|
+
if (!fs8.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
4549
5100
|
const files = listGeminiSessionFiles(geminiTmpDir);
|
|
4550
5101
|
const CHUNK_SIZE = 5;
|
|
4551
5102
|
for (let i = 0; i < files.length; i++) {
|
|
@@ -4558,6 +5109,7 @@ async function loadGeminiCostAsync(start, end, geminiTmpDir) {
|
|
|
4558
5109
|
}
|
|
4559
5110
|
function dimensionOfBlock(checkedBy, ruleName) {
|
|
4560
5111
|
if (checkedBy.includes("egress")) return "network";
|
|
5112
|
+
if (ruleName.startsWith("ssrf:")) return "network";
|
|
4561
5113
|
if (checkedBy.includes("pii") || checkedBy.includes("dlp")) return "data";
|
|
4562
5114
|
if (checkedBy === "loop-detected" || checkedBy === "mcp-pin-mismatch" || checkedBy.startsWith("injection"))
|
|
4563
5115
|
return "detection";
|
|
@@ -4568,11 +5120,11 @@ function dimensionOfBlock(checkedBy, ruleName) {
|
|
|
4568
5120
|
}
|
|
4569
5121
|
function aggregateReportFromAudit(period, opts = {}) {
|
|
4570
5122
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
4571
|
-
const auditLogPath2 = opts.auditLogPath ??
|
|
4572
|
-
const claudeProjectsDir = opts.claudeProjectsDir ??
|
|
4573
|
-
const codexSessionsDir = opts.codexSessionsDir ??
|
|
4574
|
-
const geminiTmpDir = opts.geminiTmpDir ??
|
|
4575
|
-
const hasAuditFile =
|
|
5123
|
+
const auditLogPath2 = opts.auditLogPath ?? path9.join(os8.homedir(), ".node9", "audit.log");
|
|
5124
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? path9.join(os8.homedir(), ".claude", "projects");
|
|
5125
|
+
const codexSessionsDir = opts.codexSessionsDir ?? path9.join(os8.homedir(), ".codex", "sessions");
|
|
5126
|
+
const geminiTmpDir = opts.geminiTmpDir ?? path9.join(os8.homedir(), ".gemini", "tmp");
|
|
5127
|
+
const hasAuditFile = fs8.existsSync(auditLogPath2);
|
|
4576
5128
|
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath2);
|
|
4577
5129
|
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
4578
5130
|
const { start, end } = getDateRange(period, now);
|
|
@@ -4826,18 +5378,18 @@ var init_keyed_guard = __esm({
|
|
|
4826
5378
|
});
|
|
4827
5379
|
|
|
4828
5380
|
// src/utils/provenance.ts
|
|
4829
|
-
import
|
|
4830
|
-
import
|
|
5381
|
+
import path10 from "path";
|
|
5382
|
+
import os9 from "os";
|
|
4831
5383
|
var USER_PREFIXES;
|
|
4832
5384
|
var init_provenance = __esm({
|
|
4833
5385
|
"src/utils/provenance.ts"() {
|
|
4834
5386
|
"use strict";
|
|
4835
5387
|
USER_PREFIXES = [
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
5388
|
+
path10.join(os9.homedir(), "bin"),
|
|
5389
|
+
path10.join(os9.homedir(), ".local", "bin"),
|
|
5390
|
+
path10.join(os9.homedir(), ".cargo", "bin"),
|
|
5391
|
+
path10.join(os9.homedir(), ".npm-global", "bin"),
|
|
5392
|
+
path10.join(os9.homedir(), ".volta", "bin")
|
|
4841
5393
|
];
|
|
4842
5394
|
}
|
|
4843
5395
|
});
|
|
@@ -4881,14 +5433,14 @@ var init_mcp_pin = __esm({
|
|
|
4881
5433
|
});
|
|
4882
5434
|
|
|
4883
5435
|
// src/daemon/hook-baseline.ts
|
|
4884
|
-
import
|
|
4885
|
-
import
|
|
5436
|
+
import path11 from "path";
|
|
5437
|
+
import os10 from "os";
|
|
4886
5438
|
var BASELINE_FILE, NOTIFIED_FILE;
|
|
4887
5439
|
var init_hook_baseline = __esm({
|
|
4888
5440
|
"src/daemon/hook-baseline.ts"() {
|
|
4889
5441
|
"use strict";
|
|
4890
|
-
BASELINE_FILE =
|
|
4891
|
-
NOTIFIED_FILE =
|
|
5442
|
+
BASELINE_FILE = path11.join(os10.homedir(), ".node9", "hooks-baseline.json");
|
|
5443
|
+
NOTIFIED_FILE = path11.join(os10.homedir(), ".node9", "hook-heal-notified.json");
|
|
4892
5444
|
}
|
|
4893
5445
|
});
|
|
4894
5446
|
|
|
@@ -4947,9 +5499,9 @@ var init_scan_history = __esm({
|
|
|
4947
5499
|
|
|
4948
5500
|
// src/cli/commands/scan.ts
|
|
4949
5501
|
import chalk5 from "chalk";
|
|
4950
|
-
import
|
|
4951
|
-
import
|
|
4952
|
-
import
|
|
5502
|
+
import fs9 from "fs";
|
|
5503
|
+
import path12 from "path";
|
|
5504
|
+
import os11 from "os";
|
|
4953
5505
|
import stringWidth2 from "string-width";
|
|
4954
5506
|
function claudeModelPrice2(model) {
|
|
4955
5507
|
const t = pricingFor(model);
|
|
@@ -4970,12 +5522,6 @@ function isNode9SelfOutput(text) {
|
|
|
4970
5522
|
}
|
|
4971
5523
|
return false;
|
|
4972
5524
|
}
|
|
4973
|
-
function looksLikeFixtureToken(sample) {
|
|
4974
|
-
for (const re of FIXTURE_TOKEN_PATTERNS) {
|
|
4975
|
-
if (re.test(sample)) return true;
|
|
4976
|
-
}
|
|
4977
|
-
return false;
|
|
4978
|
-
}
|
|
4979
5525
|
function stripTerminalEscapes(s) {
|
|
4980
5526
|
return s.replace(TERMINAL_ESCAPE_RE, "");
|
|
4981
5527
|
}
|
|
@@ -4985,7 +5531,7 @@ function preview(input, max) {
|
|
|
4985
5531
|
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
4986
5532
|
}
|
|
4987
5533
|
function emptyScanDedup() {
|
|
4988
|
-
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set() };
|
|
5534
|
+
return { findingsKeys: /* @__PURE__ */ new Set(), dlpKeys: /* @__PURE__ */ new Set(), canaryIndex: /* @__PURE__ */ new Map() };
|
|
4989
5535
|
}
|
|
4990
5536
|
function findingKey(ruleName, inputPreview, projLabel) {
|
|
4991
5537
|
return `${ruleName ?? "<unnamed>"}|${inputPreview}|${projLabel}`;
|
|
@@ -4993,6 +5539,73 @@ function findingKey(ruleName, inputPreview, projLabel) {
|
|
|
4993
5539
|
function dlpKey(patternName, redactedSample, projLabel) {
|
|
4994
5540
|
return `${patternName}|${redactedSample}|${projLabel}`;
|
|
4995
5541
|
}
|
|
5542
|
+
function safeCanaryScanValues() {
|
|
5543
|
+
try {
|
|
5544
|
+
return canaryCtxValues();
|
|
5545
|
+
} catch {
|
|
5546
|
+
return [];
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
function recordCanaries(scanned, toolName, timestamp, projLabel, sessionId, agent, result, dedup, values) {
|
|
5550
|
+
if (values.length === 0) return [];
|
|
5551
|
+
let pool = [...values];
|
|
5552
|
+
const matched = [];
|
|
5553
|
+
for (let guard = 0; guard < 8 && pool.length > 0; guard++) {
|
|
5554
|
+
const hit = matchCanaryArgs(scanned, pool);
|
|
5555
|
+
if (!hit) break;
|
|
5556
|
+
const v = pool.find((x) => x.id === hit.id);
|
|
5557
|
+
if (v) matched.push(v.value);
|
|
5558
|
+
pool = pool.filter((x) => x.id !== hit.id);
|
|
5559
|
+
const key = `canary|${hit.id}|${sessionId}|${agent}`;
|
|
5560
|
+
const existing = dedup.canaryIndex.get(key);
|
|
5561
|
+
if (existing) {
|
|
5562
|
+
existing.count++;
|
|
5563
|
+
if (timestamp && (!existing.timestamp || timestamp < existing.timestamp)) {
|
|
5564
|
+
existing.timestamp = timestamp;
|
|
5565
|
+
}
|
|
5566
|
+
continue;
|
|
5567
|
+
}
|
|
5568
|
+
const finding = {
|
|
5569
|
+
canaryId: hit.id,
|
|
5570
|
+
canaryHash: v?.valueHash ?? "",
|
|
5571
|
+
kind: v?.kind ?? "unknown",
|
|
5572
|
+
field: v?.field ?? "",
|
|
5573
|
+
path: v?.path ?? "",
|
|
5574
|
+
view: hit.view,
|
|
5575
|
+
retired: hit.retired,
|
|
5576
|
+
toolName,
|
|
5577
|
+
timestamp,
|
|
5578
|
+
project: projLabel,
|
|
5579
|
+
sessionId,
|
|
5580
|
+
agent,
|
|
5581
|
+
count: 1
|
|
5582
|
+
};
|
|
5583
|
+
dedup.canaryIndex.set(key, finding);
|
|
5584
|
+
result.canaryFindings.push(finding);
|
|
5585
|
+
}
|
|
5586
|
+
return matched;
|
|
5587
|
+
}
|
|
5588
|
+
function scrubDecoys(subject, values) {
|
|
5589
|
+
if (values.length === 0) return subject;
|
|
5590
|
+
const scrubText = (s) => {
|
|
5591
|
+
let out = s;
|
|
5592
|
+
for (const v of values) if (v) out = out.split(v).join("[node9-decoy]");
|
|
5593
|
+
return out;
|
|
5594
|
+
};
|
|
5595
|
+
const walk = (v, depth) => {
|
|
5596
|
+
if (depth > 6) return v;
|
|
5597
|
+
if (typeof v === "string") return scrubText(v);
|
|
5598
|
+
if (Array.isArray(v)) return v.map((x) => walk(x, depth + 1));
|
|
5599
|
+
if (v && typeof v === "object") {
|
|
5600
|
+
const out = {};
|
|
5601
|
+
for (const [k, child] of Object.entries(v))
|
|
5602
|
+
out[k] = walk(child, depth + 1);
|
|
5603
|
+
return out;
|
|
5604
|
+
}
|
|
5605
|
+
return v;
|
|
5606
|
+
};
|
|
5607
|
+
return walk(subject, 0);
|
|
5608
|
+
}
|
|
4996
5609
|
function pushFsOpAstFinding(command, toolName, input, timestamp, projLabel, sessionId, agent, result, dedup) {
|
|
4997
5610
|
const fsVerdict = analyzeFsOperation(command);
|
|
4998
5611
|
if (!fsVerdict) return false;
|
|
@@ -5091,7 +5704,7 @@ function buildRuleSources() {
|
|
|
5091
5704
|
}
|
|
5092
5705
|
return sources;
|
|
5093
5706
|
}
|
|
5094
|
-
function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
5707
|
+
function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, result, dedup, canaryVals, onProgress, onLine) {
|
|
5095
5708
|
result.filesScanned++;
|
|
5096
5709
|
result.sessions++;
|
|
5097
5710
|
onProgress?.(result.filesScanned);
|
|
@@ -5099,7 +5712,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
5099
5712
|
const session = { sessionId, costUSD: 0, toolCalls: 0 };
|
|
5100
5713
|
let raw;
|
|
5101
5714
|
try {
|
|
5102
|
-
raw =
|
|
5715
|
+
raw = fs9.readFileSync(path12.join(projPath, file), "utf-8");
|
|
5103
5716
|
} catch {
|
|
5104
5717
|
return;
|
|
5105
5718
|
}
|
|
@@ -5130,7 +5743,18 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
5130
5743
|
if (Array.isArray(content2)) {
|
|
5131
5744
|
const text = content2.filter((b) => b.type === "text").map((b) => b["text"] ?? "").join("\n");
|
|
5132
5745
|
if (text) {
|
|
5133
|
-
const
|
|
5746
|
+
const decoysHere0 = recordCanaries(
|
|
5747
|
+
{ text },
|
|
5748
|
+
"user-prompt",
|
|
5749
|
+
entry.timestamp ?? "",
|
|
5750
|
+
projLabel,
|
|
5751
|
+
sessionId,
|
|
5752
|
+
"claude",
|
|
5753
|
+
result,
|
|
5754
|
+
dedup,
|
|
5755
|
+
canaryVals
|
|
5756
|
+
);
|
|
5757
|
+
const dlpMatch = scanArgs(scrubDecoys({ text }, decoysHere0));
|
|
5134
5758
|
if (dlpMatch) {
|
|
5135
5759
|
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
5136
5760
|
if (!dedup.dlpKeys.has(k)) {
|
|
@@ -5151,15 +5775,25 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
5151
5775
|
if (block.type !== "tool_result") continue;
|
|
5152
5776
|
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
5153
5777
|
if (filePath) {
|
|
5154
|
-
const ext =
|
|
5778
|
+
const ext = path12.extname(filePath).toLowerCase();
|
|
5155
5779
|
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
5156
5780
|
}
|
|
5157
5781
|
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
5158
5782
|
if (!resultText) continue;
|
|
5159
5783
|
if (isNode9SelfOutput(resultText)) continue;
|
|
5160
|
-
const
|
|
5784
|
+
const decoysHere1 = recordCanaries(
|
|
5785
|
+
{ text: resultText },
|
|
5786
|
+
"tool-result",
|
|
5787
|
+
entry.timestamp ?? "",
|
|
5788
|
+
projLabel,
|
|
5789
|
+
sessionId,
|
|
5790
|
+
"claude",
|
|
5791
|
+
result,
|
|
5792
|
+
dedup,
|
|
5793
|
+
canaryVals
|
|
5794
|
+
);
|
|
5795
|
+
const dlpMatch = scanArgs(scrubDecoys({ text: resultText }, decoysHere1));
|
|
5161
5796
|
if (dlpMatch) {
|
|
5162
|
-
if (looksLikeFixtureToken(dlpMatch.redactedSample)) continue;
|
|
5163
5797
|
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
5164
5798
|
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
5165
5799
|
if (!dedup.dlpKeys.has(k)) {
|
|
@@ -5211,9 +5845,20 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
5211
5845
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
5212
5846
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
5213
5847
|
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
5214
|
-
const inputFileExt = inputFilePath ?
|
|
5215
|
-
|
|
5216
|
-
|
|
5848
|
+
const inputFileExt = inputFilePath ? path12.extname(inputFilePath).toLowerCase() : "";
|
|
5849
|
+
const canaryInInput = recordCanaries(
|
|
5850
|
+
input,
|
|
5851
|
+
toolName,
|
|
5852
|
+
entry.timestamp ?? "",
|
|
5853
|
+
projLabel,
|
|
5854
|
+
sessionId,
|
|
5855
|
+
"claude",
|
|
5856
|
+
result,
|
|
5857
|
+
dedup,
|
|
5858
|
+
canaryVals
|
|
5859
|
+
);
|
|
5860
|
+
if (canaryInInput.length === 0 && CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
5861
|
+
const dlpMatch = scanArgs(scrubDecoys(input, canaryInInput));
|
|
5217
5862
|
if (dlpMatch) {
|
|
5218
5863
|
if (firstDlpTs === null) firstDlpTs = entry.timestamp ?? null;
|
|
5219
5864
|
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
@@ -5308,14 +5953,14 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
5308
5953
|
}
|
|
5309
5954
|
result.perSession.push(session);
|
|
5310
5955
|
}
|
|
5311
|
-
async function processClaudeProjectAsync(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
5312
|
-
const projPath =
|
|
5956
|
+
async function processClaudeProjectAsync(proj, projectsDir, ruleSources, startDate, result, dedup, canaryVals, onProgress, onLine) {
|
|
5957
|
+
const projPath = path12.join(projectsDir, proj);
|
|
5313
5958
|
try {
|
|
5314
|
-
if (!
|
|
5959
|
+
if (!fs9.statSync(projPath).isDirectory()) return;
|
|
5315
5960
|
} catch {
|
|
5316
5961
|
return;
|
|
5317
5962
|
}
|
|
5318
|
-
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(
|
|
5963
|
+
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os11.homedir(), "~")).slice(
|
|
5319
5964
|
0,
|
|
5320
5965
|
40
|
|
5321
5966
|
);
|
|
@@ -5334,6 +5979,7 @@ async function processClaudeProjectAsync(proj, projectsDir, ruleSources, startDa
|
|
|
5334
5979
|
startDate,
|
|
5335
5980
|
result,
|
|
5336
5981
|
dedup,
|
|
5982
|
+
canaryVals,
|
|
5337
5983
|
onProgress,
|
|
5338
5984
|
onLine
|
|
5339
5985
|
);
|
|
@@ -5351,6 +5997,7 @@ function emptyClaudeScan() {
|
|
|
5351
5997
|
bashCalls: 0,
|
|
5352
5998
|
findings: [],
|
|
5353
5999
|
dlpFindings: [],
|
|
6000
|
+
canaryFindings: [],
|
|
5354
6001
|
loopFindings: [],
|
|
5355
6002
|
totalCostUSD: 0,
|
|
5356
6003
|
firstDate: null,
|
|
@@ -5360,17 +6007,18 @@ function emptyClaudeScan() {
|
|
|
5360
6007
|
};
|
|
5361
6008
|
}
|
|
5362
6009
|
async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
|
|
5363
|
-
const projectsDir =
|
|
6010
|
+
const projectsDir = path12.join(os11.homedir(), ".claude", "projects");
|
|
5364
6011
|
const result = emptyClaudeScan();
|
|
5365
|
-
if (!
|
|
6012
|
+
if (!fs9.existsSync(projectsDir)) return result;
|
|
5366
6013
|
let projDirs;
|
|
5367
6014
|
try {
|
|
5368
|
-
projDirs =
|
|
6015
|
+
projDirs = fs9.readdirSync(projectsDir);
|
|
5369
6016
|
} catch {
|
|
5370
6017
|
return result;
|
|
5371
6018
|
}
|
|
5372
6019
|
const ruleSources = buildRuleSources();
|
|
5373
6020
|
const dedup = emptyScanDedup();
|
|
6021
|
+
const canaryVals = safeCanaryScanValues();
|
|
5374
6022
|
for (const proj of projDirs) {
|
|
5375
6023
|
await processClaudeProjectAsync(
|
|
5376
6024
|
proj,
|
|
@@ -5379,6 +6027,7 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
|
|
|
5379
6027
|
startDate,
|
|
5380
6028
|
result,
|
|
5381
6029
|
dedup,
|
|
6030
|
+
canaryVals,
|
|
5382
6031
|
onProgress,
|
|
5383
6032
|
onLine
|
|
5384
6033
|
);
|
|
@@ -5386,7 +6035,8 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
|
|
|
5386
6035
|
return result;
|
|
5387
6036
|
}
|
|
5388
6037
|
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
5389
|
-
const
|
|
6038
|
+
const canaryVals = safeCanaryScanValues();
|
|
6039
|
+
const tmpDir = path12.join(os11.homedir(), ".gemini", "tmp");
|
|
5390
6040
|
const result = {
|
|
5391
6041
|
filesScanned: 0,
|
|
5392
6042
|
sessions: 0,
|
|
@@ -5394,6 +6044,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
5394
6044
|
bashCalls: 0,
|
|
5395
6045
|
findings: [],
|
|
5396
6046
|
dlpFindings: [],
|
|
6047
|
+
canaryFindings: [],
|
|
5397
6048
|
loopFindings: [],
|
|
5398
6049
|
totalCostUSD: 0,
|
|
5399
6050
|
firstDate: null,
|
|
@@ -5402,33 +6053,33 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
5402
6053
|
perSession: []
|
|
5403
6054
|
};
|
|
5404
6055
|
const dedup = emptyScanDedup();
|
|
5405
|
-
if (!
|
|
6056
|
+
if (!fs9.existsSync(tmpDir)) return result;
|
|
5406
6057
|
let slugDirs;
|
|
5407
6058
|
try {
|
|
5408
|
-
slugDirs =
|
|
6059
|
+
slugDirs = fs9.readdirSync(tmpDir);
|
|
5409
6060
|
} catch {
|
|
5410
6061
|
return result;
|
|
5411
6062
|
}
|
|
5412
6063
|
const ruleSources = buildRuleSources();
|
|
5413
6064
|
for (const slug2 of slugDirs) {
|
|
5414
|
-
const slugPath =
|
|
6065
|
+
const slugPath = path12.join(tmpDir, slug2);
|
|
5415
6066
|
try {
|
|
5416
|
-
if (!
|
|
6067
|
+
if (!fs9.statSync(slugPath).isDirectory()) continue;
|
|
5417
6068
|
} catch {
|
|
5418
6069
|
continue;
|
|
5419
6070
|
}
|
|
5420
6071
|
let projLabel = stripTerminalEscapes(slug2).slice(0, 40);
|
|
5421
6072
|
try {
|
|
5422
6073
|
projLabel = stripTerminalEscapes(
|
|
5423
|
-
|
|
5424
|
-
).replace(
|
|
6074
|
+
fs9.readFileSync(path12.join(slugPath, ".project_root"), "utf-8").trim()
|
|
6075
|
+
).replace(os11.homedir(), "~").slice(0, 40);
|
|
5425
6076
|
} catch {
|
|
5426
6077
|
}
|
|
5427
|
-
const chatsDir =
|
|
5428
|
-
if (!
|
|
6078
|
+
const chatsDir = path12.join(slugPath, "chats");
|
|
6079
|
+
if (!fs9.existsSync(chatsDir)) continue;
|
|
5429
6080
|
let chatFiles;
|
|
5430
6081
|
try {
|
|
5431
|
-
chatFiles =
|
|
6082
|
+
chatFiles = fs9.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
5432
6083
|
} catch {
|
|
5433
6084
|
continue;
|
|
5434
6085
|
}
|
|
@@ -5441,7 +6092,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
5441
6092
|
onProgress?.(result.filesScanned);
|
|
5442
6093
|
let raw;
|
|
5443
6094
|
try {
|
|
5444
|
-
raw =
|
|
6095
|
+
raw = fs9.readFileSync(path12.join(chatsDir, chatFile), "utf-8");
|
|
5445
6096
|
} catch {
|
|
5446
6097
|
continue;
|
|
5447
6098
|
}
|
|
@@ -5470,7 +6121,18 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
5470
6121
|
const content = msg.content;
|
|
5471
6122
|
const text = Array.isArray(content) ? content.map((c) => c.text ?? "").join("\n") : typeof content === "string" ? content : "";
|
|
5472
6123
|
if (text) {
|
|
5473
|
-
const
|
|
6124
|
+
const decoysHere3 = recordCanaries(
|
|
6125
|
+
{ text },
|
|
6126
|
+
"user-prompt",
|
|
6127
|
+
msg.timestamp ?? "",
|
|
6128
|
+
projLabel,
|
|
6129
|
+
sessionId,
|
|
6130
|
+
"gemini",
|
|
6131
|
+
result,
|
|
6132
|
+
dedup,
|
|
6133
|
+
canaryVals
|
|
6134
|
+
);
|
|
6135
|
+
const dlpMatch = scanArgs(scrubDecoys({ text }, decoysHere3));
|
|
5474
6136
|
if (dlpMatch) {
|
|
5475
6137
|
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
5476
6138
|
if (!dedup.dlpKeys.has(k)) {
|
|
@@ -5517,7 +6179,18 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
5517
6179
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
5518
6180
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
5519
6181
|
continue;
|
|
5520
|
-
const
|
|
6182
|
+
const decoysHere4 = recordCanaries(
|
|
6183
|
+
input,
|
|
6184
|
+
toolName,
|
|
6185
|
+
msg.timestamp ?? "",
|
|
6186
|
+
projLabel,
|
|
6187
|
+
sessionId,
|
|
6188
|
+
"gemini",
|
|
6189
|
+
result,
|
|
6190
|
+
dedup,
|
|
6191
|
+
canaryVals
|
|
6192
|
+
);
|
|
6193
|
+
const dlpMatch = scanArgs(scrubDecoys(input, decoysHere4));
|
|
5521
6194
|
if (dlpMatch) {
|
|
5522
6195
|
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
5523
6196
|
if (!dedup.dlpKeys.has(k)) {
|
|
@@ -5614,7 +6287,8 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
5614
6287
|
return result;
|
|
5615
6288
|
}
|
|
5616
6289
|
function scanCodexHistory(startDate, onProgress, onLine) {
|
|
5617
|
-
const
|
|
6290
|
+
const canaryVals = safeCanaryScanValues();
|
|
6291
|
+
const sessionsBase = path12.join(os11.homedir(), ".codex", "sessions");
|
|
5618
6292
|
const result = {
|
|
5619
6293
|
filesScanned: 0,
|
|
5620
6294
|
sessions: 0,
|
|
@@ -5622,6 +6296,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
5622
6296
|
bashCalls: 0,
|
|
5623
6297
|
findings: [],
|
|
5624
6298
|
dlpFindings: [],
|
|
6299
|
+
canaryFindings: [],
|
|
5625
6300
|
loopFindings: [],
|
|
5626
6301
|
totalCostUSD: 0,
|
|
5627
6302
|
firstDate: null,
|
|
@@ -5630,32 +6305,32 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
5630
6305
|
perSession: []
|
|
5631
6306
|
};
|
|
5632
6307
|
const dedup = emptyScanDedup();
|
|
5633
|
-
if (!
|
|
6308
|
+
if (!fs9.existsSync(sessionsBase)) return result;
|
|
5634
6309
|
const jsonlFiles = [];
|
|
5635
6310
|
try {
|
|
5636
|
-
for (const year of
|
|
5637
|
-
const yearPath =
|
|
6311
|
+
for (const year of fs9.readdirSync(sessionsBase)) {
|
|
6312
|
+
const yearPath = path12.join(sessionsBase, year);
|
|
5638
6313
|
try {
|
|
5639
|
-
if (!
|
|
6314
|
+
if (!fs9.statSync(yearPath).isDirectory()) continue;
|
|
5640
6315
|
} catch {
|
|
5641
6316
|
continue;
|
|
5642
6317
|
}
|
|
5643
|
-
for (const month of
|
|
5644
|
-
const monthPath =
|
|
6318
|
+
for (const month of fs9.readdirSync(yearPath)) {
|
|
6319
|
+
const monthPath = path12.join(yearPath, month);
|
|
5645
6320
|
try {
|
|
5646
|
-
if (!
|
|
6321
|
+
if (!fs9.statSync(monthPath).isDirectory()) continue;
|
|
5647
6322
|
} catch {
|
|
5648
6323
|
continue;
|
|
5649
6324
|
}
|
|
5650
|
-
for (const day of
|
|
5651
|
-
const dayPath =
|
|
6325
|
+
for (const day of fs9.readdirSync(monthPath)) {
|
|
6326
|
+
const dayPath = path12.join(monthPath, day);
|
|
5652
6327
|
try {
|
|
5653
|
-
if (!
|
|
6328
|
+
if (!fs9.statSync(dayPath).isDirectory()) continue;
|
|
5654
6329
|
} catch {
|
|
5655
6330
|
continue;
|
|
5656
6331
|
}
|
|
5657
|
-
for (const file of
|
|
5658
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
6332
|
+
for (const file of fs9.readdirSync(dayPath)) {
|
|
6333
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path12.join(dayPath, file));
|
|
5659
6334
|
}
|
|
5660
6335
|
}
|
|
5661
6336
|
}
|
|
@@ -5669,7 +6344,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
5669
6344
|
onProgress?.(result.filesScanned);
|
|
5670
6345
|
let lines;
|
|
5671
6346
|
try {
|
|
5672
|
-
lines =
|
|
6347
|
+
lines = fs9.readFileSync(filePath, "utf-8").split("\n");
|
|
5673
6348
|
} catch {
|
|
5674
6349
|
continue;
|
|
5675
6350
|
}
|
|
@@ -5696,7 +6371,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
5696
6371
|
sessionId = String(payload["id"] ?? filePath);
|
|
5697
6372
|
startTime = String(payload["timestamp"] ?? "");
|
|
5698
6373
|
const cwd = String(payload["cwd"] ?? "");
|
|
5699
|
-
projLabel = stripTerminalEscapes(cwd.replace(
|
|
6374
|
+
projLabel = stripTerminalEscapes(cwd.replace(os11.homedir(), "~")).slice(0, 40);
|
|
5700
6375
|
continue;
|
|
5701
6376
|
}
|
|
5702
6377
|
if (entry.type === "turn_context" && typeof payload["model"] === "string") {
|
|
@@ -5714,7 +6389,18 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
5714
6389
|
if (entry.type === "event_msg" && payload["type"] === "user_message") {
|
|
5715
6390
|
const text = String(payload["message"] ?? "");
|
|
5716
6391
|
if (text) {
|
|
5717
|
-
const
|
|
6392
|
+
const decoysHere9 = recordCanaries(
|
|
6393
|
+
{ text },
|
|
6394
|
+
"user-prompt",
|
|
6395
|
+
entry.timestamp ?? startTime,
|
|
6396
|
+
projLabel,
|
|
6397
|
+
sessionId,
|
|
6398
|
+
"codex",
|
|
6399
|
+
result,
|
|
6400
|
+
dedup,
|
|
6401
|
+
canaryVals
|
|
6402
|
+
);
|
|
6403
|
+
const dlpMatch2 = scanArgs(scrubDecoys({ text }, decoysHere9));
|
|
5718
6404
|
if (dlpMatch2) {
|
|
5719
6405
|
const k = dlpKey(dlpMatch2.patternName, dlpMatch2.redactedSample, projLabel);
|
|
5720
6406
|
if (!dedup.dlpKeys.has(k)) {
|
|
@@ -5758,7 +6444,18 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
5758
6444
|
}
|
|
5759
6445
|
const rawCmd = String(input["command"] ?? "").trimStart();
|
|
5760
6446
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
5761
|
-
const
|
|
6447
|
+
const decoysHere10 = recordCanaries(
|
|
6448
|
+
input,
|
|
6449
|
+
toolName,
|
|
6450
|
+
ts,
|
|
6451
|
+
projLabel,
|
|
6452
|
+
sessionId,
|
|
6453
|
+
"codex",
|
|
6454
|
+
result,
|
|
6455
|
+
dedup,
|
|
6456
|
+
canaryVals
|
|
6457
|
+
);
|
|
6458
|
+
const dlpMatch = scanArgs(scrubDecoys(input, decoysHere10));
|
|
5762
6459
|
if (dlpMatch) {
|
|
5763
6460
|
const k = dlpKey(dlpMatch.patternName, dlpMatch.redactedSample, projLabel);
|
|
5764
6461
|
if (!dedup.dlpKeys.has(k)) {
|
|
@@ -5857,7 +6554,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
5857
6554
|
}
|
|
5858
6555
|
return result;
|
|
5859
6556
|
}
|
|
5860
|
-
var toolInspectionMap, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS,
|
|
6557
|
+
var toolInspectionMap, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
|
|
5861
6558
|
var init_scan = __esm({
|
|
5862
6559
|
"src/cli/commands/scan.ts"() {
|
|
5863
6560
|
"use strict";
|
|
@@ -5867,6 +6564,8 @@ var init_scan = __esm({
|
|
|
5867
6564
|
init_policy();
|
|
5868
6565
|
init_dist();
|
|
5869
6566
|
init_dlp();
|
|
6567
|
+
init_dlp();
|
|
6568
|
+
init_registry();
|
|
5870
6569
|
init_litellm();
|
|
5871
6570
|
init_cost_gemini();
|
|
5872
6571
|
init_cost_codex();
|
|
@@ -5915,16 +6614,6 @@ var init_scan = __esm({
|
|
|
5915
6614
|
/\bseverity:\s*['"](?:block|review|allow)['"]/,
|
|
5916
6615
|
/NODE9 SECURITY ALERT/
|
|
5917
6616
|
];
|
|
5918
|
-
FIXTURE_TOKEN_PATTERNS = [
|
|
5919
|
-
/(.)\1{5,}/,
|
|
5920
|
-
// 6+ repeated characters (aaaaaa, 000000)
|
|
5921
|
-
/(?:EXAMPLE|FAKE|DUMMY|PLACEHOLDER|XXXXX)/i,
|
|
5922
|
-
/abcdefghijklmn/i,
|
|
5923
|
-
// long alpha sequence — fixture, not entropy
|
|
5924
|
-
/1234567890/,
|
|
5925
|
-
// long digit sequence — fixture, not entropy
|
|
5926
|
-
/qwerty/i
|
|
5927
|
-
];
|
|
5928
6617
|
TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
5929
6618
|
LOOP_TOOLS = /* @__PURE__ */ new Set([
|
|
5930
6619
|
"bash",
|
|
@@ -5944,23 +6633,23 @@ var init_scan = __esm({
|
|
|
5944
6633
|
});
|
|
5945
6634
|
|
|
5946
6635
|
// src/tui/dashboard/data.ts
|
|
5947
|
-
import
|
|
5948
|
-
import
|
|
5949
|
-
import
|
|
6636
|
+
import fs10 from "fs";
|
|
6637
|
+
import os12 from "os";
|
|
6638
|
+
import path13 from "path";
|
|
5950
6639
|
import http from "http";
|
|
5951
6640
|
function auditLogPath() {
|
|
5952
|
-
return
|
|
6641
|
+
return path13.join(os12.homedir(), ".node9", "audit.log");
|
|
5953
6642
|
}
|
|
5954
6643
|
function readAuditEntriesAsync(chunkSize = 1e3, customPath) {
|
|
5955
6644
|
return new Promise((resolve) => {
|
|
5956
6645
|
const p = customPath ?? auditLogPath();
|
|
5957
|
-
if (!
|
|
6646
|
+
if (!fs10.existsSync(p)) {
|
|
5958
6647
|
resolve([]);
|
|
5959
6648
|
return;
|
|
5960
6649
|
}
|
|
5961
6650
|
let raw;
|
|
5962
6651
|
try {
|
|
5963
|
-
raw =
|
|
6652
|
+
raw = fs10.readFileSync(p, "utf8");
|
|
5964
6653
|
} catch {
|
|
5965
6654
|
resolve([]);
|
|
5966
6655
|
return;
|
|
@@ -6092,13 +6781,13 @@ function loadBlast() {
|
|
|
6092
6781
|
}
|
|
6093
6782
|
}
|
|
6094
6783
|
function shortenPath(p) {
|
|
6095
|
-
const home =
|
|
6784
|
+
const home = os12.homedir();
|
|
6096
6785
|
return p.startsWith(home) ? p.replace(home, "~") : p;
|
|
6097
6786
|
}
|
|
6098
6787
|
async function loadReportAuditAsync(period) {
|
|
6099
|
-
const claudeProjectsDir =
|
|
6100
|
-
const codexSessionsDir =
|
|
6101
|
-
const geminiTmpDir =
|
|
6788
|
+
const claudeProjectsDir = path13.join(os12.homedir(), ".claude", "projects");
|
|
6789
|
+
const codexSessionsDir = path13.join(os12.homedir(), ".codex", "sessions");
|
|
6790
|
+
const geminiTmpDir = path13.join(os12.homedir(), ".gemini", "tmp");
|
|
6102
6791
|
const { start, end } = getDateRange(period, /* @__PURE__ */ new Date());
|
|
6103
6792
|
const entries = await readAuditEntriesAsync();
|
|
6104
6793
|
void ensurePricingLoaded();
|
|
@@ -7197,8 +7886,8 @@ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-run
|
|
|
7197
7886
|
function TopToolsProjects({ audit }) {
|
|
7198
7887
|
const data = audit?.data;
|
|
7199
7888
|
const tools = data ? [...data.toolMap.entries()].sort(([, a], [, b]) => b.calls - a.calls).slice(0, ROW_LIMIT) : [];
|
|
7200
|
-
const projects = data ? [...data.cost.byProject.entries()].map(([
|
|
7201
|
-
name: basenameOf(
|
|
7889
|
+
const projects = data ? [...data.cost.byProject.entries()].map(([path14, r]) => ({
|
|
7890
|
+
name: basenameOf(path14),
|
|
7202
7891
|
cost: r.cost,
|
|
7203
7892
|
tokens: r.inputTokens + r.outputTokens
|
|
7204
7893
|
})).sort((a, b) => b.cost - a.cost).slice(0, ROW_LIMIT) : [];
|
|
@@ -7635,8 +8324,8 @@ function pickTopLoopFile(loops) {
|
|
|
7635
8324
|
map.set(k, (map.get(k) ?? 0) + (l.count ?? 0));
|
|
7636
8325
|
}
|
|
7637
8326
|
if (map.size === 0) return void 0;
|
|
7638
|
-
const [
|
|
7639
|
-
return { path:
|
|
8327
|
+
const [path14, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
8328
|
+
return { path: path14, count };
|
|
7640
8329
|
}
|
|
7641
8330
|
var EMPTY_FILTERED_SCAN;
|
|
7642
8331
|
var init_derive = __esm({
|