kifaru 1.0.194 → 1.0.196

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/kifaru-gateway.js CHANGED
@@ -49488,6 +49488,752 @@ The report should be worthy of maximum payout.
49488
49488
  })(AtlantisDiversityEnforcer ||= {});
49489
49489
  });
49490
49490
 
49491
+ // ../core/src/entity-graph.ts
49492
+ class EngagementRegistry {
49493
+ graphs = new Map;
49494
+ register(graph) {
49495
+ this.graphs.set(graph.engagement, graph);
49496
+ }
49497
+ get(engagement) {
49498
+ if (!engagement)
49499
+ return;
49500
+ return this.graphs.get(engagement);
49501
+ }
49502
+ list() {
49503
+ return [...this.graphs.keys()].sort();
49504
+ }
49505
+ }
49506
+ function detectEntityMatches(input2) {
49507
+ const hits = [];
49508
+ const seen = new Set;
49509
+ const consider = (field, value) => {
49510
+ if (!value)
49511
+ return;
49512
+ const key = `${field}::${value.toLowerCase()}`;
49513
+ if (seen.has(key))
49514
+ return;
49515
+ if (containsToken(input2.content, value)) {
49516
+ hits.push({ token: value, field, engagement: input2.graph.engagement });
49517
+ seen.add(key);
49518
+ }
49519
+ };
49520
+ for (const value of input2.graph.domains ?? [])
49521
+ consider("domains", value);
49522
+ for (const value of input2.graph.cloud_accounts ?? [])
49523
+ consider("cloud_accounts", value);
49524
+ for (const value of input2.graph.repos ?? [])
49525
+ consider("repos", value);
49526
+ for (const value of input2.graph.team_names ?? [])
49527
+ consider("team_names", value);
49528
+ for (const value of input2.graph.employee_names ?? [])
49529
+ consider("employee_names", value);
49530
+ for (const value of input2.graph.build_jobs ?? [])
49531
+ consider("build_jobs", value);
49532
+ for (const cidr of input2.graph.cidrs ?? []) {
49533
+ for (const ip of extractIPv4(input2.content)) {
49534
+ if (ipInCidr(ip, cidr)) {
49535
+ const key = `cidrs::${ip}::${cidr}`;
49536
+ if (!seen.has(key)) {
49537
+ hits.push({ token: ip, field: "cidrs", engagement: input2.graph.engagement });
49538
+ seen.add(key);
49539
+ }
49540
+ }
49541
+ }
49542
+ }
49543
+ return hits;
49544
+ }
49545
+ function containsToken(haystack, needle) {
49546
+ if (!needle)
49547
+ return false;
49548
+ const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49549
+ const boundary = "(?:^|[^A-Za-z0-9_-])";
49550
+ const tail = "(?:[^A-Za-z0-9_-]|$)";
49551
+ const re = new RegExp(`${boundary}${escaped}${tail}`, "i");
49552
+ return re.test(haystack);
49553
+ }
49554
+ function extractIPv4(content) {
49555
+ const matches = content.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/g) ?? [];
49556
+ return [...new Set(matches)];
49557
+ }
49558
+ function ipInCidr(ip, cidr) {
49559
+ const [base, prefixStr] = cidr.split("/");
49560
+ if (!base || !prefixStr)
49561
+ return false;
49562
+ const prefix = Number.parseInt(prefixStr, 10);
49563
+ if (!Number.isFinite(prefix) || prefix < 0 || prefix > 32)
49564
+ return false;
49565
+ const ipInt = ipToInt(ip);
49566
+ const baseInt = ipToInt(base);
49567
+ if (ipInt === null || baseInt === null)
49568
+ return false;
49569
+ if (prefix === 0)
49570
+ return true;
49571
+ const mask = prefix === 32 ? 4294967295 : ~0 << 32 - prefix >>> 0;
49572
+ return (ipInt & mask) === (baseInt & mask);
49573
+ }
49574
+ function ipToInt(ip) {
49575
+ const parts = ip.split(".");
49576
+ if (parts.length !== 4)
49577
+ return null;
49578
+ let total = 0;
49579
+ for (const part of parts) {
49580
+ const n = Number.parseInt(part, 10);
49581
+ if (!Number.isFinite(n) || n < 0 || n > 255 || String(n) !== part)
49582
+ return null;
49583
+ total = total << 8 | n;
49584
+ }
49585
+ return total >>> 0;
49586
+ }
49587
+ var init_entity_graph = () => {};
49588
+
49589
+ // ../core/src/pii-detector.ts
49590
+ var PiiDetector;
49591
+ var init_pii_detector = __esm(() => {
49592
+ ((PiiDetector) => {
49593
+ function build() {
49594
+ return (content) => {
49595
+ return findMatches(content).length > 0 ? ["pii"] : [];
49596
+ };
49597
+ }
49598
+ PiiDetector.build = build;
49599
+ function findMatches(content) {
49600
+ const matches = [];
49601
+ const push = (value) => {
49602
+ if (value)
49603
+ matches.push({ value, label: "pii" });
49604
+ };
49605
+ for (const m of content.match(EMAIL_REGEX_G) ?? [])
49606
+ push(m);
49607
+ for (const re of PHONE_REGEXES_G) {
49608
+ for (const m of content.match(re) ?? [])
49609
+ push(m);
49610
+ }
49611
+ for (const m of validSsnMatches(content))
49612
+ push(m);
49613
+ for (const m of validCreditCardMatches(content))
49614
+ push(m);
49615
+ for (const m of validIbanMatches(content))
49616
+ push(m);
49617
+ for (const m of content.match(PASSPORT_REGEX_G) ?? [])
49618
+ push(m);
49619
+ for (const m of content.match(DOB_REGEX_G) ?? [])
49620
+ push(m);
49621
+ for (const m of content.match(NATIONAL_ID_REGEX_G) ?? [])
49622
+ push(m);
49623
+ return matches;
49624
+ }
49625
+ PiiDetector.findMatches = findMatches;
49626
+ const EMAIL_REGEX_G = /\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/gi;
49627
+ const PHONE_REGEXES_G = [
49628
+ /(?<![\d-])\+\d{1,3}[\s.-]?\(?\d{1,4}\)?[\s.-]?\d{2,4}[\s.-]?\d{2,4}[\s.-]?\d{2,4}\b/g,
49629
+ /\(\d{3}\)\s?\d{3}[-.\s]?\d{4}/g,
49630
+ /\b\d{3}[-.\s]\d{3}[-.\s]\d{4}\b/g
49631
+ ];
49632
+ const SSN_REGEX = /\b(\d{3})-(\d{2})-(\d{4})\b/g;
49633
+ const PASSPORT_REGEX_G = /\b(?:passport(?:\s*(?:no|number|#))?\s*[:#]?\s*[A-Z0-9]{6,9})\b/gi;
49634
+ const DOB_REGEX_G = /\b(?:dob|birth(?:day|date)?)\s*[:#]?\s*\d{1,4}[-/.]\d{1,2}[-/.]\d{1,4}\b/gi;
49635
+ const NATIONAL_ID_REGEX_G = /\b[A-Z]{2}\d{6}[A-Z]\b/g;
49636
+ function validSsnMatches(content) {
49637
+ SSN_REGEX.lastIndex = 0;
49638
+ const out = [];
49639
+ let match;
49640
+ while ((match = SSN_REGEX.exec(content)) !== null) {
49641
+ const [whole, area, group, serial] = match;
49642
+ if (area === "000" || area === "666" || area && Number.parseInt(area, 10) >= 900)
49643
+ continue;
49644
+ if (group === "00")
49645
+ continue;
49646
+ if (serial === "0000")
49647
+ continue;
49648
+ out.push(whole);
49649
+ }
49650
+ return out;
49651
+ }
49652
+ function validCreditCardMatches(content) {
49653
+ const candidates = content.match(/(?:\d[ .-]?){13,}/g) ?? [];
49654
+ const out = [];
49655
+ for (const candidate of candidates) {
49656
+ const found = firstLuhnWindow(candidate);
49657
+ if (found)
49658
+ out.push(found);
49659
+ }
49660
+ return out;
49661
+ }
49662
+ function firstLuhnWindow(run) {
49663
+ const digitPos = [];
49664
+ for (let i = 0;i < run.length; i += 1) {
49665
+ const c = run.charCodeAt(i);
49666
+ if (c >= 48 && c <= 57)
49667
+ digitPos.push(i);
49668
+ }
49669
+ const total = digitPos.length;
49670
+ if (total < 13)
49671
+ return;
49672
+ const tryWindow = (start, len) => {
49673
+ const from = digitPos[start];
49674
+ const to = digitPos[start + len - 1];
49675
+ const slice = run.slice(from, to + 1);
49676
+ const digits = slice.replace(/[ .-]/g, "");
49677
+ if (digits.length === len && luhnValid(digits))
49678
+ return slice.trim();
49679
+ return;
49680
+ };
49681
+ if (total >= 13 && total <= 19) {
49682
+ const whole = tryWindow(0, total);
49683
+ if (whole)
49684
+ return whole;
49685
+ }
49686
+ const startsGroup = (idx) => {
49687
+ const pos = digitPos[idx];
49688
+ if (pos === 0)
49689
+ return true;
49690
+ const prev = run.charCodeAt(pos - 1);
49691
+ return !(prev >= 48 && prev <= 57);
49692
+ };
49693
+ const endsGroup = (idx) => {
49694
+ const pos = digitPos[idx];
49695
+ if (pos === run.length - 1)
49696
+ return true;
49697
+ const nxt = run.charCodeAt(pos + 1);
49698
+ return !(nxt >= 48 && nxt <= 57);
49699
+ };
49700
+ for (let len = Math.min(19, total);len >= 13; len -= 1) {
49701
+ for (let start = 0;start + len <= total; start += 1) {
49702
+ if (!startsGroup(start) || !endsGroup(start + len - 1))
49703
+ continue;
49704
+ const hit = tryWindow(start, len);
49705
+ if (hit)
49706
+ return hit;
49707
+ }
49708
+ }
49709
+ return;
49710
+ }
49711
+ function luhnValid(digits) {
49712
+ let sum = 0;
49713
+ let alternate = false;
49714
+ for (let i = digits.length - 1;i >= 0; i -= 1) {
49715
+ const code = digits.charCodeAt(i) - 48;
49716
+ if (code < 0 || code > 9)
49717
+ return false;
49718
+ let n = code;
49719
+ if (alternate) {
49720
+ n *= 2;
49721
+ if (n > 9)
49722
+ n -= 9;
49723
+ }
49724
+ sum += n;
49725
+ alternate = !alternate;
49726
+ }
49727
+ return sum % 10 === 0;
49728
+ }
49729
+ function validIbanMatches(content) {
49730
+ const candidates = content.match(/\b[A-Za-z]{2}\d{2}[A-Za-z0-9]{10,30}\b/g) ?? [];
49731
+ const out = [];
49732
+ for (const candidate of candidates) {
49733
+ if (ibanMod97(candidate))
49734
+ out.push(candidate);
49735
+ }
49736
+ return out;
49737
+ }
49738
+ function ibanMod97(rawIban) {
49739
+ const iban = rawIban.toUpperCase();
49740
+ const rearranged = iban.slice(4) + iban.slice(0, 4);
49741
+ let numeric = "";
49742
+ for (const ch2 of rearranged) {
49743
+ const code = ch2.charCodeAt(0);
49744
+ if (code >= 48 && code <= 57)
49745
+ numeric += ch2;
49746
+ else if (code >= 65 && code <= 90)
49747
+ numeric += String(code - 55);
49748
+ else
49749
+ return false;
49750
+ }
49751
+ let remainder = 0;
49752
+ for (let i = 0;i < numeric.length; i += 7) {
49753
+ const chunk = String(remainder) + numeric.slice(i, i + 7);
49754
+ remainder = Number.parseInt(chunk, 10) % 97;
49755
+ }
49756
+ return remainder === 1;
49757
+ }
49758
+ function attachTo(flowGateway) {
49759
+ const detector = build();
49760
+ flowGateway.registerDetector((content, ctx) => detector(content, ctx));
49761
+ }
49762
+ PiiDetector.attachTo = attachTo;
49763
+ })(PiiDetector ||= {});
49764
+ });
49765
+
49766
+ // ../core/src/privacy-gateway.ts
49767
+ import { createHash as createHash6 } from "crypto";
49768
+ import { appendFile, mkdir, writeFile as writeFile2 } from "fs/promises";
49769
+ import path7 from "path";
49770
+ var FlowGateway;
49771
+ var init_privacy_gateway = __esm(() => {
49772
+ init_entity_graph();
49773
+ init_pii_detector();
49774
+ ((FlowGateway) => {
49775
+ FlowGateway.ROLLOUT_MODES = [
49776
+ "observe",
49777
+ "sanitized_shadow",
49778
+ "zone2_egress",
49779
+ "tool_log",
49780
+ "verifier",
49781
+ "non_bypassable"
49782
+ ];
49783
+ FlowGateway.DEFAULT_POLICY = {
49784
+ id: "kifaru.privacy.observe.v1",
49785
+ rolloutMode: "observe"
49786
+ };
49787
+ function rolloutModeRank(mode) {
49788
+ return FlowGateway.ROLLOUT_MODES.indexOf(mode);
49789
+ }
49790
+ FlowGateway.rolloutModeRank = rolloutModeRank;
49791
+ function rolloutAtLeast(mode, gate) {
49792
+ return rolloutModeRank(mode) >= rolloutModeRank(gate);
49793
+ }
49794
+ FlowGateway.rolloutAtLeast = rolloutAtLeast;
49795
+ function shouldEnforce(decision, action, mode) {
49796
+ if (mode === "observe" || mode === "sanitized_shadow")
49797
+ return false;
49798
+ if (decision.decision === "allow")
49799
+ return true;
49800
+ if (action === "model.call.before" || action === "model.call.after" || action === "model.fallback") {
49801
+ return rolloutAtLeast(mode, "zone2_egress");
49802
+ }
49803
+ if (action === "report.export" || action === "report.finding") {
49804
+ return rolloutAtLeast(mode, "zone2_egress");
49805
+ }
49806
+ if (action === "tool.call.before" || action === "tool.result.after" || action === "mcp.tool.call.before" || action === "mcp.tool.result.after" || action === "log.write" || action === "trace.write" || action === "memory.read" || action === "memory.write") {
49807
+ return rolloutAtLeast(mode, "tool_log");
49808
+ }
49809
+ return rolloutAtLeast(mode, "non_bypassable");
49810
+ }
49811
+ FlowGateway.shouldEnforce = shouldEnforce;
49812
+ function stableStringify(input2) {
49813
+ if (input2 === undefined)
49814
+ return "null";
49815
+ if (input2 === null || typeof input2 !== "object")
49816
+ return JSON.stringify(input2);
49817
+ if (Array.isArray(input2))
49818
+ return `[${input2.map((item) => stableStringify(item)).join(",")}]`;
49819
+ const record3 = input2;
49820
+ return `{${Object.keys(record3).sort().filter((key) => record3[key] !== undefined).map((key) => `${JSON.stringify(key)}:${stableStringify(record3[key])}`).join(",")}}`;
49821
+ }
49822
+ FlowGateway.stableStringify = stableStringify;
49823
+ function stableHash(input2) {
49824
+ return createHash6("sha256").update(stableStringify(input2)).digest("hex");
49825
+ }
49826
+ FlowGateway.stableHash = stableHash;
49827
+ function contentHash(content) {
49828
+ return createHash6("sha256").update(content).digest("hex");
49829
+ }
49830
+ FlowGateway.contentHash = contentHash;
49831
+ let registry3 = [];
49832
+ let engagementRegistry;
49833
+ function bindEngagementRegistry(reg) {
49834
+ engagementRegistry = reg;
49835
+ }
49836
+ FlowGateway.bindEngagementRegistry = bindEngagementRegistry;
49837
+ function getEngagementRegistry() {
49838
+ return engagementRegistry;
49839
+ }
49840
+ FlowGateway.getEngagementRegistry = getEngagementRegistry;
49841
+ function getDetectorRegistry() {
49842
+ return registry3;
49843
+ }
49844
+ FlowGateway.getDetectorRegistry = getDetectorRegistry;
49845
+ function setDetectorRegistry(next) {
49846
+ registry3 = [...next];
49847
+ }
49848
+ FlowGateway.setDetectorRegistry = setDetectorRegistry;
49849
+ function registerDetector(detector) {
49850
+ registry3.push(detector);
49851
+ }
49852
+ FlowGateway.registerDetector = registerDetector;
49853
+ function resetDetectorRegistry() {
49854
+ registry3 = [FlowGateway.regexDetector, FlowGateway.entityGraphDetector, FlowGateway.provenanceDetector, FlowGateway.piiDetector];
49855
+ }
49856
+ FlowGateway.resetDetectorRegistry = resetDetectorRegistry;
49857
+ function secretPatterns() {
49858
+ return [
49859
+ /\b(?:sk|ghp|AKIA|ASIA|xox[baprs]|kf_secret)[A-Za-z0-9_\-]{6,}\b/g,
49860
+ /\bGOCSPX-[A-Za-z0-9_\-]{6,}\b/g,
49861
+ /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
49862
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/g,
49863
+ /\bAIza[0-9A-Za-z_-]{35}\b/g,
49864
+ /(?:(?:password|passwd|secret|api[_-]?key|token|bearer|[A-Za-z0-9_-]{0,40}(?:key|secret|token|password))\s*[=:]\s*(?!\$\{)(?!([^\s"',;}\])])\1*(?![^\s"',;}\])]))(?!(?:true|false|verbose|none|null|changeme|example|your_token_here)(?![^\s"',;}\])]))(?=[^\s"',;}\])]*(?:\d|[^\sA-Za-z0-9"',;}\])])|[^\s"',;}\])]{20})[^\s"',;}\])]{6,})|(?:(?:password|passwd|secret|api[_-]?key|token|bearer|[A-Za-z0-9_-]{0,40}(?:key|secret|token|password))\s+(?!\$\{)(?!([^\s"',;}\])])\2*(?![^\s"',;}\])]))(?!(?:true|false|verbose|none|null|changeme|example|your_token_here)(?![^\s"',;}\])]))(?=[^\s"',;}\])]*(?:\d|[^\sA-Za-z0-9"',;}\])])|[^\s"',;}\])]{20})[^\s"',;}\])]{6,})|(?:Authorization\s*:\s*(?:Bearer\s+(?!\$\{)(?!([^\s"',;}\])])\3*(?![^\s"',;}\])]))(?!(?:true|false|verbose|none|null|changeme|example|your_token_here)(?![^\s"',;}\])]))(?=[^\s"',;}\])]*(?:\d|[^\sA-Za-z0-9"',;}\])])|[^\s"',;}\])]{20})|Basic\s+(?!\$\{)(?!([^\s"',;}\])])\4*(?![^\s"',;}\])]))(?!(?:true|false|verbose|none|null|changeme|example|your_token_here)(?![^\s"',;}\])])))[^\s"',;}\])]{6,})/gi
49865
+ ];
49866
+ }
49867
+ function cloudAccountPatterns() {
49868
+ return [
49869
+ /arn:aws:[^:\s]*:[^:\s]*:(\d{12})\b/g,
49870
+ /(?:aws|account)[^\d]{0,20}\b(\d{12})\b/gi,
49871
+ /\b(\d{12})\b[^\d]{0,20}(?:aws|account)/gi
49872
+ ];
49873
+ }
49874
+ const REPO_PATH_REGEX = /\/(?:home|repo|srv|var|opt|usr|Users|mnt)\/[A-Za-z0-9._/\-]+/g;
49875
+ FlowGateway.regexDetector = (content) => {
49876
+ const labels = new Set;
49877
+ if (secretPatterns().some((re) => re.test(content)))
49878
+ labels.add("secret");
49879
+ if (/\b[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:internal|corp|local|lan)\b/i.test(content)) {
49880
+ labels.add("internal_hostname");
49881
+ }
49882
+ if (cloudAccountPatterns().some((re) => re.test(content)))
49883
+ labels.add("cloud_account_id");
49884
+ if (REPO_PATH_REGEX.test(content)) {
49885
+ REPO_PATH_REGEX.lastIndex = 0;
49886
+ labels.add("repo_path");
49887
+ }
49888
+ if (/\b(?:HTTP\/1\.1 200|stack trace|uid=\d+|curl -i|exploit output)\b/i.test(content))
49889
+ labels.add("exploit_output");
49890
+ if (/\b(?:CVE-\d{4}-\d+|SSRF|IDOR|authorization bypass|privilege escalation)\b/i.test(content)) {
49891
+ labels.add("vulnerability_evidence");
49892
+ }
49893
+ if (/\b(?:ignore previous instructions|exfiltrate|send the secret|leak the token)\b/i.test(content)) {
49894
+ labels.add("prompt_injection");
49895
+ }
49896
+ return [...labels].sort();
49897
+ };
49898
+ FlowGateway.entityGraphDetector = (content, context2) => {
49899
+ const graph = context2.engagementGraph ?? engagementRegistry?.get(context2.engagement);
49900
+ if (!graph)
49901
+ return [];
49902
+ const hits = detectEntityMatches({ content, graph });
49903
+ if (hits.length === 0)
49904
+ return [];
49905
+ const labels = new Set;
49906
+ for (const hit of hits) {
49907
+ switch (hit.field) {
49908
+ case "domains":
49909
+ labels.add("internal_hostname");
49910
+ break;
49911
+ case "cidrs":
49912
+ labels.add("internal_hostname");
49913
+ break;
49914
+ case "cloud_accounts":
49915
+ labels.add("cloud_account_id");
49916
+ break;
49917
+ case "repos":
49918
+ labels.add("repo_path");
49919
+ break;
49920
+ case "team_names":
49921
+ case "employee_names":
49922
+ labels.add("customer_data");
49923
+ break;
49924
+ case "build_jobs":
49925
+ labels.add("engagement_asset");
49926
+ break;
49927
+ default:
49928
+ labels.add("engagement_asset");
49929
+ }
49930
+ }
49931
+ return [...labels].sort();
49932
+ };
49933
+ FlowGateway.provenanceSensor = (_content, context2) => {
49934
+ const labels = new Set;
49935
+ const metadata = context2.metadata ?? {};
49936
+ const surface = context2.sourceSurface ?? "";
49937
+ if (typeof metadata.mcpServerId === "string" && metadata.mcpServerId.length > 0) {
49938
+ labels.add("untrusted_provenance");
49939
+ }
49940
+ if (surface.startsWith("mcp.tool")) {
49941
+ labels.add("untrusted_provenance");
49942
+ }
49943
+ if (typeof metadata.sourceUrl === "string" && /^https?:\/\//i.test(metadata.sourceUrl)) {
49944
+ labels.add("untrusted_provenance");
49945
+ }
49946
+ const tool2 = typeof metadata.tool === "string" ? metadata.tool : "";
49947
+ if (tool2 && /(?:browser|fetch|crawl|scrape|wget|curl)/i.test(tool2)) {
49948
+ labels.add("untrusted_provenance");
49949
+ }
49950
+ return [...labels].sort();
49951
+ };
49952
+ FlowGateway.provenanceDetector = FlowGateway.provenanceSensor;
49953
+ FlowGateway.piiDetector = (() => {
49954
+ const detect = PiiDetector.build();
49955
+ return (content, ctx) => detect(content, ctx);
49956
+ })();
49957
+ registry3 = [FlowGateway.regexDetector, FlowGateway.entityGraphDetector, FlowGateway.provenanceDetector, FlowGateway.piiDetector];
49958
+ function detectLabels(content, context2) {
49959
+ const ctx = context2 ?? {};
49960
+ const all = registry3.flatMap((detector) => {
49961
+ try {
49962
+ return detector(content, ctx);
49963
+ } catch {
49964
+ return [];
49965
+ }
49966
+ });
49967
+ return [...new Set(all)].sort();
49968
+ }
49969
+ FlowGateway.detectLabels = detectLabels;
49970
+ function createFlowEvent(input2) {
49971
+ const detectContext = {
49972
+ engagement: typeof input2.metadata?.engagement === "string" ? input2.metadata.engagement : undefined,
49973
+ engagementGraph: input2.metadata?.engagementGraph,
49974
+ metadata: input2.metadata,
49975
+ sourceSurface: input2.sourceSurface
49976
+ };
49977
+ const labels = [...new Set([...input2.labels ?? [], ...detectLabels(input2.content, detectContext)])].sort();
49978
+ const base = {
49979
+ schemaVersion: "kifaru.flow-event.v1",
49980
+ actor: input2.actor,
49981
+ sessionId: input2.sessionId,
49982
+ action: input2.action,
49983
+ purpose: input2.purpose,
49984
+ destination: input2.destination,
49985
+ destinationTrustZone: input2.destinationTrustZone,
49986
+ contentRef: input2.contentRef,
49987
+ inputRefs: input2.inputRefs ?? [],
49988
+ sourceSurface: input2.sourceSurface,
49989
+ sourceTrustZone: input2.sourceTrustZone,
49990
+ representation: input2.representation,
49991
+ labels,
49992
+ metadata: input2.metadata ?? {},
49993
+ contentHash: contentHash(input2.content),
49994
+ contentBytes: new TextEncoder().encode(input2.content).byteLength
49995
+ };
49996
+ return {
49997
+ ...base,
49998
+ eventId: stableHash(base)
49999
+ };
50000
+ }
50001
+ FlowGateway.createFlowEvent = createFlowEvent;
50002
+ function hasSensitiveLabel(labels) {
50003
+ return labels.some((label) => [
50004
+ "pii",
50005
+ "secret",
50006
+ "internal_hostname",
50007
+ "cloud_account_id",
50008
+ "repo_path",
50009
+ "exploit_output",
50010
+ "vulnerability_evidence",
50011
+ "customer_data",
50012
+ "unknown_sensitive",
50013
+ "engagement_asset"
50014
+ ].includes(label));
50015
+ }
50016
+ function hasUntrustedProvenance(labels) {
50017
+ return labels.includes("untrusted_provenance");
50018
+ }
50019
+ function hasEvidenceIds(event) {
50020
+ const evidenceIds = event.metadata?.evidenceIds;
50021
+ return Array.isArray(evidenceIds) && evidenceIds.length > 0;
50022
+ }
50023
+ function decideFlow(event, policy = FlowGateway.DEFAULT_POLICY) {
50024
+ const obligations = [];
50025
+ const outputRefs = [];
50026
+ let decision = "allow";
50027
+ let representation = event.representation;
50028
+ const sensitive = hasSensitiveLabel(event.labels);
50029
+ const rawSensitive = sensitive && event.representation === "raw";
50030
+ if (event.action === "model.fallback" && event.metadata?.previousRepresentation && event.metadata.previousRepresentation !== "raw" && event.representation === "raw") {
50031
+ decision = "block";
50032
+ obligations.push("fallback_must_not_downgrade_privacy");
50033
+ } else if (event.action === "report.finding" && !hasEvidenceIds(event)) {
50034
+ decision = "require_approval";
50035
+ obligations.push("attach_evidence_ids_before_report");
50036
+ } else if (event.action === "mcp.tool.result.after" && event.labels.includes("prompt_injection")) {
50037
+ decision = "store_raw_return_ref";
50038
+ representation = "reference";
50039
+ obligations.push("vault_raw_mcp_result");
50040
+ obligations.push("return_sanitized_preview");
50041
+ outputRefs.push(`evidence:${event.contentHash.slice(0, 16)}`);
50042
+ } else if (event.action === "mcp.tool.result.after" && hasUntrustedProvenance(event.labels) && event.representation === "raw") {
50043
+ decision = "store_raw_return_ref";
50044
+ representation = "reference";
50045
+ obligations.push("vault_untrusted_mcp_result");
50046
+ obligations.push("return_sanitized_preview");
50047
+ outputRefs.push(`evidence:${event.contentHash.slice(0, 16)}`);
50048
+ } else if (rawSensitive && (event.action === "tool.result.after" || event.action === "mcp.tool.result.after")) {
50049
+ decision = "store_raw_return_ref";
50050
+ representation = "reference";
50051
+ obligations.push("vault_raw_tool_result");
50052
+ obligations.push("return_sanitized_preview");
50053
+ outputRefs.push(`evidence:${event.contentHash.slice(0, 16)}`);
50054
+ } else if (rawSensitive && (event.destinationTrustZone === "zone_2_external" || event.action === "log.write" || event.action === "trace.write" || event.action === "memory.write" || event.action === "memory.read" || event.action === "report.export" || event.action === "agent.message")) {
50055
+ decision = "transform";
50056
+ representation = "sanitized_preview";
50057
+ obligations.push("replace_raw_values_with_aliases");
50058
+ obligations.push("preserve_typed_evidence_refs");
50059
+ }
50060
+ const base = {
50061
+ schemaVersion: "kifaru.flow-decision.v1",
50062
+ eventId: event.eventId,
50063
+ eventHash: stableHash(event),
50064
+ decision,
50065
+ representation,
50066
+ labels: event.labels,
50067
+ policyId: policy.id,
50068
+ obligations,
50069
+ outputRefs
50070
+ };
50071
+ return {
50072
+ ...base,
50073
+ decisionId: stableHash(base)
50074
+ };
50075
+ }
50076
+ FlowGateway.decideFlow = decideFlow;
50077
+ function createAuditRecord(event, decision, generatedAt) {
50078
+ const base = {
50079
+ schemaVersion: "kifaru.flow-audit.v1",
50080
+ generatedAt,
50081
+ event,
50082
+ decision
50083
+ };
50084
+ return {
50085
+ ...base,
50086
+ auditId: stableHash(base)
50087
+ };
50088
+ }
50089
+ FlowGateway.createAuditRecord = createAuditRecord;
50090
+
50091
+ class AuditLedger {
50092
+ records = [];
50093
+ append(record3) {
50094
+ this.records.push(record3);
50095
+ }
50096
+ list() {
50097
+ return [...this.records];
50098
+ }
50099
+ }
50100
+ FlowGateway.AuditLedger = AuditLedger;
50101
+ async function appendAuditRecord(filepath, record3) {
50102
+ await mkdir(path7.dirname(filepath), { recursive: true });
50103
+ await appendFile(filepath, `${stableStringify(record3)}
50104
+ `, "utf8");
50105
+ }
50106
+ FlowGateway.appendAuditRecord = appendAuditRecord;
50107
+ function aliasFor(input2) {
50108
+ const prefix = {
50109
+ internal_hostname: "HOST",
50110
+ cloud_account_id: "CLOUD_ACCT",
50111
+ repo_path: "REPO_PATH",
50112
+ secret: "SECRET",
50113
+ pii: "PII",
50114
+ customer_data: "CUSTOMER",
50115
+ exploit_output: "EXPLOIT",
50116
+ vulnerability_evidence: "VULN",
50117
+ prompt_injection: "INJECT",
50118
+ unknown_sensitive: "SENSITIVE"
50119
+ };
50120
+ const tag2 = prefix[input2.label] ?? "EVIDENCE";
50121
+ const seed = stableStringify({ engagement: input2.engagement ?? "default", label: input2.label, value: input2.value });
50122
+ const digest2 = createHash6("sha256").update(seed).digest("hex");
50123
+ const num = parseInt(digest2.slice(0, 10), 16) % 1e6;
50124
+ return `${tag2}_${String(num).padStart(6, "0")}`;
50125
+ }
50126
+ FlowGateway.aliasFor = aliasFor;
50127
+ function sanitize(input2) {
50128
+ const aliases = {};
50129
+ let sanitized = input2.content;
50130
+ const tokenRules = [
50131
+ ...secretPatterns().map((regex) => ({ regex, label: "secret" })),
50132
+ {
50133
+ regex: /\b[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:internal|corp|local|lan)\b/gi,
50134
+ label: "internal_hostname"
50135
+ },
50136
+ { regex: new RegExp(REPO_PATH_REGEX.source, "g"), label: "repo_path" }
50137
+ ];
50138
+ for (const rule of tokenRules) {
50139
+ sanitized = sanitized.replace(rule.regex, (match) => {
50140
+ const alias = aliasFor({ engagement: input2.engagement, label: rule.label, value: match });
50141
+ aliases[alias] = match;
50142
+ return alias;
50143
+ });
50144
+ }
50145
+ for (const regex of cloudAccountPatterns()) {
50146
+ sanitized = sanitized.replace(regex, (match, account) => {
50147
+ if (!account)
50148
+ return match;
50149
+ const alias = aliasFor({ engagement: input2.engagement, label: "cloud_account_id", value: account });
50150
+ aliases[alias] = account;
50151
+ return match.replace(account, alias);
50152
+ });
50153
+ }
50154
+ const piiMatches = [...PiiDetector.findMatches(sanitized)].sort((a, b) => b.value.length - a.value.length);
50155
+ for (const hit of piiMatches) {
50156
+ const alias = aliasFor({ engagement: input2.engagement, label: "pii", value: hit.value });
50157
+ const escaped = hit.value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50158
+ let replaced = false;
50159
+ sanitized = sanitized.replace(new RegExp(escaped, "g"), () => {
50160
+ replaced = true;
50161
+ return alias;
50162
+ });
50163
+ if (replaced)
50164
+ aliases[alias] = hit.value;
50165
+ }
50166
+ const graph = input2.engagementGraph ?? engagementRegistry?.get(input2.engagement);
50167
+ if (graph) {
50168
+ const hits = detectEntityMatches({ content: sanitized, graph });
50169
+ const ranked = [...hits].sort((a, b) => b.token.length - a.token.length);
50170
+ for (const hit of ranked) {
50171
+ const label = entityFieldToLabel(hit.field);
50172
+ const alias = aliasFor({ engagement: input2.engagement, label, value: hit.token });
50173
+ aliases[alias] = hit.token;
50174
+ const escaped = hit.token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50175
+ const re = new RegExp(`(^|[^A-Za-z0-9_-])${escaped}([^A-Za-z0-9_-]|$)`, "gi");
50176
+ sanitized = sanitized.replace(re, (_match, before, after) => `${before}${alias}${after}`);
50177
+ }
50178
+ }
50179
+ return {
50180
+ sanitized,
50181
+ aliases,
50182
+ labels: detectLabels(input2.content, {
50183
+ engagement: input2.engagement,
50184
+ engagementGraph: graph
50185
+ })
50186
+ };
50187
+ }
50188
+ FlowGateway.sanitize = sanitize;
50189
+ function entityFieldToLabel(field) {
50190
+ switch (field) {
50191
+ case "domains":
50192
+ case "cidrs":
50193
+ return "internal_hostname";
50194
+ case "cloud_accounts":
50195
+ return "cloud_account_id";
50196
+ case "repos":
50197
+ return "repo_path";
50198
+ case "team_names":
50199
+ case "employee_names":
50200
+ return "customer_data";
50201
+ case "build_jobs":
50202
+ return "engagement_asset";
50203
+ default:
50204
+ return "engagement_asset";
50205
+ }
50206
+ }
50207
+ async function vaultRaw(input2) {
50208
+ const detectedLabels = [...new Set([...input2.labels ?? [], ...detectLabels(input2.content)])].sort();
50209
+ const { sanitized, aliases } = sanitize({ engagement: input2.engagement, content: input2.content });
50210
+ const hash4 = contentHash(input2.content);
50211
+ const evidenceId = `EV-${hash4.slice(0, 16)}`;
50212
+ const createdAt = new Date().toISOString();
50213
+ const entry = {
50214
+ evidenceId,
50215
+ contentHash: hash4,
50216
+ contentBytes: new TextEncoder().encode(input2.content).byteLength,
50217
+ labels: detectedLabels,
50218
+ aliases,
50219
+ sanitizedPreview: sanitized,
50220
+ trustZone: "zone_0_raw",
50221
+ createdAt
50222
+ };
50223
+ await mkdir(input2.vaultDir, { recursive: true, mode: 448 });
50224
+ const rawPath = path7.join(input2.vaultDir, `${evidenceId}.raw.txt`);
50225
+ const metaPath = path7.join(input2.vaultDir, `${evidenceId}.json`);
50226
+ await writeFile2(rawPath, input2.content, { encoding: "utf8", mode: 384 });
50227
+ await writeFile2(metaPath, stableStringify({
50228
+ ...entry,
50229
+ sourceSurface: input2.sourceSurface ?? "unknown"
50230
+ }), { encoding: "utf8", mode: 384 });
50231
+ return entry;
50232
+ }
50233
+ FlowGateway.vaultRaw = vaultRaw;
50234
+ })(FlowGateway ||= {});
50235
+ });
50236
+
49491
50237
  // ../core/src/index.ts
