opencode-swarm 7.150.0 → 7.151.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{config-doctor-96qyv31a.js → config-doctor-23ega26j.js} +2 -2
- package/dist/cli/{core-xx0x5tfv.js → core-3g4krkj8.js} +1 -1
- package/dist/cli/{curation-policy-nck9g765.js → curation-policy-fmvccdrg.js} +6 -6
- package/dist/cli/{curator-llm-factory-b83jsg02.js → curator-llm-factory-k0nxn5gt.js} +28 -28
- package/dist/cli/{curator-frrehxxe.js → curator-rym2wtn6.js} +28 -28
- package/dist/cli/{evidence-summary-service-dv4bt2bx.js → evidence-summary-service-f0gvjesf.js} +12 -12
- package/dist/cli/{gate-evidence-dkvs7aez.js → gate-evidence-cghq17xc.js} +6 -6
- package/dist/cli/{guardrail-explain-1zbk21fq.js → guardrail-explain-cv52smw2.js} +29 -29
- package/dist/cli/{guardrail-log-d20jewtr.js → guardrail-log-wgwmh9b7.js} +5 -5
- package/dist/cli/{guardrail-reset-wb3ba243.js → guardrail-reset-vx7zsmzd.js} +28 -28
- package/dist/cli/{hive-promoter-10zv1b45.js → hive-promoter-jb2nzv3j.js} +28 -28
- package/dist/cli/{index-s0xz2520.js → index-02qc4ehj.js} +3 -3
- package/dist/cli/{index-k2sm9677.js → index-0q2mjxp2.js} +1 -1
- package/dist/cli/index-2qte9nr7.js +149 -0
- package/dist/cli/{index-mnhagjvn.js → index-5ep31be7.js} +4 -4
- package/dist/cli/{index-exn0ztsv.js → index-6zkm4p7p.js} +1 -1
- package/dist/cli/{index-t54jt9cs.js → index-7qbyetsf.js} +1 -1
- package/dist/cli/{index-x3pt7d0b.js → index-8wy7pb4a.js} +2 -2
- package/dist/cli/{index-prxrmc3b.js → index-9wpr1p2g.js} +2 -2
- package/dist/cli/{index-xggpmtfx.js → index-aw492hpn.js} +57 -40
- package/dist/cli/{index-an922xj4.js → index-bfc4bbq4.js} +7 -7
- package/dist/cli/{index-dtmgt2xn.js → index-dvycdkhj.js} +1 -0
- package/dist/cli/{index-r5djc1cc.js → index-e9qh1j19.js} +5 -5
- package/dist/cli/{index-4sdhbxgq.js → index-f73px21b.js} +1 -1
- package/dist/cli/{index-mef0trex.js → index-fkqfcv14.js} +2 -1
- package/dist/cli/{index-apwcvfjt.js → index-frp2hwdg.js} +2 -2
- package/dist/cli/{index-qqs44wcp.js → index-fybg880e.js} +77 -64
- package/dist/cli/{index-n99s022c.js → index-jjpppdvb.js} +1 -1
- package/dist/cli/{index-htqs4p52.js → index-kghpgs47.js} +1 -1
- package/dist/cli/{index-bhg6brtw.js → index-qa8bmwav.js} +745 -8
- package/dist/cli/{index-ma1p1w05.js → index-sdff3rvj.js} +6 -6
- package/dist/cli/{index-reknv4f9.js → index-sg48w5e4.js} +2 -2
- package/dist/cli/{index-1rpjd5na.js → index-tfkc0zf4.js} +1 -1
- package/dist/cli/{index-w9pb72vg.js → index-tp1avc8j.js} +2 -2
- package/dist/cli/{index-m8dn0qnm.js → index-txbd25bc.js} +1 -1
- package/dist/cli/{index-jb9tb1sd.js → index-v3gshkpz.js} +1 -1
- package/dist/cli/{index-8cn6545x.js → index-vradyw0x.js} +30 -30
- package/dist/cli/{index-zyv56em5.js → index-vs8wtavs.js} +3 -3
- package/dist/cli/{index-1rww5k9e.js → index-wna9f75x.js} +4 -4
- package/dist/cli/{index-0tn1kxej.js → index-wxkgy9gh.js} +7 -7
- package/dist/cli/{index-1wmd5wn1.js → index-xeptzfb6.js} +4 -4
- package/dist/cli/{index-vy6q9d8j.js → index-z4bg1hv8.js} +1 -1
- package/dist/cli/{index-g75rbw3e.js → index-zkxnba57.js} +5 -5
- package/dist/cli/index.js +28 -28
- package/dist/cli/{knowledge-escalator-pc7smwrd.js → knowledge-escalator-a9dwxqz8.js} +11 -11
- package/dist/cli/{knowledge-events-jmb3jm84.js → knowledge-events-0q4sb713.js} +9 -9
- package/dist/cli/{knowledge-link-mzn8x0qd.js → knowledge-link-v7hj1h8m.js} +5 -5
- package/dist/cli/{knowledge-store-wd6zh05h.js → knowledge-store-y2nbt9jk.js} +6 -6
- package/dist/cli/{knowledge-validator-2v6ar1wk.js → knowledge-validator-4wr1m0xa.js} +8 -8
- package/dist/cli/{pending-delegations-qzy6rnvw.js → pending-delegations-a2nmssr1.js} +3 -3
- package/dist/cli/{pr-subscriptions-q862pvz1.js → pr-subscriptions-7879nq5k.js} +3 -3
- package/dist/cli/{runner-1aygs98c.js → runner-7znestx4.js} +6 -6
- package/dist/cli/{scan-cursor-hr8e3jqj.js → scan-cursor-bsrfrxm6.js} +7 -7
- package/dist/cli/{schema-g44y7s0w.js → schema-s6yh3rst.js} +1 -1
- package/dist/cli/{scope-persistence-0n2cv4kn.js → scope-persistence-f77w4jrx.js} +8 -8
- package/dist/cli/{skill-generator-0gmpxwgk.js → skill-generator-jr7c8fcg.js} +13 -13
- package/dist/cli/{telemetry-5deacb7w.js → telemetry-52pw5xac.js} +1 -1
- package/dist/cli/{worktree-collision-ownership-9v8e0dz7.js → worktree-collision-ownership-p31zm5k0.js} +3 -3
- package/dist/cli/{worktree-isolation-pcr5zv0c.js → worktree-isolation-ka20b0rm.js} +28 -28
- package/dist/commands/close.d.ts +1 -0
- package/dist/hooks/guardrails/audit-log.d.ts +70 -19
- package/dist/hooks/guardrails/helpers.d.ts +16 -1
- package/dist/hooks/guardrails/shell-audit-store.d.ts +211 -0
- package/dist/hooks/guardrails/tool-before.d.ts +0 -2
- package/dist/index.js +337 -317
- package/dist/observability/catalog.d.ts +7 -5
- package/dist/services/guardrail-log-service.d.ts +4 -0
- package/dist/telemetry.d.ts +22 -1
- package/package.json +2 -1
- package/dist/cli/index-ggs716fj.js +0 -110
|
@@ -11,10 +11,13 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
init_schema,
|
|
13
13
|
stripKnownSwarmPrefix
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-fkqfcv14.js";
|
|
15
15
|
import {
|
|
16
16
|
init_constants
|
|
17
17
|
} from "./index-cpg98bh1.js";
|
|
18
|
+
import {
|
|
19
|
+
telemetry
|
|
20
|
+
} from "./index-aw492hpn.js";
|
|
18
21
|
import {
|
|
19
22
|
init_logger,
|
|
20
23
|
warn
|
|
@@ -2588,20 +2591,704 @@ function isInDeclaredScope(filePath, scopeEntries, cwd) {
|
|
|
2588
2591
|
function redactShellCommand(cmd) {
|
|
2589
2592
|
if (typeof cmd !== "string")
|
|
2590
2593
|
return "";
|
|
2591
|
-
let out = cmd.replace(/\/home\/[^/\s"']+/g, "~").replace(/[A-Za-z]:\\Users\\[^\\\s"']+/
|
|
2592
|
-
out = out.replace(
|
|
2594
|
+
let out = cmd.replace(/\/home\/[^/\s"']+/g, "~").replace(/[A-Za-z]:\\Users\\[^\\\s"']+/gi, "~").replace(/\/Users\/[^/\s"']+/g, "~").replace(/\\\\[^\\\s"']+\\[^\\\s"']+\\[^\\\s"']+(?:\\[^\s"']*)?/g, "~");
|
|
2595
|
+
out = out.replace(/([a-zA-Z][a-zA-Z0-9+.-]{0,32}:\/\/[^\s"'@/]+):([^@\s"'/]+)@/g, "$1:[REDACTED]@");
|
|
2596
|
+
const VALUE = String.raw`(?:"[^"]*"|'[^']*'|\S+)`;
|
|
2597
|
+
out = out.replace(new RegExp(String.raw`\b([A-Z_]{0,64}(?:TOKEN|SECRET|PASSWORD|PASSWD|API[_]?KEY|APIKEY|AUTH|CREDENTIAL|PRIVATE[_]?KEY|ACCESS[_]?KEY|_KEY)[A-Z_0-9]{0,64})\s*=\s*` + VALUE, "gi"), "$1=[REDACTED]");
|
|
2598
|
+
out = out.replace(new RegExp(String.raw`(\$env:[A-Za-z_][A-Za-z_0-9]{0,64}(?:TOKEN|SECRET|PASSWORD|PASSWD|API[_]?KEY|APIKEY|AUTH|CREDENTIAL|PRIVATE[_]?KEY|ACCESS[_]?KEY|_KEY)[A-Za-z_0-9]{0,64})(\s*=\s*)` + VALUE, "gi"), "$1=[REDACTED]");
|
|
2599
|
+
out = out.replace(new RegExp(String.raw`\b(set|SET)(\s+"?)([A-Za-z_][A-Za-z_0-9]{0,64}(?:TOKEN|SECRET|PASSWORD|PASSWD|API[_]?KEY|APIKEY|AUTH|CREDENTIAL|PRIVATE[_]?KEY|ACCESS[_]?KEY|_KEY)[A-Za-z_0-9]{0,64})(=?)(\s+)` + VALUE, "gi"), "$1$2$3=[REDACTED]");
|
|
2593
2600
|
out = out.replace(/--([a-zA-Z-]*(?:token|secret|password|passwd|api[_-]?key|apikey|auth|credential|private[_-]?key|access[_-]?key)[a-zA-Z-]*)=(\S+)/gi, "--$1=[REDACTED]");
|
|
2594
2601
|
out = out.replace(/(--[a-zA-Z-]*(?:token|secret|password|passwd|api[_-]?key|apikey|auth|credential|private[_-]?key|access[_-]?key)[a-zA-Z-]*)(\s+)(?!--)(\S+)/gi, "$1$2[REDACTED]");
|
|
2595
2602
|
out = out.replace(/\b(Bearer|Basic)\s+[A-Za-z0-9+/=._-]{4,}/gi, "$1 [REDACTED]");
|
|
2596
2603
|
out = out.replace(/(-H\s+['"]?(?:Authorization|X-API-Key|X-Auth-Token|[A-Za-z][A-Za-z-]*-(?:key|token|secret|auth|credential)):\s*)([^'">\s][^'">\n]*)(['"]?)/gi, "$1[REDACTED]$3");
|
|
2604
|
+
out = out.replace(/\bsk-[A-Za-z0-9_-]{20,}/g, "[REDACTED:openai]").replace(/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g, "[REDACTED:github]").replace(/\bgithub_pat_[A-Za-z0-9_]{22,}/g, "[REDACTED:github]").replace(/\bAKIA[0-9A-Z]{16}\b/g, "[REDACTED:aws]").replace(/\bxox[baprs]-[A-Za-z0-9-]{10,}/g, "[REDACTED:slack]").replace(/\bAIza[A-Za-z0-9_-]{30,}/g, "[REDACTED:google]");
|
|
2605
|
+
out = out.replace(/[A-Za-z0-9+/]{80,}={0,2}/g, (run) => /[A-Z]/.test(run) && /[a-z]/.test(run) && /[0-9]/.test(run) ? "[REDACTED:base64]" : run);
|
|
2597
2606
|
return out;
|
|
2598
2607
|
}
|
|
2599
2608
|
|
|
2609
|
+
// src/hooks/guardrails/shell-audit-store.ts
|
|
2610
|
+
import * as fs from "fs";
|
|
2611
|
+
import * as path2 from "path";
|
|
2612
|
+
import { StringDecoder } from "string_decoder";
|
|
2613
|
+
init_logger();
|
|
2614
|
+
var SHELL_AUDIT_LIMITS = {
|
|
2615
|
+
activeMaxBytes: 1024 * 1024,
|
|
2616
|
+
securityMaxEntries: 4000,
|
|
2617
|
+
allowedMaxEntries: 2000,
|
|
2618
|
+
allowedAgeMaxMs: 72 * 60 * 60 * 1000,
|
|
2619
|
+
compactMaxBytes: 256 * 1024,
|
|
2620
|
+
readMaxBytes: 256 * 1024,
|
|
2621
|
+
checkInterval: 25,
|
|
2622
|
+
warnCooldownMs: 60000,
|
|
2623
|
+
headerMaxBytes: 16 * 1024,
|
|
2624
|
+
maxLineBytes: 64 * 1024,
|
|
2625
|
+
maxCommandChars: 4096,
|
|
2626
|
+
maxReasonChars: 1024,
|
|
2627
|
+
migrationMaxBytes: 8 * 1024 * 1024
|
|
2628
|
+
};
|
|
2629
|
+
function decisionClassOf(event) {
|
|
2630
|
+
return typeof event.type === "string" && event.type.length > 0 ? "security" : "allowed";
|
|
2631
|
+
}
|
|
2632
|
+
function decisionDiscriminator(event) {
|
|
2633
|
+
if (typeof event.type === "string" && event.type.length > 0) {
|
|
2634
|
+
return event.type.slice(0, 64);
|
|
2635
|
+
}
|
|
2636
|
+
return "shell";
|
|
2637
|
+
}
|
|
2638
|
+
function decisionTimestamp(event) {
|
|
2639
|
+
if (typeof event.ts !== "string")
|
|
2640
|
+
return null;
|
|
2641
|
+
const ts = Date.parse(event.ts);
|
|
2642
|
+
return Number.isNaN(ts) ? null : event.ts;
|
|
2643
|
+
}
|
|
2644
|
+
var MANIFEST_TYPE = "swarm-shell-audit-manifest";
|
|
2645
|
+
var MANIFEST_SCHEMA = 1;
|
|
2646
|
+
var BY_TYPE_MAX_KEYS = 16;
|
|
2647
|
+
var BY_TYPE_OTHER = "__other__";
|
|
2648
|
+
function emptyFolded() {
|
|
2649
|
+
return {
|
|
2650
|
+
totalDecisions: 0,
|
|
2651
|
+
byType: {},
|
|
2652
|
+
corrupt: 0,
|
|
2653
|
+
dropped: 0,
|
|
2654
|
+
oldestTimestamp: null,
|
|
2655
|
+
newestTimestamp: null
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
function emptyManifest() {
|
|
2659
|
+
return {
|
|
2660
|
+
v: 1,
|
|
2661
|
+
type: MANIFEST_TYPE,
|
|
2662
|
+
schemaVersion: MANIFEST_SCHEMA,
|
|
2663
|
+
folded: emptyFolded(),
|
|
2664
|
+
updatedAt: new Date().toISOString()
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
function parseManifestLine(line) {
|
|
2668
|
+
const trimmed = line.trim();
|
|
2669
|
+
if (!trimmed)
|
|
2670
|
+
return null;
|
|
2671
|
+
try {
|
|
2672
|
+
const obj = JSON.parse(trimmed);
|
|
2673
|
+
if (typeof obj !== "object" || obj === null)
|
|
2674
|
+
return null;
|
|
2675
|
+
const rec = obj;
|
|
2676
|
+
if (rec.type !== MANIFEST_TYPE || rec.v !== 1 || rec.schemaVersion !== MANIFEST_SCHEMA || typeof rec.folded !== "object" || rec.folded === null) {
|
|
2677
|
+
return null;
|
|
2678
|
+
}
|
|
2679
|
+
const f = rec.folded;
|
|
2680
|
+
const manifest = emptyManifest();
|
|
2681
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
2682
|
+
manifest.folded.totalDecisions = num(f.totalDecisions);
|
|
2683
|
+
manifest.folded.corrupt = num(f.corrupt);
|
|
2684
|
+
manifest.folded.dropped = num(f.dropped);
|
|
2685
|
+
if (typeof f.byType === "object" && f.byType !== null) {
|
|
2686
|
+
for (const [k, v] of Object.entries(f.byType)) {
|
|
2687
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= 0) {
|
|
2688
|
+
manifest.folded.byType[k] = v;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
if (typeof f.oldestTimestamp === "string") {
|
|
2693
|
+
manifest.folded.oldestTimestamp = f.oldestTimestamp;
|
|
2694
|
+
}
|
|
2695
|
+
if (typeof f.newestTimestamp === "string") {
|
|
2696
|
+
manifest.folded.newestTimestamp = f.newestTimestamp;
|
|
2697
|
+
}
|
|
2698
|
+
manifest.updatedAt = typeof rec.updatedAt === "string" ? rec.updatedAt : new Date().toISOString();
|
|
2699
|
+
return manifest;
|
|
2700
|
+
} catch {
|
|
2701
|
+
return null;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
var _internals = {
|
|
2705
|
+
appendFileSync: fs.appendFileSync,
|
|
2706
|
+
readFileSync: fs.readFileSync,
|
|
2707
|
+
existsSync: fs.existsSync,
|
|
2708
|
+
mkdirSync: fs.mkdirSync,
|
|
2709
|
+
statSync: fs.statSync,
|
|
2710
|
+
renameSync: fs.renameSync,
|
|
2711
|
+
writeFileSync: fs.writeFileSync,
|
|
2712
|
+
unlinkSync: fs.unlinkSync,
|
|
2713
|
+
openSync: fs.openSync,
|
|
2714
|
+
closeSync: fs.closeSync,
|
|
2715
|
+
readSync: fs.readSync,
|
|
2716
|
+
now: () => Date.now(),
|
|
2717
|
+
limits: SHELL_AUDIT_LIMITS,
|
|
2718
|
+
emitHealth: emitShellAuditHealth
|
|
2719
|
+
};
|
|
2720
|
+
function shellAuditFilePath(directory) {
|
|
2721
|
+
return path2.join(directory, ".swarm", "session", "shell-audit.jsonl");
|
|
2722
|
+
}
|
|
2723
|
+
function lockFilePath(directory) {
|
|
2724
|
+
return path2.join(directory, ".swarm", "session", "shell-audit.lock");
|
|
2725
|
+
}
|
|
2726
|
+
function tmpPathFor(finalPath) {
|
|
2727
|
+
return `${finalPath}.${process.pid}.tmp`;
|
|
2728
|
+
}
|
|
2729
|
+
function readBoundedChunk(filePath, maxBytes, offset = 0) {
|
|
2730
|
+
try {
|
|
2731
|
+
if (!_internals.existsSync(filePath)) {
|
|
2732
|
+
return { text: "", truncated: false };
|
|
2733
|
+
}
|
|
2734
|
+
const fd = _internals.openSync(filePath, "r");
|
|
2735
|
+
try {
|
|
2736
|
+
const size = _internals.statSync(filePath).size;
|
|
2737
|
+
const readable = Math.max(0, size - offset);
|
|
2738
|
+
const truncated = readable > maxBytes;
|
|
2739
|
+
const len = Math.min(readable, maxBytes);
|
|
2740
|
+
const buf = Buffer.alloc(len);
|
|
2741
|
+
let read = 0;
|
|
2742
|
+
while (read < len) {
|
|
2743
|
+
const n = _internals.readSync(fd, buf, read, len - read, offset + read);
|
|
2744
|
+
if (n <= 0)
|
|
2745
|
+
break;
|
|
2746
|
+
read += n;
|
|
2747
|
+
}
|
|
2748
|
+
return { text: buf.toString("utf-8", 0, read), truncated };
|
|
2749
|
+
} finally {
|
|
2750
|
+
_internals.closeSync(fd);
|
|
2751
|
+
}
|
|
2752
|
+
} catch {
|
|
2753
|
+
warnThrottled("bounded read failed (transient I/O)");
|
|
2754
|
+
return { text: "", truncated: false };
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
function fileSizeOrZero(filePath) {
|
|
2758
|
+
try {
|
|
2759
|
+
if (!_internals.existsSync(filePath))
|
|
2760
|
+
return 0;
|
|
2761
|
+
return _internals.statSync(filePath).size;
|
|
2762
|
+
} catch {
|
|
2763
|
+
return 0;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
function foldLineInto(folded, event) {
|
|
2767
|
+
folded.totalDecisions += 1;
|
|
2768
|
+
const key = decisionDiscriminator(event);
|
|
2769
|
+
const keys = Object.keys(folded.byType);
|
|
2770
|
+
if (folded.byType[key] !== undefined || keys.length < BY_TYPE_MAX_KEYS) {
|
|
2771
|
+
folded.byType[key] = (folded.byType[key] ?? 0) + 1;
|
|
2772
|
+
} else {
|
|
2773
|
+
folded.byType[BY_TYPE_OTHER] = (folded.byType[BY_TYPE_OTHER] ?? 0) + 1;
|
|
2774
|
+
}
|
|
2775
|
+
const ts = decisionTimestamp(event);
|
|
2776
|
+
if (ts !== null) {
|
|
2777
|
+
if (folded.oldestTimestamp === null || ts < folded.oldestTimestamp) {
|
|
2778
|
+
folded.oldestTimestamp = ts;
|
|
2779
|
+
}
|
|
2780
|
+
if (folded.newestTimestamp === null || ts > folded.newestTimestamp) {
|
|
2781
|
+
folded.newestTimestamp = ts;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
function cloneFolded(folded) {
|
|
2786
|
+
return {
|
|
2787
|
+
totalDecisions: folded.totalDecisions,
|
|
2788
|
+
byType: { ...folded.byType },
|
|
2789
|
+
corrupt: folded.corrupt,
|
|
2790
|
+
dropped: folded.dropped,
|
|
2791
|
+
oldestTimestamp: folded.oldestTimestamp,
|
|
2792
|
+
newestTimestamp: folded.newestTimestamp
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
function atomicReplace(directory, content) {
|
|
2796
|
+
const lines = content.split(`
|
|
2797
|
+
`);
|
|
2798
|
+
if (lines.length < 2 || lines[lines.length - 1] !== "") {
|
|
2799
|
+
throw new Error("shell-audit atomic replace failed (framing)");
|
|
2800
|
+
}
|
|
2801
|
+
if (parseManifestLine(lines[0] ?? "") === null) {
|
|
2802
|
+
throw new Error("shell-audit atomic replace failed (manifest)");
|
|
2803
|
+
}
|
|
2804
|
+
for (let i = 1;i < lines.length - 1; i += 1) {
|
|
2805
|
+
const line = lines[i];
|
|
2806
|
+
if (line.trim() === "")
|
|
2807
|
+
continue;
|
|
2808
|
+
JSON.parse(line);
|
|
2809
|
+
}
|
|
2810
|
+
const finalPath = shellAuditFilePath(directory);
|
|
2811
|
+
const tmpPath = tmpPathFor(finalPath);
|
|
2812
|
+
try {
|
|
2813
|
+
if (_internals.existsSync(tmpPath)) {
|
|
2814
|
+
try {
|
|
2815
|
+
_internals.unlinkSync(tmpPath);
|
|
2816
|
+
} catch {}
|
|
2817
|
+
}
|
|
2818
|
+
_internals.writeFileSync(tmpPath, content, "utf-8");
|
|
2819
|
+
if (_internals.readFileSync(tmpPath, "utf-8") !== content) {
|
|
2820
|
+
throw new Error("tmp verify mismatch");
|
|
2821
|
+
}
|
|
2822
|
+
renameSyncWithRetry(tmpPath, finalPath);
|
|
2823
|
+
} catch {
|
|
2824
|
+
try {
|
|
2825
|
+
if (_internals.existsSync(tmpPath)) {
|
|
2826
|
+
_internals.unlinkSync(tmpPath);
|
|
2827
|
+
}
|
|
2828
|
+
} catch {}
|
|
2829
|
+
throw new Error("shell-audit atomic replace failed");
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
var LOCK_RETRY_ATTEMPTS = 20;
|
|
2833
|
+
var LOCK_RETRY_DELAY_MS = 5;
|
|
2834
|
+
var LOCK_STALE_MS = 5 * 60000;
|
|
2835
|
+
var sleepScratch = new Int32Array(new SharedArrayBuffer(4));
|
|
2836
|
+
function syncSleep(ms) {
|
|
2837
|
+
try {
|
|
2838
|
+
Atomics.wait(sleepScratch, 0, 0, ms);
|
|
2839
|
+
} catch {
|
|
2840
|
+
const start = _internals.now();
|
|
2841
|
+
while (_internals.now() - start < ms) {}
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
var RENAME_RETRY_DELAYS_MS = [10, 25, 50, 100, 200];
|
|
2845
|
+
var RETRYABLE_RENAME_CODES = new Set(["EPERM", "EBUSY", "EACCES", "EEXIST"]);
|
|
2846
|
+
function renameSyncWithRetry(fromPath, toPath) {
|
|
2847
|
+
let lastError;
|
|
2848
|
+
for (let attempt = 0;attempt <= RENAME_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
2849
|
+
try {
|
|
2850
|
+
_internals.renameSync(fromPath, toPath);
|
|
2851
|
+
return;
|
|
2852
|
+
} catch (err) {
|
|
2853
|
+
lastError = err;
|
|
2854
|
+
const code = err?.code;
|
|
2855
|
+
if (!code || !RETRYABLE_RENAME_CODES.has(code))
|
|
2856
|
+
throw err;
|
|
2857
|
+
if (attempt < RENAME_RETRY_DELAYS_MS.length) {
|
|
2858
|
+
syncSleep(RENAME_RETRY_DELAYS_MS[attempt]);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
throw lastError;
|
|
2863
|
+
}
|
|
2864
|
+
function tryLockOnce(directory) {
|
|
2865
|
+
const lockPath = lockFilePath(directory);
|
|
2866
|
+
try {
|
|
2867
|
+
_internals.mkdirSync(path2.dirname(lockPath), { recursive: true });
|
|
2868
|
+
const fd = _internals.openSync(lockPath, "wx");
|
|
2869
|
+
_internals.closeSync(fd);
|
|
2870
|
+
return true;
|
|
2871
|
+
} catch {
|
|
2872
|
+
try {
|
|
2873
|
+
const age = _internals.now() - _internals.statSync(lockPath).mtimeMs;
|
|
2874
|
+
if (age > LOCK_STALE_MS) {
|
|
2875
|
+
try {
|
|
2876
|
+
_internals.unlinkSync(lockPath);
|
|
2877
|
+
} catch {}
|
|
2878
|
+
try {
|
|
2879
|
+
const fd = _internals.openSync(lockPath, "wx");
|
|
2880
|
+
_internals.closeSync(fd);
|
|
2881
|
+
return true;
|
|
2882
|
+
} catch {
|
|
2883
|
+
return false;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
} catch {}
|
|
2887
|
+
return false;
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
function withShellAuditStoreLock(directory, fn) {
|
|
2891
|
+
for (let attempt = 0;attempt < LOCK_RETRY_ATTEMPTS; attempt += 1) {
|
|
2892
|
+
if (tryLockOnce(directory)) {
|
|
2893
|
+
try {
|
|
2894
|
+
return fn();
|
|
2895
|
+
} finally {
|
|
2896
|
+
try {
|
|
2897
|
+
_internals.unlinkSync(lockFilePath(directory));
|
|
2898
|
+
} catch {}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
if (attempt < LOCK_RETRY_ATTEMPTS - 1) {
|
|
2902
|
+
syncSleep(LOCK_RETRY_DELAY_MS);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
return null;
|
|
2906
|
+
}
|
|
2907
|
+
var _lastWarnAt = 0;
|
|
2908
|
+
function warnThrottled(message) {
|
|
2909
|
+
const now = _internals.now();
|
|
2910
|
+
if (now - _lastWarnAt < _internals.limits.warnCooldownMs)
|
|
2911
|
+
return;
|
|
2912
|
+
_lastWarnAt = now;
|
|
2913
|
+
warn(`shell-audit: ${message}`);
|
|
2914
|
+
}
|
|
2915
|
+
var EMPTY_VIEW = {
|
|
2916
|
+
manifest: null,
|
|
2917
|
+
lines: [],
|
|
2918
|
+
corruptLines: 0,
|
|
2919
|
+
prefixFolded: null
|
|
2920
|
+
};
|
|
2921
|
+
function streamLegacyStore(filePath) {
|
|
2922
|
+
const size = fileSizeOrZero(filePath);
|
|
2923
|
+
const prefixFolded = emptyFolded();
|
|
2924
|
+
const retained = [];
|
|
2925
|
+
let retainedBytes = 0;
|
|
2926
|
+
const windowBudget = _internals.limits.activeMaxBytes + _internals.limits.headerMaxBytes;
|
|
2927
|
+
const chunkBytes = Math.min(512 * 1024, Math.max(1 * 1024, _internals.limits.compactMaxBytes * 2));
|
|
2928
|
+
const ingest = (line) => {
|
|
2929
|
+
if (line.trim() === "")
|
|
2930
|
+
return;
|
|
2931
|
+
let event = null;
|
|
2932
|
+
try {
|
|
2933
|
+
const parsed = JSON.parse(line);
|
|
2934
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
2935
|
+
event = parsed;
|
|
2936
|
+
}
|
|
2937
|
+
} catch {}
|
|
2938
|
+
if (event === null) {
|
|
2939
|
+
prefixFolded.corrupt += 1;
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
const lineBytes = Buffer.byteLength(line) + 1;
|
|
2943
|
+
if (retainedBytes + lineBytes > windowBudget && retained.length > 0) {
|
|
2944
|
+
const oldest = retained.shift();
|
|
2945
|
+
retainedBytes -= Buffer.byteLength(oldest.line) + 1;
|
|
2946
|
+
foldLineInto(prefixFolded, oldest.event);
|
|
2947
|
+
}
|
|
2948
|
+
retained.push({ line, event });
|
|
2949
|
+
retainedBytes += lineBytes;
|
|
2950
|
+
};
|
|
2951
|
+
let offset = 0;
|
|
2952
|
+
let carry = "";
|
|
2953
|
+
const decoder = new StringDecoder("utf-8");
|
|
2954
|
+
const fd = _internals.openSync(filePath, "r");
|
|
2955
|
+
try {
|
|
2956
|
+
const buf = Buffer.alloc(chunkBytes);
|
|
2957
|
+
for (;; ) {
|
|
2958
|
+
const n = _internals.readSync(fd, buf, 0, chunkBytes, offset);
|
|
2959
|
+
if (n <= 0)
|
|
2960
|
+
break;
|
|
2961
|
+
offset += n;
|
|
2962
|
+
const decoded = decoder.write(buf.subarray(0, n));
|
|
2963
|
+
const parts = `${carry}${decoded}`.split(`
|
|
2964
|
+
`);
|
|
2965
|
+
carry = parts.pop() ?? "";
|
|
2966
|
+
for (const line of parts)
|
|
2967
|
+
ingest(line);
|
|
2968
|
+
if (offset >= size)
|
|
2969
|
+
break;
|
|
2970
|
+
}
|
|
2971
|
+
} finally {
|
|
2972
|
+
_internals.closeSync(fd);
|
|
2973
|
+
}
|
|
2974
|
+
const residue = decoder.end();
|
|
2975
|
+
if (residue !== "")
|
|
2976
|
+
carry += residue;
|
|
2977
|
+
if (carry.trim() !== "")
|
|
2978
|
+
ingest(carry);
|
|
2979
|
+
return { manifest: null, lines: retained, corruptLines: 0, prefixFolded };
|
|
2980
|
+
}
|
|
2981
|
+
function readStoreFull(directory) {
|
|
2982
|
+
const filePath = shellAuditFilePath(directory);
|
|
2983
|
+
if (!_internals.existsSync(filePath)) {
|
|
2984
|
+
return EMPTY_VIEW;
|
|
2985
|
+
}
|
|
2986
|
+
if (fileSizeOrZero(filePath) > _internals.limits.migrationMaxBytes) {
|
|
2987
|
+
return streamLegacyStore(filePath);
|
|
2988
|
+
}
|
|
2989
|
+
let text;
|
|
2990
|
+
try {
|
|
2991
|
+
text = _internals.readFileSync(filePath, "utf-8");
|
|
2992
|
+
} catch {
|
|
2993
|
+
return EMPTY_VIEW;
|
|
2994
|
+
}
|
|
2995
|
+
const rawLines = text.split(`
|
|
2996
|
+
`);
|
|
2997
|
+
const lines = [];
|
|
2998
|
+
let corruptLines = 0;
|
|
2999
|
+
let manifest = null;
|
|
3000
|
+
for (let i = 0;i < rawLines.length; i += 1) {
|
|
3001
|
+
const line = rawLines[i];
|
|
3002
|
+
if (line.trim() === "")
|
|
3003
|
+
continue;
|
|
3004
|
+
if (i === 0) {
|
|
3005
|
+
manifest = parseManifestLine(line);
|
|
3006
|
+
if (manifest)
|
|
3007
|
+
continue;
|
|
3008
|
+
}
|
|
3009
|
+
try {
|
|
3010
|
+
const parsed = JSON.parse(line);
|
|
3011
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
3012
|
+
lines.push({ line, event: parsed });
|
|
3013
|
+
} else {
|
|
3014
|
+
corruptLines += 1;
|
|
3015
|
+
}
|
|
3016
|
+
} catch {
|
|
3017
|
+
corruptLines += 1;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
return { manifest, lines, corruptLines, prefixFolded: null };
|
|
3021
|
+
}
|
|
3022
|
+
function coverageFor(fileExists, lineCount, truncated) {
|
|
3023
|
+
if (!fileExists || lineCount === 0)
|
|
3024
|
+
return "empty";
|
|
3025
|
+
return truncated ? "truncated" : "complete";
|
|
3026
|
+
}
|
|
3027
|
+
function readShellAuditTail(directory, maxBytes) {
|
|
3028
|
+
const bound = Math.max(1024, Math.min(maxBytes ?? _internals.limits.readMaxBytes, _internals.limits.readMaxBytes));
|
|
3029
|
+
const filePath = shellAuditFilePath(directory);
|
|
3030
|
+
if (!_internals.existsSync(filePath)) {
|
|
3031
|
+
return { text: "", truncated: false, coverage: "empty" };
|
|
3032
|
+
}
|
|
3033
|
+
let size = fileSizeOrZero(filePath);
|
|
3034
|
+
let truncated = size > bound;
|
|
3035
|
+
let offset = truncated ? Math.max(0, size - bound) : 0;
|
|
3036
|
+
let text = readBoundedChunk(filePath, bound, offset).text;
|
|
3037
|
+
if (truncated && text === "") {
|
|
3038
|
+
size = fileSizeOrZero(filePath);
|
|
3039
|
+
truncated = size > bound;
|
|
3040
|
+
offset = truncated ? Math.max(0, size - bound) : 0;
|
|
3041
|
+
text = readBoundedChunk(filePath, bound, offset).text;
|
|
3042
|
+
}
|
|
3043
|
+
if (truncated) {
|
|
3044
|
+
const firstNewline = text.indexOf(`
|
|
3045
|
+
`);
|
|
3046
|
+
text = firstNewline === -1 ? "" : text.slice(firstNewline + 1);
|
|
3047
|
+
}
|
|
3048
|
+
const lines = text.split(`
|
|
3049
|
+
`);
|
|
3050
|
+
const start = parseManifestLine(lines[0] ?? "") !== null ? 1 : 0;
|
|
3051
|
+
if (start === 1) {
|
|
3052
|
+
text = lines.slice(1).join(`
|
|
3053
|
+
`);
|
|
3054
|
+
}
|
|
3055
|
+
const decisionLineCount = text.split(`
|
|
3056
|
+
`).filter((l) => l.trim() !== "").length;
|
|
3057
|
+
const coverage = start === 1 ? truncated ? "truncated" : "complete" : coverageFor(true, decisionLineCount, truncated);
|
|
3058
|
+
return { text, truncated, coverage };
|
|
3059
|
+
}
|
|
3060
|
+
function getShellAuditFoldedSummary(directory) {
|
|
3061
|
+
const filePath = shellAuditFilePath(directory);
|
|
3062
|
+
if (!_internals.existsSync(filePath))
|
|
3063
|
+
return null;
|
|
3064
|
+
const head = readBoundedChunk(filePath, _internals.limits.headerMaxBytes + 64);
|
|
3065
|
+
const manifest = parseManifestLine(head.text.split(`
|
|
3066
|
+
`)[0] ?? "");
|
|
3067
|
+
return manifest ? manifest.folded : null;
|
|
3068
|
+
}
|
|
3069
|
+
function foldPass(directory, trigger, forceFull) {
|
|
3070
|
+
const filePath = shellAuditFilePath(directory);
|
|
3071
|
+
if (!_internals.existsSync(filePath))
|
|
3072
|
+
return;
|
|
3073
|
+
const view = readStoreFull(directory);
|
|
3074
|
+
if (view.manifest === null && view.lines.length === 0 && view.corruptLines === 0 && view.prefixFolded === null) {
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
const folded = view.manifest ? cloneFolded(view.manifest.folded) : emptyFolded();
|
|
3078
|
+
if (view.prefixFolded !== null) {
|
|
3079
|
+
folded.totalDecisions += view.prefixFolded.totalDecisions;
|
|
3080
|
+
for (const [k, v] of Object.entries(view.prefixFolded.byType)) {
|
|
3081
|
+
folded.byType[k] = (folded.byType[k] ?? 0) + v;
|
|
3082
|
+
}
|
|
3083
|
+
folded.corrupt += view.prefixFolded.corrupt;
|
|
3084
|
+
folded.dropped += view.prefixFolded.dropped;
|
|
3085
|
+
if (view.prefixFolded.oldestTimestamp !== null && (folded.oldestTimestamp === null || view.prefixFolded.oldestTimestamp < folded.oldestTimestamp)) {
|
|
3086
|
+
folded.oldestTimestamp = view.prefixFolded.oldestTimestamp;
|
|
3087
|
+
}
|
|
3088
|
+
if (view.prefixFolded.newestTimestamp !== null && (folded.newestTimestamp === null || view.prefixFolded.newestTimestamp > folded.newestTimestamp)) {
|
|
3089
|
+
folded.newestTimestamp = view.prefixFolded.newestTimestamp;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
const now = _internals.now();
|
|
3093
|
+
const agePruned = new Set;
|
|
3094
|
+
const kept = [];
|
|
3095
|
+
for (const entry of view.lines) {
|
|
3096
|
+
if (entry.event === null) {
|
|
3097
|
+
agePruned.add(entry);
|
|
3098
|
+
continue;
|
|
3099
|
+
}
|
|
3100
|
+
if (decisionClassOf(entry.event) === "security") {
|
|
3101
|
+
kept.push(entry);
|
|
3102
|
+
continue;
|
|
3103
|
+
}
|
|
3104
|
+
const ts = decisionTimestamp(entry.event);
|
|
3105
|
+
if (ts !== null && now - Date.parse(ts) > _internals.limits.allowedAgeMaxMs) {
|
|
3106
|
+
agePruned.add(entry);
|
|
3107
|
+
} else {
|
|
3108
|
+
kept.push(entry);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
const retained = [];
|
|
3112
|
+
let securityCount = 0;
|
|
3113
|
+
let allowedCount = 0;
|
|
3114
|
+
let bytes = Buffer.byteLength(JSON.stringify(view.manifest ?? emptyManifest())) + 1;
|
|
3115
|
+
for (let i = kept.length - 1;i >= 0; i -= 1) {
|
|
3116
|
+
const entry = kept[i];
|
|
3117
|
+
const lineBytes = Buffer.byteLength(entry.line) + 1;
|
|
3118
|
+
if (bytes + lineBytes > _internals.limits.activeMaxBytes) {
|
|
3119
|
+
break;
|
|
3120
|
+
}
|
|
3121
|
+
const cls = decisionClassOf(entry.event);
|
|
3122
|
+
if (cls === "security") {
|
|
3123
|
+
if (securityCount >= _internals.limits.securityMaxEntries) {
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
securityCount += 1;
|
|
3127
|
+
} else {
|
|
3128
|
+
if (allowedCount >= _internals.limits.allowedMaxEntries) {
|
|
3129
|
+
continue;
|
|
3130
|
+
}
|
|
3131
|
+
allowedCount += 1;
|
|
3132
|
+
}
|
|
3133
|
+
retained.push(entry);
|
|
3134
|
+
bytes += lineBytes;
|
|
3135
|
+
}
|
|
3136
|
+
retained.reverse();
|
|
3137
|
+
const retainedSet = new Set(retained);
|
|
3138
|
+
const candidates = [];
|
|
3139
|
+
for (const entry of view.lines) {
|
|
3140
|
+
if (entry.event === null) {
|
|
3141
|
+
candidates.push(entry);
|
|
3142
|
+
} else if (!retainedSet.has(entry)) {
|
|
3143
|
+
candidates.push(entry);
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
const toFold = new Set;
|
|
3147
|
+
let foldedBytes = 0;
|
|
3148
|
+
for (const cand of candidates) {
|
|
3149
|
+
const cb = Buffer.byteLength(cand.line) + 1;
|
|
3150
|
+
if (!forceFull && foldedBytes + cb > _internals.limits.compactMaxBytes && toFold.size > 0) {
|
|
3151
|
+
break;
|
|
3152
|
+
}
|
|
3153
|
+
toFold.add(cand);
|
|
3154
|
+
foldedBytes += cb;
|
|
3155
|
+
}
|
|
3156
|
+
let dropped = 0;
|
|
3157
|
+
for (const cand of toFold) {
|
|
3158
|
+
if (cand.event === null) {
|
|
3159
|
+
folded.corrupt += 1;
|
|
3160
|
+
} else {
|
|
3161
|
+
foldLineInto(folded, cand.event);
|
|
3162
|
+
if (agePruned.has(cand))
|
|
3163
|
+
dropped += 1;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
folded.dropped += dropped;
|
|
3167
|
+
folded.corrupt += view.corruptLines;
|
|
3168
|
+
const finalRetained = [];
|
|
3169
|
+
for (const entry of view.lines) {
|
|
3170
|
+
if (!toFold.has(entry))
|
|
3171
|
+
finalRetained.push(entry);
|
|
3172
|
+
}
|
|
3173
|
+
const manifest = {
|
|
3174
|
+
...emptyManifest(),
|
|
3175
|
+
folded,
|
|
3176
|
+
updatedAt: new Date().toISOString()
|
|
3177
|
+
};
|
|
3178
|
+
const outLines = [`${JSON.stringify(manifest)}`];
|
|
3179
|
+
for (const entry of finalRetained)
|
|
3180
|
+
outLines.push(entry.line);
|
|
3181
|
+
atomicReplace(directory, outLines.length > 1 ? `${outLines.join(`
|
|
3182
|
+
`)}
|
|
3183
|
+
` : `${outLines[0]}
|
|
3184
|
+
`);
|
|
3185
|
+
emitHealth(directory, {
|
|
3186
|
+
trigger,
|
|
3187
|
+
accepted: folded.totalDecisions + finalRetained.length,
|
|
3188
|
+
compacted: toFold.size - dropped,
|
|
3189
|
+
retained: finalRetained.length,
|
|
3190
|
+
dropped,
|
|
3191
|
+
corrupt: folded.corrupt,
|
|
3192
|
+
oldest: folded.oldestTimestamp,
|
|
3193
|
+
newest: folded.newestTimestamp,
|
|
3194
|
+
bytes: fileSizeOrZero(filePath),
|
|
3195
|
+
limitBytes: _internals.limits.activeMaxBytes
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
function finalizeShellAuditForClose(directory, options) {
|
|
3199
|
+
try {
|
|
3200
|
+
const filePath = shellAuditFilePath(directory);
|
|
3201
|
+
if (!_internals.existsSync(filePath))
|
|
3202
|
+
return;
|
|
3203
|
+
withShellAuditStoreLock(directory, () => {
|
|
3204
|
+
let prev = Number.POSITIVE_INFINITY;
|
|
3205
|
+
for (let pass = 0;pass < 1e4; pass += 1) {
|
|
3206
|
+
const view = readStoreFull(directory);
|
|
3207
|
+
if (view.manifest !== null)
|
|
3208
|
+
break;
|
|
3209
|
+
if (view.lines.length === 0)
|
|
3210
|
+
break;
|
|
3211
|
+
if (view.lines.length >= prev)
|
|
3212
|
+
break;
|
|
3213
|
+
prev = view.lines.length;
|
|
3214
|
+
foldPass(directory, "close", false);
|
|
3215
|
+
}
|
|
3216
|
+
foldPass(directory, "close", true);
|
|
3217
|
+
const transform = options?.lineTransform;
|
|
3218
|
+
if (transform !== undefined) {
|
|
3219
|
+
const view = readStoreFull(directory);
|
|
3220
|
+
if (view.manifest !== null) {
|
|
3221
|
+
const manifest = {
|
|
3222
|
+
...view.manifest,
|
|
3223
|
+
folded: cloneFolded(view.manifest.folded),
|
|
3224
|
+
updatedAt: new Date().toISOString()
|
|
3225
|
+
};
|
|
3226
|
+
const outLines = [`${JSON.stringify(manifest)}`];
|
|
3227
|
+
for (const entry of view.lines)
|
|
3228
|
+
outLines.push(transform(entry.line));
|
|
3229
|
+
try {
|
|
3230
|
+
atomicReplace(directory, `${outLines.join(`
|
|
3231
|
+
`)}
|
|
3232
|
+
`);
|
|
3233
|
+
} catch {
|
|
3234
|
+
warnThrottled("archive re-redaction rewrite failed");
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
return true;
|
|
3239
|
+
});
|
|
3240
|
+
} catch {
|
|
3241
|
+
warnThrottled("finalize failed");
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
function emitShellAuditHealth(directory, payload) {
|
|
3245
|
+
try {
|
|
3246
|
+
telemetry.shellAuditHealth({
|
|
3247
|
+
trigger: payload.trigger,
|
|
3248
|
+
accepted_count: payload.accepted,
|
|
3249
|
+
compacted_count: payload.compacted,
|
|
3250
|
+
retained_count: payload.retained,
|
|
3251
|
+
dropped_count: payload.dropped,
|
|
3252
|
+
corrupt_count: payload.corrupt,
|
|
3253
|
+
oldest_timestamp: payload.oldest,
|
|
3254
|
+
newest_timestamp: payload.newest,
|
|
3255
|
+
bytes: payload.bytes,
|
|
3256
|
+
limit_bytes: payload.limitBytes
|
|
3257
|
+
});
|
|
3258
|
+
} catch {}
|
|
3259
|
+
}
|
|
3260
|
+
function emitHealth(directory, payload) {
|
|
3261
|
+
_internals.emitHealth(directory, payload);
|
|
3262
|
+
}
|
|
3263
|
+
|
|
2600
3264
|
// src/hooks/guardrails/audit-log.ts
|
|
2601
3265
|
init_logger();
|
|
2602
|
-
import
|
|
3266
|
+
import { createHash } from "crypto";
|
|
3267
|
+
import * as path3 from "path";
|
|
3268
|
+
var SHELL_AUDIT_FIELD_CLASSES = Object.freeze({
|
|
3269
|
+
ts: "timestamp",
|
|
3270
|
+
sessionID: "identifier",
|
|
3271
|
+
agent: "identifier",
|
|
3272
|
+
tool: "identifier",
|
|
3273
|
+
type: "decision-type",
|
|
3274
|
+
command: "redacted-command",
|
|
3275
|
+
commandHash: "content-hash",
|
|
3276
|
+
path: "redacted-path",
|
|
3277
|
+
reason: "free-text-redacted",
|
|
3278
|
+
resolvedScope: "redacted-path",
|
|
3279
|
+
declaredScope: "redacted-path",
|
|
3280
|
+
action: "enum",
|
|
3281
|
+
destructiveCategory: "enum",
|
|
3282
|
+
executorMechanism: "enum",
|
|
3283
|
+
skipReason: "enum"
|
|
3284
|
+
});
|
|
3285
|
+
function hashRedactedCommand(redacted) {
|
|
3286
|
+
return createHash("sha256").update(redacted, "utf-8").digest("hex").slice(0, 16);
|
|
3287
|
+
}
|
|
2603
3288
|
function redactPath(filePath) {
|
|
2604
|
-
if (typeof filePath !== "string"
|
|
3289
|
+
if (typeof filePath !== "string")
|
|
3290
|
+
return "";
|
|
3291
|
+
if (filePath.length === 0)
|
|
2605
3292
|
return filePath;
|
|
2606
3293
|
const rawPosixHomeMatch = filePath.match(/^(\/home\/[^/]+)(\/.*)$/);
|
|
2607
3294
|
if (rawPosixHomeMatch) {
|
|
@@ -2611,8 +3298,8 @@ function redactPath(filePath) {
|
|
|
2611
3298
|
if (rawMacHomeMatch) {
|
|
2612
3299
|
return `~${rawMacHomeMatch[2]}`;
|
|
2613
3300
|
}
|
|
2614
|
-
const normalized =
|
|
2615
|
-
const windowsHomeMatch = normalized.match(/^([A-Za-z]:\\
|
|
3301
|
+
const normalized = path3.normalize(filePath);
|
|
3302
|
+
const windowsHomeMatch = normalized.match(/^([A-Za-z]:\\[Uu][Ss][Ee][Rr][Ss]\\[^\\]+)(\\.*)$/i);
|
|
2616
3303
|
if (windowsHomeMatch) {
|
|
2617
3304
|
return `~\\${windowsHomeMatch[2]}`;
|
|
2618
3305
|
}
|
|
@@ -2622,5 +3309,55 @@ function redactPath(filePath) {
|
|
|
2622
3309
|
}
|
|
2623
3310
|
return normalized;
|
|
2624
3311
|
}
|
|
3312
|
+
function redactEmbeddedPaths(text) {
|
|
3313
|
+
return text.replace(/\/home\/[^/\s]+(\/[^\s"'`)]*)?/g, (match) => redactPath(match)).replace(/\/Users\/[^/\s]+(\/[^\s"'`)]*)?/g, (match) => redactPath(match)).replace(/[A-Za-z]:\\Users\\[^\\\s]+(\\[^\s"'`)]*)?/gi, (match) => redactPath(match)).replace(/\\\\[^\\\s]+\\[^\\\s]+\\[^\\\s]+(\\[^\s"'`)]*)?/g, (match) => redactPath(match));
|
|
3314
|
+
}
|
|
3315
|
+
function redactDecisionLineForArchive(line) {
|
|
3316
|
+
let parsed;
|
|
3317
|
+
try {
|
|
3318
|
+
parsed = JSON.parse(line);
|
|
3319
|
+
} catch {
|
|
3320
|
+
return line;
|
|
3321
|
+
}
|
|
3322
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3323
|
+
return line;
|
|
3324
|
+
}
|
|
3325
|
+
const record = parsed;
|
|
3326
|
+
let changed = false;
|
|
3327
|
+
if (typeof record.command === "string") {
|
|
3328
|
+
const redacted = redactShellCommand(record.command);
|
|
3329
|
+
if (redacted !== record.command) {
|
|
3330
|
+
record.command = redacted;
|
|
3331
|
+
changed = true;
|
|
3332
|
+
if (typeof record.commandHash === "string") {
|
|
3333
|
+
record.commandHash = hashRedactedCommand(redacted);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
if (typeof record.path === "string") {
|
|
3338
|
+
const redacted = redactPath(record.path);
|
|
3339
|
+
if (redacted !== record.path) {
|
|
3340
|
+
record.path = redacted;
|
|
3341
|
+
changed = true;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
if (typeof record.reason === "string") {
|
|
3345
|
+
const redacted = redactEmbeddedPaths(record.reason);
|
|
3346
|
+
if (redacted !== record.reason) {
|
|
3347
|
+
record.reason = redacted;
|
|
3348
|
+
changed = true;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
for (const field of ["declaredScope", "resolvedScope"]) {
|
|
3352
|
+
if (typeof record[field] === "string") {
|
|
3353
|
+
const redacted = redactPath(record[field]);
|
|
3354
|
+
if (redacted !== record[field]) {
|
|
3355
|
+
record[field] = redacted;
|
|
3356
|
+
changed = true;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
return changed ? JSON.stringify(record) : line;
|
|
3361
|
+
}
|
|
2625
3362
|
|
|
2626
|
-
export { classifyFile, stableCanonicalStringify, checkFileAuthority, normalizeToolNameLowerCase, isInDeclaredScope, redactShellCommand, redactPath };
|
|
3363
|
+
export { classifyFile, stableCanonicalStringify, checkFileAuthority, normalizeToolNameLowerCase, isInDeclaredScope, redactShellCommand, readShellAuditTail, getShellAuditFoldedSummary, finalizeShellAuditForClose, redactPath, redactDecisionLineForArchive };
|