49492
50238
  var init_src2 = __esm(() => {
49493
50239
  init_kifaru_model_router();
@@ -49503,6 +50249,9 @@ var init_src2 = __esm(() => {
49503
50249
  init_governed_skill_tool();
49504
50250
  init_embedded_skills();
49505
50251
  init_embedded_theme();
50252
+ init_privacy_gateway();
50253
+ init_entity_graph();
50254
+ init_pii_detector();
49506
50255
  });
49507
50256
 
49508
50257
  // ../headless-runtime/src/policy-gate.ts
@@ -49711,13 +50460,13 @@ var init_event_sink = __esm(() => {
49711
50460
 
49712
50461
  // ../headless-runtime/src/sqlite-event-store.ts
49713
50462
  import fs7 from "fs";
49714
- import path7 from "path";
50463
+ import path8 from "path";
49715
50464
  import { Database as Database2 } from "bun:sqlite";
49716
50465
 
49717
50466
  class SqliteEventStore {
49718
50467
  db;
49719
50468
  constructor(dbPath) {
49720
- fs7.mkdirSync(path7.dirname(dbPath), { recursive: true });
50469
+ fs7.mkdirSync(path8.dirname(dbPath), { recursive: true });
49721
50470
  this.db = new Database2(dbPath, { create: true });
49722
50471
  this.initialize();
49723
50472
  }
@@ -50217,9 +50966,9 @@ var init_run_workflow = __esm(() => {
50217
50966
 
50218
50967
  // ../headless-runtime/src/report-files.ts
50219
50968
  import fs8 from "fs";
50220
- import path8 from "path";
50969
+ import path9 from "path";
50221
50970
  function writeLocalReport(record3, outDir) {
50222
- const base = path8.join(outDir, record3.runId);
50971
+ const base = path9.join(outDir, record3.runId);
50223
50972
  const files = {
50224
50973
  json: `${base}.json`,
50225
50974
  md: `${base}.md`,
@@ -50253,10 +51002,10 @@ var init_report_files = __esm(() => {
50253
51002
 
50254
51003
  // ../headless-runtime/src/workflow-skill-editor.ts
50255
51004
  import fs9 from "fs";
50256
- import path9 from "path";
50257
- import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
51005
+ import path10 from "path";
51006
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
50258
51007
  function skillRoot() {
50259
- return path9.resolve(process.env.KIFARU_WORKFLOW_SKILLS_DIR ?? "data/headless-skill-overrides");
51008
+ return path10.resolve(process.env.KIFARU_WORKFLOW_SKILLS_DIR ?? "data/headless-skill-overrides");
50260
51009
  }
50261
51010
  function builtIn(workflowKey) {
50262
51011
  const workflow = workflowByKey(workflowKey);
@@ -50270,7 +51019,7 @@ function builtIn(workflowKey) {
50270
51019
  return { skillName, source, pack };
50271
51020
  }
50272
51021
  function revision(markdown) {
50273
- return createHash6("sha256").update(markdown).digest("hex");
51022
+ return createHash7("sha256").update(markdown).digest("hex");
50274
51023
  }
50275
51024
  function validateMarkdown(markdown) {
50276
51025
  const body = markdown.trim();
@@ -50281,7 +51030,7 @@ function validateMarkdown(markdown) {
50281
51030
  }
50282
51031
  function readWorkflowSkill(workflowKey) {
50283
51032
  const { skillName, pack } = builtIn(workflowKey);
50284
- const filename = path9.join(skillRoot(), `${workflowKey}.md`);
51033
+ const filename = path10.join(skillRoot(), `${workflowKey}.md`);
50285
51034
  const customized = fs9.existsSync(filename);
50286
51035
  const markdown = customized ? validateMarkdown(fs9.readFileSync(filename, "utf8")) : pack.body;
50287
51036
  return { workflowKey, skillName, markdown, revision: revision(markdown), customized };
@@ -50294,8 +51043,8 @@ function saveWorkflowSkill(workflowKey, markdown, expectedRevision) {
50294
51043
  }
50295
51044
  const root = skillRoot();
50296
51045
  fs9.mkdirSync(root, { recursive: true });
50297
- const filename = path9.join(root, `${workflowKey}.md`);
50298
- const pending = path9.join(root, `${workflowKey}.${randomUUID3()}.pending`);
51046
+ const filename = path10.join(root, `${workflowKey}.md`);
51047
+ const pending = path10.join(root, `${workflowKey}.${randomUUID3()}.pending`);
50299
51048
  try {
50300
51049
  fs9.writeFileSync(pending, `${body}
50301
51050
  `, { flag: "wx" });
@@ -52891,7 +53640,7 @@ class HttpClient {
52891
53640
  this.request = async ({
52892
53641
  body,
52893
53642
  secure,
52894
- path: path11,
53643
+ path: path12,
52895
53644
  type,
52896
53645
  query,
52897
53646
  format,
@@ -52904,7 +53653,7 @@ class HttpClient {
52904
53653
  const queryString = query && this.toQueryString(query);
52905
53654
  const payloadFormatter = this.contentFormatters[type || ContentType.Json];
52906
53655
  const responseFormat = format || requestParams.format;
52907
- return this.customFetch(`${baseUrl || this.baseUrl || ""}${path11}${queryString ? `?${queryString}` : ""}`, {
53656
+ return this.customFetch(`${baseUrl || this.baseUrl || ""}${path12}${queryString ? `?${queryString}` : ""}`, {
52908
53657
  ...requestParams,
52909
53658
  headers: {
52910
53659
  ...requestParams.headers || {},
@@ -54207,7 +54956,7 @@ var init_lib2 = __esm(() => {
54207
54956
  });
54208
54957
 
54209
54958
  // src/index.ts
54210
- import fs14 from "fs";
54959
+ import fs15 from "fs";
54211
54960
 
54212
54961
  // src/config.ts
54213
54962
  init_zod();
@@ -64316,10 +65065,10 @@ function escapeLike(value) {
64316
65065
  // src/platform/interactive-report.ts
64317
65066
  init_src();
64318
65067
  init_src3();
64319
- import path10 from "path";
64320
- import { createHash as createHash7 } from "crypto";
65068
+ import path11 from "path";
65069
+ import { createHash as createHash8 } from "crypto";
64321
65070
  function stableUuid(value) {
64322
- const bytes = createHash7("sha256").update(value).digest();
65071
+ const bytes = createHash8("sha256").update(value).digest();
64323
65072
  bytes[6] = (bytes[6] ?? 0) & 15 | 64;
64324
65073
  bytes[8] = (bytes[8] ?? 0) & 63 | 128;
64325
65074
  const hex5 = bytes.subarray(0, 16).toString("hex");
@@ -64380,11 +65129,11 @@ function writeInteractiveRunReport(db, workspaceID, platformRunID, outDir) {
64380
65129
  evidence,
64381
65130
  version: 0
64382
65131
  });
64383
- return writeLocalReport(record3, path10.resolve(outDir));
65132
+ return writeLocalReport(record3, path11.resolve(outDir));
64384
65133
  }
64385
65134
 
64386
65135
  // src/platform/repository.ts
64387
- import { createHash as createHash8, randomUUID as randomUUID4 } from "crypto";
65136
+ import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
64388
65137
  class PlatformRunNotFoundError extends Error {
64389
65138
  name = "PlatformRunNotFoundError";
64390
65139
  }
@@ -64823,7 +65572,7 @@ function createPlatformRepository(db) {
64823
65572
  insertJob.run(id, actor.workspaceID, input2.runID ?? null, input2.kind, input2.priority, JSON.stringify(input2.payload), input2.maxAttempts, input2.availableAt ?? now2, now2, now2, input2.idempotencyKey);
64824
65573
  const delivery2 = githubDelivery(input2.kind, input2.payload);
64825
65574
  if (delivery2) {
64826
- upsertWebhookDelivery.run(delivery2.deliveryID, actor.workspaceID, delivery2.eventName, delivery2.action, createHash8("sha256").update(JSON.stringify(input2.payload)).digest("hex"), input2.runID ?? null, now2);
65575
+ upsertWebhookDelivery.run(delivery2.deliveryID, actor.workspaceID, delivery2.eventName, delivery2.action, createHash9("sha256").update(JSON.stringify(input2.payload)).digest("hex"), input2.runID ?? null, now2);
64827
65576
  }
64828
65577
  const created = findJobByID.get(actor.workspaceID, id);
64829
65578
  db.exec("COMMIT");
@@ -64905,7 +65654,7 @@ function createPlatformRepository(db) {
64905
65654
  try {
64906
65655
  for (const finding of inputs) {
64907
65656
  const existing = findFinding.get(workspaceID, finding.fingerprint);
64908
- const id = existing?.id ?? `finding_${createHash8("sha256").update(`${workspaceID}:${finding.fingerprint}`).digest("hex").slice(0, 24)}`;
65657
+ const id = existing?.id ?? `finding_${createHash9("sha256").update(`${workspaceID}:${finding.fingerprint}`).digest("hex").slice(0, 24)}`;
64909
65658
  const description = finding.description ?? finding.title;
64910
65659
  const cwe = finding.cwe ?? (finding.ruleID.startsWith("CWE-") ? finding.ruleID : null);
64911
65660
  if (existing) {
@@ -65133,10 +65882,10 @@ function mapAgentSession(row) {
65133
65882
  };
65134
65883
  }
65135
65884
  function operationID(workspaceID, sessionID, toolCallID) {
65136
- return `operation_${createHash8("sha256").update(`${workspaceID}:${sessionID}:${toolCallID}`).digest("hex").slice(0, 24)}`;
65885
+ return `operation_${createHash9("sha256").update(`${workspaceID}:${sessionID}:${toolCallID}`).digest("hex").slice(0, 24)}`;
65137
65886
  }
65138
65887
  function evidenceID(runID, fingerprint) {
65139
- return `evidence_${createHash8("sha256").update(`${runID}:${fingerprint}`).digest("hex").slice(0, 24)}`;
65888
+ return `evidence_${createHash9("sha256").update(`${runID}:${fingerprint}`).digest("hex").slice(0, 24)}`;
65140
65889
  }
65141
65890
  function redactForStorage(value, depth = 0) {
65142
65891
  if (value === undefined || value === null || typeof value === "boolean" || typeof value === "number")
@@ -65197,11 +65946,11 @@ var getFilePath = (options) => {
65197
65946
  } else if (!filename.match(/\.[a-zA-Z0-9_-]+$/)) {
65198
65947
  filename = filename.concat("/" + defaultDocument);
65199
65948
  }
65200
- const path11 = getFilePathWithoutDefaultDocument({
65949
+ const path12 = getFilePathWithoutDefaultDocument({
65201
65950
  root: options.root,
65202
65951
  filename
65203
65952
  });
65204
- return path11;
65953
+ return path12;
65205
65954
  };
65206
65955
  var getFilePathWithoutDefaultDocument = (options) => {
65207
65956
  let root = options.root || "";
@@ -65212,12 +65961,12 @@ var getFilePathWithoutDefaultDocument = (options) => {
65212
65961
  filename = filename.replace(/^\.?[\/\\]/, "");
65213
65962
  filename = filename.replace(/\\/, "/");
65214
65963
  root = root.replace(/\/$/, "");
65215
- let path11 = root ? root + "/" + filename : filename;
65216
- path11 = path11.replace(/^\.?\//, "");
65217
- if (root[0] !== "/" && path11[0] === "/") {
65964
+ let path12 = root ? root + "/" + filename : filename;
65965
+ path12 = path12.replace(/^\.?\//, "");
65966
+ if (root[0] !== "/" && path12[0] === "/") {
65218
65967
  return;
65219
65968
  }
65220
- return path11;
65969
+ return path12;
65221
65970
  };
65222
65971
 
65223
65972
  // ../../node_modules/.bun/hono@4.7.10/node_modules/hono/dist/utils/mime.js
@@ -65300,7 +66049,7 @@ var ENCODINGS = {
65300
66049
  };
65301
66050
  var ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
65302
66051
  var DEFAULT_DOCUMENT = "index.html";
65303
- var defaultPathResolve = (path11) => path11;
66052
+ var defaultPathResolve = (path12) => path12;
65304
66053
  var serveStatic = (options) => {
65305
66054
  let isAbsoluteRoot = false;
65306
66055
  let root;
@@ -65328,21 +66077,21 @@ var serveStatic = (options) => {
65328
66077
  filename += "/";
65329
66078
  }
65330
66079
  }
65331
- let path11 = getFilePath({
66080
+ let path12 = getFilePath({
65332
66081
  filename,
65333
66082
  root,
65334
66083
  defaultDocument: DEFAULT_DOCUMENT
65335
66084
  });
65336
- if (!path11) {
66085
+ if (!path12) {
65337
66086
  return await next();
65338
66087
  }
65339
66088
  if (isAbsoluteRoot) {
65340
- path11 = "/" + path11;
66089
+ path12 = "/" + path12;
65341
66090
  }
65342
66091
  const getContent = options.getContent;
65343
66092
  const pathResolve = options.pathResolve ?? defaultPathResolve;
65344
- path11 = pathResolve(path11);
65345
- let content = await getContent(path11, c);
66093
+ path12 = pathResolve(path12);
66094
+ let content = await getContent(path12, c);
65346
66095
  if (!content) {
65347
66096
  let pathWithoutDefaultDocument = getFilePathWithoutDefaultDocument({
65348
66097
  filename,
@@ -65352,10 +66101,10 @@ var serveStatic = (options) => {
65352
66101
  return await next();
65353
66102
  }
65354
66103
  pathWithoutDefaultDocument = pathResolve(pathWithoutDefaultDocument);
65355
- if (pathWithoutDefaultDocument !== path11) {
66104
+ if (pathWithoutDefaultDocument !== path12) {
65356
66105
  content = await getContent(pathWithoutDefaultDocument, c);
65357
66106
  if (content) {
65358
- path11 = pathWithoutDefaultDocument;
66107
+ path12 = pathWithoutDefaultDocument;
65359
66108
  }
65360
66109
  }
65361
66110
  }
@@ -65363,7 +66112,7 @@ var serveStatic = (options) => {
65363
66112
  return c.newResponse(content.body, content);
65364
66113
  }
65365
66114
  if (content) {
65366
- const mimeType = options.mimes && getMimeType(path11, options.mimes) || getMimeType(path11);
66115
+ const mimeType = options.mimes && getMimeType(path12, options.mimes) || getMimeType(path12);
65367
66116
  c.header("Content-Type", mimeType || "application/octet-stream");
65368
66117
  if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
65369
66118
  const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
@@ -65371,7 +66120,7 @@ var serveStatic = (options) => {
65371
66120
  if (!acceptEncodingSet.has(encoding)) {
65372
66121
  continue;
65373
66122
  }
65374
- const compressedContent = await getContent(path11 + ENCODINGS[encoding], c);
66123
+ const compressedContent = await getContent(path12 + ENCODINGS[encoding], c);
65375
66124
  if (compressedContent) {
65376
66125
  content = compressedContent;
65377
66126
  c.header("Content-Encoding", encoding);
@@ -65380,10 +66129,10 @@ var serveStatic = (options) => {
65380
66129
  }
65381
66130
  }
65382
66131
  }
65383
- await options.onFound?.(path11, c);
66132
+ await options.onFound?.(path12, c);
65384
66133
  return c.body(content);
65385
66134
  }
65386
- await options.onNotFound?.(path11, c);
66135
+ await options.onNotFound?.(path12, c);
65387
66136
  await next();
65388
66137
  return;
65389
66138
  };
@@ -65392,18 +66141,18 @@ var serveStatic = (options) => {
65392
66141
  // ../../node_modules/.bun/hono@4.7.10/node_modules/hono/dist/adapter/bun/serve-static.js
65393
66142
  var serveStatic2 = (options) => {
65394
66143
  return async function serveStatic22(c, next) {
65395
- const getContent = async (path11) => {
65396
- path11 = path11.startsWith("/") ? path11 : `./${path11}`;
65397
- const file3 = Bun.file(path11);
66144
+ const getContent = async (path12) => {
66145
+ path12 = path12.startsWith("/") ? path12 : `./${path12}`;
66146
+ const file3 = Bun.file(path12);
65398
66147
  return await file3.exists() ? file3 : null;
65399
66148
  };
65400
- const pathResolve = (path11) => {
65401
- return path11.startsWith("/") ? path11 : `./${path11}`;
66149
+ const pathResolve = (path12) => {
66150
+ return path12.startsWith("/") ? path12 : `./${path12}`;
65402
66151
  };
65403
- const isDir = async (path11) => {
66152
+ const isDir = async (path12) => {
65404
66153
  let isDir2;
65405
66154
  try {
65406
- const stats = await stat(path11);
66155
+ const stats = await stat(path12);
65407
66156
  isDir2 = stats.isDirectory();
65408
66157
  } catch {}
65409
66158
  return isDir2;
@@ -65459,7 +66208,7 @@ var WSContext = class {
65459
66208
  // src/app.ts
65460
66209
  init_zod();
65461
66210
  import fs11, { existsSync as existsSync2 } from "fs";
65462
- import path11 from "path";
66211
+ import path12 from "path";
65463
66212
 
65464
66213
  // src/session-archive/contract.ts
65465
66214
  init_zod();
@@ -66018,7 +66767,7 @@ function escapeLike2(value) {
66018
66767
 
66019
66768
  // src/session-archive/writer.ts
66020
66769
  init_zod();
66021
- import { createHash as createHash9 } from "crypto";
66770
+ import { createHash as createHash10 } from "crypto";
66022
66771
  var MAX_PREVIEW_LENGTH = 1000;
66023
66772
 
66024
66773
  class SessionArchiveInputError extends Error {
@@ -66403,7 +67152,7 @@ function logLineID(sessionID, sequence) {
66403
67152
  return `log_${hash4(`${sessionID}:${sequence}`)}`;
66404
67153
  }
66405
67154
  function hash4(value) {
66406
- return createHash9("sha256").update(value).digest("hex").slice(0, 24);
67155
+ return createHash10("sha256").update(value).digest("hex").slice(0, 24);
66407
67156
  }
66408
67157
 
66409
67158
  // src/tracing.ts
@@ -67670,8 +68419,8 @@ Disallow: /
67670
68419
  const events = store.readEvents(runId);
67671
68420
  const job = store.getLocalRun(runId);
67672
68421
  let started = events.find((event) => event.eventType === "headless.run.started")?.payload;
67673
- const outDir = path11.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
67674
- const reportPath = path11.join(outDir, `${runId}.md`);
68422
+ const outDir = path12.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
68423
+ const reportPath = path12.join(outDir, `${runId}.md`);
67675
68424
  if (!started && job) {
67676
68425
  const workflow = workflowByKey(job.workflowKey);
67677
68426
  started = {
@@ -67682,9 +68431,9 @@ Disallow: /
67682
68431
  stages: workflow?.stages.map((stage2) => ({ key: stage2.key, name: stage2.name })) ?? []
67683
68432
  };
67684
68433
  }
67685
- if (!started && fs11.existsSync(path11.join(outDir, `${runId}.json`))) {
68434
+ if (!started && fs11.existsSync(path12.join(outDir, `${runId}.json`))) {
67686
68435
  try {
67687
- const record3 = JSON.parse(fs11.readFileSync(path11.join(outDir, `${runId}.json`), "utf8"));
68436
+ const record3 = JSON.parse(fs11.readFileSync(path12.join(outDir, `${runId}.json`), "utf8"));
67688
68437
  if (record3.runId === runId && typeof record3.workflowKey === "string" && typeof record3.target === "string") {
67689
68438
  const workflow = workflowByKey(record3.workflowKey);
67690
68439
  started = {
@@ -67765,7 +68514,7 @@ Disallow: /
67765
68514
  if (!localReportsEnabled(c) || context2.actor.role !== "admin")
67766
68515
  return c.json({ code: "local_runs_disabled" }, 403);
67767
68516
  const { SqliteEventStore: SqliteEventStore2 } = await Promise.resolve().then(() => (init_src3(), exports_src2));
67768
- const store = new SqliteEventStore2(path11.resolve(process.env.KIFARU_LOCAL_RUNS_DB ?? "data/headless-events.sqlite"));
68517
+ const store = new SqliteEventStore2(path12.resolve(process.env.KIFARU_LOCAL_RUNS_DB ?? "data/headless-events.sqlite"));
67769
68518
  try {
67770
68519
  return c.json({ items: store.listLocalRunIds().flatMap((runId) => {
67771
68520
  const item = localRunStatus(store, runId);
@@ -67785,7 +68534,7 @@ Disallow: /
67785
68534
  if (!/^[0-9a-f-]{36}$/i.test(runId))
67786
68535
  return c.json({ code: "invalid_run_id" }, 400);
67787
68536
  const { SqliteEventStore: SqliteEventStore2 } = await Promise.resolve().then(() => (init_src3(), exports_src2));
67788
- const store = new SqliteEventStore2(path11.resolve(process.env.KIFARU_LOCAL_RUNS_DB ?? "data/headless-events.sqlite"));
68537
+ const store = new SqliteEventStore2(path12.resolve(process.env.KIFARU_LOCAL_RUNS_DB ?? "data/headless-events.sqlite"));
67789
68538
  try {
67790
68539
  const item = localRunStatus(store, runId);
67791
68540
  return item ? c.json({ item }) : c.json({ code: "run_not_found" }, 404);
@@ -67808,8 +68557,8 @@ Disallow: /
67808
68557
  if (!workflow)
67809
68558
  return c.json({ code: "workflow_not_found", detail: "Workflow was not found." }, 404);
67810
68559
  const { SqliteEventStore: SqliteEventStore2 } = await Promise.resolve().then(() => (init_src3(), exports_src2));
67811
- const dbPath = path11.resolve(process.env.KIFARU_LOCAL_RUNS_DB ?? "data/headless-events.sqlite");
67812
- const outDir = path11.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
68560
+ const dbPath = path12.resolve(process.env.KIFARU_LOCAL_RUNS_DB ?? "data/headless-events.sqlite");
68561
+ const outDir = path12.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
67813
68562
  const runId = randomUUID6();
67814
68563
  const optional3 = new Set(workflow.stages.filter((s) => s.optional).map((s) => s.key));
67815
68564
  const selectedOptionalStageKeys = (selectedStageKeys ?? []).filter((key) => optional3.has(key));
@@ -67833,7 +68582,7 @@ Disallow: /
67833
68582
  permissionMode,
67834
68583
  status: "queued",
67835
68584
  dbPath,
67836
- reportPath: path11.join(outDir, `${runId}.md`)
68585
+ reportPath: path12.join(outDir, `${runId}.md`)
67837
68586
  }, 202);
67838
68587
  });
67839
68588
  const localReportsEnabled = (c) => process.env.KIFARU_LOCAL_RUNS === "1" && (["127.0.0.1", "::1", "localhost"].includes(input2.config.host) || isLoopbackPeer(c));
@@ -67844,14 +68593,14 @@ Disallow: /
67844
68593
  return context2;
67845
68594
  if (!localReportsEnabled(c))
67846
68595
  return c.json({ code: "local_runs_disabled" }, 403);
67847
- const outDir = path11.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
68596
+ const outDir = path12.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
67848
68597
  if (!fs11.existsSync(outDir))
67849
68598
  return c.json({ items: [] });
67850
68599
  const { ReportRecord: ReportRecord2 } = await Promise.resolve().then(() => (init_src(), exports_src));
67851
68600
  const permitted = context2.actor.role === "admin" ? null : workspaceReportIDs(context2.workspace.workspaceID);
67852
68601
  const items = fs11.readdirSync(outDir).filter((name21) => /^[0-9a-f-]{36}\.json$/i.test(name21)).flatMap((name21) => {
67853
68602
  try {
67854
- const record3 = ReportRecord2.parse(JSON.parse(fs11.readFileSync(path11.join(outDir, name21), "utf8")));
68603
+ const record3 = ReportRecord2.parse(JSON.parse(fs11.readFileSync(path12.join(outDir, name21), "utf8")));
67855
68604
  if (record3.runId !== name21.slice(0, -5) || permitted && !permitted.has(record3.runId))
67856
68605
  return [];
67857
68606
  return [{
@@ -67881,12 +68630,12 @@ Disallow: /
67881
68630
  if (!/^[0-9a-f-]{36}$/i.test(runId) || !["json", "md", "xlsx", "pdf"].includes(ext)) {
67882
68631
  return c.json({ code: "invalid_report_path" }, 400);
67883
68632
  }
67884
- const outDir = path11.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
67885
- const filename = path11.join(outDir, `${runId}.${ext}`);
68633
+ const outDir = path12.resolve(process.env.KIFARU_LOCAL_RUNS_OUT ?? "data/headless-reports");
68634
+ const filename = path12.join(outDir, `${runId}.${ext}`);
67886
68635
  if (!fs11.existsSync(filename))
67887
68636
  return c.json({ code: "report_not_found" }, 404);
67888
68637
  if (context2.actor.role !== "admin") {
67889
- const metadata = path11.join(outDir, `${runId}.json`);
68638
+ const metadata = path12.join(outDir, `${runId}.json`);
67890
68639
  if (!fs11.existsSync(metadata) || !workspaceReportIDs(context2.workspace.workspaceID).has(runId)) {
67891
68640
  return c.json({ code: "report_not_found" }, 404);
67892
68641
  }
@@ -68431,7 +69180,7 @@ import {
68431
69180
  defineTool as defineTool3,
68432
69181
  getAgentDir as getAgentDir2
68433
69182
  } from "@earendil-works/pi-coding-agent";
68434
- import path13 from "path";
69183
+ import path14 from "path";
68435
69184
  import fs13 from "fs";
68436
69185
  init_src();
68437
69186
 
@@ -68652,7 +69401,7 @@ import {
68652
69401
  createWriteToolDefinition
68653
69402
  } from "@earendil-works/pi-coding-agent";
68654
69403
  import fs12 from "fs";
68655
- import path12 from "path";
69404
+ import path13 from "path";
68656
69405
  var APPROVAL_TIMEOUT_MS = 10 * 60000;
68657
69406
  var DESTRUCTIVE_COMMANDS = new Set([
68658
69407
  "dd",
@@ -68782,15 +69531,15 @@ Allow it?`,
68782
69531
  return null;
68783
69532
  }
68784
69533
  function pathConsentRequest(target, cwd) {
68785
- const resolved = path12.resolve(cwd, target);
69534
+ const resolved = path13.resolve(cwd, target);
68786
69535
  let canonical = resolved;
68787
69536
  try {
68788
69537
  let probe = resolved;
68789
69538
  while (!fs12.existsSync(probe))
68790
- probe = path12.dirname(probe);
68791
- canonical = path12.join(fs12.realpathSync(probe), resolved.slice(probe.length));
69539
+ probe = path13.dirname(probe);
69540
+ canonical = path13.join(fs12.realpathSync(probe), resolved.slice(probe.length));
68792
69541
  } catch {}
68793
- const inside = canonical === cwd || canonical.startsWith(`${cwd}${path12.sep}`);
69542
+ const inside = canonical === cwd || canonical.startsWith(`${cwd}${path13.sep}`);
68794
69543
  if (inside)
68795
69544
  return null;
68796
69545
  return {
@@ -73158,7 +73907,7 @@ class WorkbenchAgentService {
73158
73907
  fs13.mkdirSync(config3.workbenchCwd, { recursive: true });
73159
73908
  this.cwd = fs13.realpathSync(config3.workbenchCwd);
73160
73909
  this.skillsPath = materializeKifaruSkills();
73161
- this.registryPath = path13.join(getAgentDir2(), "workbench-conversations.json");
73910
+ this.registryPath = path14.join(getAgentDir2(), "workbench-conversations.json");
73162
73911
  this.saved = fs13.existsSync(this.registryPath) ? JSON.parse(fs13.readFileSync(this.registryPath, "utf8")) : [];
73163
73912
  for (const saved of this.saved) {
73164
73913
  for (const status of ["queued", "running"]) {
@@ -73385,7 +74134,7 @@ class WorkbenchAgentService {
73385
74134
  this.persistRegistry();
73386
74135
  }
73387
74136
  persistRegistry() {
73388
- fs13.mkdirSync(path13.dirname(this.registryPath), { recursive: true });
74137
+ fs13.mkdirSync(path14.dirname(this.registryPath), { recursive: true });
73389
74138
  const temporary = `${this.registryPath}.tmp`;
73390
74139
  fs13.writeFileSync(temporary, JSON.stringify(this.saved), { mode: 384 });
73391
74140
  fs13.renameSync(temporary, this.registryPath);
@@ -73988,8 +74737,8 @@ class WorkbenchAgentService {
73988
74737
  function isSharedSessionPath(sessionPath) {
73989
74738
  try {
73990
74739
  const resolved = fs13.realpathSync(sessionPath);
73991
- const root = fs13.realpathSync(path13.join(getAgentDir2(), "sessions"));
73992
- return resolved.endsWith(".jsonl") && resolved.startsWith(root + path13.sep) && fs13.statSync(resolved).isFile();
74740
+ const root = fs13.realpathSync(path14.join(getAgentDir2(), "sessions"));
74741
+ return resolved.endsWith(".jsonl") && resolved.startsWith(root + path14.sep) && fs13.statSync(resolved).isFile();
73993
74742
  } catch {
73994
74743
  return false;
73995
74744
  }
@@ -74295,13 +75044,66 @@ function workbenchSocketHandlers(service) {
74295
75044
  };
74296
75045
  }
74297
75046
 
75047
+ // src/local-run-supervisor.ts
75048
+ import { spawn } from "child_process";
75049
+ import fs14 from "fs";
75050
+ import path15 from "path";
75051
+ function localRunConcurrency(value = process.env.KIFARU_LOCAL_RUN_CONCURRENCY) {
75052
+ const parsed = Number(value ?? "3");
75053
+ return Number.isInteger(parsed) && parsed > 0 ? Math.min(parsed, 8) : 3;
75054
+ }
75055
+ function localRunWorkerEntry(fromDir = import.meta.dir) {
75056
+ const bundled = path15.join(fromDir, "kifaru-local-run-worker.js");
75057
+ if (fs14.existsSync(bundled))
75058
+ return bundled;
75059
+ const source = path15.join(fromDir, "local-run-worker.ts");
75060
+ return fs14.existsSync(source) ? source : null;
75061
+ }
75062
+ function startLocalRunWorkers(options = {}) {
75063
+ const env = options.env ?? process.env;
75064
+ if (env.KIFARU_LOCAL_RUNS !== "1")
75065
+ return () => {};
75066
+ const entry = localRunWorkerEntry(options.fromDir);
75067
+ if (!entry)
75068
+ throw new Error("Local headless worker entry is missing from this Kifaru installation");
75069
+ const children = new Set;
75070
+ let stopping = false;
75071
+ const count = localRunConcurrency(env.KIFARU_LOCAL_RUN_CONCURRENCY);
75072
+ const launch = (slot) => {
75073
+ if (stopping)
75074
+ return;
75075
+ const child = spawn(process.execPath, [entry], { env, stdio: "inherit" });
75076
+ children.add(child);
75077
+ child.on("exit", (code) => {
75078
+ children.delete(child);
75079
+ if (!stopping) {
75080
+ if (code !== 0)
75081
+ console.error(`[local-run] worker ${slot} exited (${code ?? "signal"}); restarting`);
75082
+ setTimeout(() => launch(slot), code === 0 ? 100 : 1000);
75083
+ }
75084
+ });
75085
+ child.on("error", (error92) => console.error(`[local-run] worker ${slot} failed:`, error92));
75086
+ };
75087
+ for (let slot = 1;slot <= count; slot++)
75088
+ launch(slot);
75089
+ console.log(`[local-run] ${count} worker slots ready`);
75090
+ return () => {
75091
+ stopping = true;
75092
+ for (const child of children)
75093
+ child.kill("SIGTERM");
75094
+ };
75095
+ }
75096
+
74298
75097
  // src/index.ts
74299
75098
  var config3 = loadConfig();
75099
+ if (process.env.KIFARU_LOCAL_RUNS === undefined && ["127.0.0.1", "::1", "localhost"].includes(config3.host)) {
75100
+ process.env.KIFARU_LOCAL_RUNS = "1";
75101
+ }
74300
75102
  var catalog = loadModelCatalog(config3);
74301
75103
  var storage = createSqliteStorage(config3.sessionDbPath);
74302
75104
  var workbench = config3.workbenchCwd ? await createWorkbench() : undefined;
74303
75105
  async function createWorkbench() {
74304
- fs14.mkdirSync(config3.workbenchCwd, { recursive: true });
75106
+ fs15.mkdirSync(config3.workbenchCwd, { recursive: true });
74305
75107
  try {
74306
75108
  const service = await WorkbenchAgentService.create(config3, catalog, createPlatformRepository(storage.db));
74307
75109
  console.log(`[session-gateway] live workspace enabled at ${config3.workbenchCwd}`);
@@ -74354,6 +75156,7 @@ var server = Bun.serve({
74354
75156
  return new Response("Internal server error", { status: 500 });
74355
75157
  }
74356
75158
  });
75159
+ var stopLocalRunWorkers = startLocalRunWorkers();
74357
75160
  console.log(`[session-gateway] listening on http://${server.hostname}:${server.port} with ${Object.keys(catalog.models).length} model(s)`);
74358
75161
  if (config3.hasOAuthProviders) {
74359
75162
  console.log(`[session-gateway] OAuth providers enabled: ${config3.oauthProviderIds.join(", ")}`);
@@ -74365,6 +75168,7 @@ if (config3.hasBootstrapToken) {
74365
75168
  }
74366
75169
  for (const signal of ["SIGINT", "SIGTERM"]) {
74367
75170
  process.on(signal, () => {
75171
+ stopLocalRunWorkers();
74368
75172
  storage.close();
74369
75173
  server.stop(true);
74370
75174
  process.exit(0);