@akasecurity/cli 0.9.10 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/dist/cli.js +1466 -766
- package/extension/manifest.json +1 -1
- package/native-host/host.js +756 -528
- package/native-host/scan-worker.js +153 -58
- package/native-host/sync.js +284 -103
- package/package.json +5 -5
- package/web-ui/package.json +1 -1
- package/web-ui/web-ui/.next/BUILD_ID +1 -1
- package/web-ui/web-ui/.next/app-build-manifest.json +64 -64
- package/web-ui/web-ui/.next/app-path-routes-manifest.json +5 -5
- package/web-ui/web-ui/.next/build-manifest.json +2 -2
- package/web-ui/web-ui/.next/prerender-manifest.json +14 -14
- package/web-ui/web-ui/.next/server/app/(app)/activity/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/activity/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/activity/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/data-shares/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/detections/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/detections/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/detections/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/exceptions/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/findings/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/findings/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/findings/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/inventory/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/policies/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/policies/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/policies/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/scan/page.js +3 -3
- package/web-ui/web-ui/.next/server/app/(app)/scan/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/scan/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/security/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/security/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/security/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/settings/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/settings/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/updates/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/updates/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/updates/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/vault/page.js +2 -2
- package/web-ui/web-ui/.next/server/app/(app)/vault/page.js.nft.json +1 -1
- package/web-ui/web-ui/.next/server/app/(app)/vault/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found.html +1 -1
- package/web-ui/web-ui/.next/server/app/_not-found.rsc +2 -2
- package/web-ui/web-ui/.next/server/app/index.html +1 -1
- package/web-ui/web-ui/.next/server/app/index.rsc +2 -2
- package/web-ui/web-ui/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/app-paths-manifest.json +5 -5
- package/web-ui/web-ui/.next/server/chunks/471.js +950 -0
- package/web-ui/web-ui/.next/server/chunks/475.js +1 -1
- package/web-ui/web-ui/.next/server/chunks/7.js +1 -1
- package/web-ui/web-ui/.next/server/chunks/739.js +1 -0
- package/web-ui/web-ui/.next/server/chunks/766.js +6 -0
- package/web-ui/web-ui/.next/server/chunks/851.js +1 -1
- package/web-ui/web-ui/.next/server/chunks/905.js +959 -0
- package/web-ui/web-ui/.next/server/functions-config-manifest.json +5 -5
- package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
- package/web-ui/web-ui/.next/server/pages/404.html +1 -1
- package/web-ui/web-ui/.next/server/pages/500.html +1 -1
- package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
- package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
- package/web-ui/web-ui/.next/static/chunks/907-2ba9428e7818b656.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-c5a55618d187c906.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-b1c76b8c00e1ae2a.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-159167b47a005b33.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/error-dead9507fc349f26.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-5e872a82af85ef64.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-0695d0006518b2cb.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-adec99f2f4bbc180.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-d013384d2ddb3036.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-42c1b48a01fdecb0.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-dc7ae7664dcdc3c5.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-9b6d7cc4ce9fb33e.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-f3ee9ecde06df153.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-24a003c709776317.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-4ea49ce2e765fffb.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-6c7b94b070de8986.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-f37ab8e1edc67663.js +1 -0
- package/web-ui/web-ui/.next/static/css/c6954dd65f2115e6.css +2 -0
- package/web-ui/web-ui/dist/scan-worker.js +153 -58
- package/web-ui/web-ui/.next/server/chunks/389.js +0 -903
- package/web-ui/web-ui/.next/server/chunks/667.js +0 -894
- package/web-ui/web-ui/.next/server/chunks/950.js +0 -6
- package/web-ui/web-ui/.next/static/chunks/907-207847c57011ba44.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-e4f94a75c631ce86.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-d70fcc5612f380b7.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-e1823ce15bb6277d.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/error-14165283b00c8626.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-0fcffe4b9dff201f.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-c95079a2acd284fd.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-4b9f034bd078f5d3.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-b97fc083884ef341.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-e96e7e947ed5f279.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-83072d434c6b6b0e.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-b90748d139a546c9.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-7a46abe149271870.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-f4a1ee202d6e60a4.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-2ec6d863f1a31792.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-b388003fb0d3efd1.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-6bda74d234a1376a.js +0 -1
- package/web-ui/web-ui/.next/static/css/81a7ed955b02c959.css +0 -2
- /package/web-ui/web-ui/.next/static/{k7tk0MLkPITbi_x6JL01X → osEmGZG6yEV-XkQlklt-B}/_buildManifest.js +0 -0
- /package/web-ui/web-ui/.next/static/{k7tk0MLkPITbi_x6JL01X → osEmGZG6yEV-XkQlklt-B}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -552,7 +552,66 @@ function runInherit(command, args) {
|
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
+
// ../packages/local-ops/src/semver.ts
|
|
556
|
+
function parse(version2) {
|
|
557
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-.]+))?$/.exec(version2.trim());
|
|
558
|
+
if (!match) return null;
|
|
559
|
+
return {
|
|
560
|
+
core: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
561
|
+
pre: match[4] ? match[4].split(".") : []
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function isSemver(version2) {
|
|
565
|
+
return parse(version2) !== null;
|
|
566
|
+
}
|
|
567
|
+
function comparePre(a, b) {
|
|
568
|
+
if (a.length === 0 && b.length === 0) return 0;
|
|
569
|
+
if (a.length === 0) return 1;
|
|
570
|
+
if (b.length === 0) return -1;
|
|
571
|
+
const len = Math.min(a.length, b.length);
|
|
572
|
+
for (let i = 0; i < len; i++) {
|
|
573
|
+
const ai = a[i] ?? "";
|
|
574
|
+
const bi = b[i] ?? "";
|
|
575
|
+
if (ai === bi) continue;
|
|
576
|
+
const an = /^\d+$/.test(ai);
|
|
577
|
+
const bn = /^\d+$/.test(bi);
|
|
578
|
+
if (an && !bn) return -1;
|
|
579
|
+
if (!an && bn) return 1;
|
|
580
|
+
if (an && bn) return Number(ai) < Number(bi) ? -1 : 1;
|
|
581
|
+
return ai < bi ? -1 : 1;
|
|
582
|
+
}
|
|
583
|
+
return a.length === b.length ? 0 : a.length < b.length ? -1 : 1;
|
|
584
|
+
}
|
|
585
|
+
function compareSemver(a, b) {
|
|
586
|
+
const pa = parse(a);
|
|
587
|
+
const pb = parse(b);
|
|
588
|
+
if (!pa || !pb) return 0;
|
|
589
|
+
for (let i = 0; i < 3; i++) {
|
|
590
|
+
const av = pa.core[i] ?? 0;
|
|
591
|
+
const bv = pb.core[i] ?? 0;
|
|
592
|
+
if (av !== bv) return av < bv ? -1 : 1;
|
|
593
|
+
}
|
|
594
|
+
return comparePre(pa.pre, pb.pre);
|
|
595
|
+
}
|
|
596
|
+
function isNewer(latest, installed) {
|
|
597
|
+
return compareSemver(latest, installed) > 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
555
600
|
// ../packages/local-ops/src/cli-plugin-manager.ts
|
|
601
|
+
var LEADING_WRAP = /^[([{"'`]{0,2}[vV]?/;
|
|
602
|
+
var TRAILING_WRAP = /[)\]}"'`,;]{0,4}$/;
|
|
603
|
+
function versionTokenFrom(stdout) {
|
|
604
|
+
const [firstLine = ""] = stdout.split("\n");
|
|
605
|
+
for (const token of [...firstLine.trim().split(/\s+/), ...stdout.split(/\s+/)]) {
|
|
606
|
+
const candidate = token.replace(LEADING_WRAP, "").replace(TRAILING_WRAP, "");
|
|
607
|
+
if (isSemver(candidate)) return candidate;
|
|
608
|
+
}
|
|
609
|
+
return void 0;
|
|
610
|
+
}
|
|
611
|
+
function hostCliVersion(bin) {
|
|
612
|
+
const { ok, stdout } = runCapture(bin, ["--version"], 5e3);
|
|
613
|
+
return ok ? versionTokenFrom(stdout) : void 0;
|
|
614
|
+
}
|
|
556
615
|
var HOST_VERBS = {
|
|
557
616
|
claude: {
|
|
558
617
|
install: (ref) => [["plugin", "install", ref]],
|
|
@@ -1002,7 +1061,7 @@ __export(external_exports, {
|
|
|
1002
1061
|
optional: () => optional,
|
|
1003
1062
|
output: () => output,
|
|
1004
1063
|
overwrite: () => _overwrite,
|
|
1005
|
-
parse: () =>
|
|
1064
|
+
parse: () => parse3,
|
|
1006
1065
|
parseAsync: () => parseAsync2,
|
|
1007
1066
|
partialRecord: () => partialRecord,
|
|
1008
1067
|
pipe: () => pipe,
|
|
@@ -1346,7 +1405,7 @@ __export(core_exports2, {
|
|
|
1346
1405
|
memoizer: () => memoizer,
|
|
1347
1406
|
mergeValues: () => mergeValues,
|
|
1348
1407
|
meta: () => meta,
|
|
1349
|
-
parse: () =>
|
|
1408
|
+
parse: () => parse2,
|
|
1350
1409
|
parseAsync: () => parseAsync,
|
|
1351
1410
|
parseURLObject: () => parseURLObject,
|
|
1352
1411
|
prettifyError: () => prettifyError,
|
|
@@ -2549,7 +2608,7 @@ var _parse = (_Err) => {
|
|
|
2549
2608
|
};
|
|
2550
2609
|
return fn;
|
|
2551
2610
|
};
|
|
2552
|
-
var
|
|
2611
|
+
var parse2 = /* @__PURE__ */ _parse($ZodRealError);
|
|
2553
2612
|
var _parseAsync = (_Err) => {
|
|
2554
2613
|
const fn = async (schema, value, _ctx, params) => {
|
|
2555
2614
|
const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
@@ -5565,10 +5624,10 @@ var $ZodFunction = /* @__PURE__ */ $constructor("$ZodFunction", (inst, def) => {
|
|
|
5565
5624
|
throw new Error("implement() must be called with a function");
|
|
5566
5625
|
}
|
|
5567
5626
|
return Object.defineProperty(function(...args) {
|
|
5568
|
-
const parsedArgs = inst._def.input ?
|
|
5627
|
+
const parsedArgs = inst._def.input ? parse2(inst._def.input, args) : args;
|
|
5569
5628
|
const result = Reflect.apply(func, this, parsedArgs);
|
|
5570
5629
|
if (inst._def.output) {
|
|
5571
|
-
return
|
|
5630
|
+
return parse2(inst._def.output, result);
|
|
5572
5631
|
}
|
|
5573
5632
|
return result;
|
|
5574
5633
|
}, "_zod", { value: inst._zod, enumerable: false });
|
|
@@ -17438,7 +17497,7 @@ var ZodRealError = /* @__PURE__ */ $constructor("ZodError", initializer2, void 0
|
|
|
17438
17497
|
});
|
|
17439
17498
|
|
|
17440
17499
|
// ../node_modules/.pnpm/zod@4.5.4/node_modules/zod/v4/classic/parse.js
|
|
17441
|
-
var
|
|
17500
|
+
var parse3 = /* @__PURE__ */ _parse(ZodRealError);
|
|
17442
17501
|
var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
17443
17502
|
var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
17444
17503
|
var safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
@@ -17575,7 +17634,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
17575
17634
|
util_exports.own(this, "~standard", value);
|
|
17576
17635
|
},
|
|
17577
17636
|
parse: function _parse2(data, params) {
|
|
17578
|
-
return
|
|
17637
|
+
return parse3(this, data, params, { callee: _parse2 });
|
|
17579
17638
|
},
|
|
17580
17639
|
parseAsync: async function _parseAsync2(data, params) {
|
|
17581
17640
|
return await parseAsync2(this, data, params, { callee: _parseAsync2 });
|
|
@@ -20601,6 +20660,101 @@ function epochMillisToIso(ms) {
|
|
|
20601
20660
|
return new Date(ms).toISOString();
|
|
20602
20661
|
}
|
|
20603
20662
|
|
|
20663
|
+
// ../packages/schema/src/security/recommendations.ts
|
|
20664
|
+
var SEVERITY_WEIGHT = {
|
|
20665
|
+
critical: 4,
|
|
20666
|
+
high: 3,
|
|
20667
|
+
medium: 2,
|
|
20668
|
+
low: 1
|
|
20669
|
+
};
|
|
20670
|
+
var SEVERITY_WEIGHT_BY_STRING = SEVERITY_WEIGHT;
|
|
20671
|
+
function severityWeight(severity) {
|
|
20672
|
+
return SEVERITY_WEIGHT_BY_STRING[severity] ?? 0;
|
|
20673
|
+
}
|
|
20674
|
+
var ADVICE = {
|
|
20675
|
+
secret: "Rotate the exposed credentials and move them out of prompts (secrets manager / env vars).",
|
|
20676
|
+
pii: "Remove or mask personal data before it reaches the model.",
|
|
20677
|
+
financial: "Strip card and account numbers; share only non-sensitive references.",
|
|
20678
|
+
phi: "Remove protected health information \u2014 it should never reach an external model.",
|
|
20679
|
+
code_context: "Confirm this proprietary code context is safe to share.",
|
|
20680
|
+
code_flaw: "Review the flagged pattern and apply the secure alternative (parameterized queries, safe deserializers, etc.).",
|
|
20681
|
+
config: "Review the setting \u2014 a hook conflict or an egress change applies to every session that follows.",
|
|
20682
|
+
custom: "Review against your organization\u2019s custom policy."
|
|
20683
|
+
};
|
|
20684
|
+
var REC_TEMPLATE = {
|
|
20685
|
+
secret: { title: "Exposed secret detected", action: "Rotate" },
|
|
20686
|
+
pii: { title: "Personal data in a prompt", action: "Remove" },
|
|
20687
|
+
financial: { title: "Financial data detected", action: "Strip" },
|
|
20688
|
+
phi: { title: "Health information detected", action: "Remove" },
|
|
20689
|
+
code_context: { title: "Proprietary code shared", action: "Review" },
|
|
20690
|
+
code_flaw: { title: "Insecure code pattern", action: "Fix" },
|
|
20691
|
+
config: { title: "Weakened configuration", action: "Review" },
|
|
20692
|
+
custom: { title: "Custom policy match", action: "Review" }
|
|
20693
|
+
};
|
|
20694
|
+
var MAX_RECOMMENDATIONS = 10;
|
|
20695
|
+
function bucketizeRecommendations(findings) {
|
|
20696
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
20697
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
20698
|
+
for (const f of findings) {
|
|
20699
|
+
const n = f.count ?? 1;
|
|
20700
|
+
byRule.set(f.ruleId, (byRule.get(f.ruleId) ?? 0) + n);
|
|
20701
|
+
const b = buckets.get(f.category) ?? {
|
|
20702
|
+
category: f.category,
|
|
20703
|
+
count: 0,
|
|
20704
|
+
categoryCount: 0,
|
|
20705
|
+
severity: f.severity,
|
|
20706
|
+
weight: 0,
|
|
20707
|
+
ruleId: f.ruleId
|
|
20708
|
+
};
|
|
20709
|
+
b.categoryCount += n;
|
|
20710
|
+
const w = severityWeight(f.severity);
|
|
20711
|
+
if (w > b.weight) {
|
|
20712
|
+
b.weight = w;
|
|
20713
|
+
b.severity = f.severity;
|
|
20714
|
+
b.ruleId = f.ruleId;
|
|
20715
|
+
}
|
|
20716
|
+
buckets.set(f.category, b);
|
|
20717
|
+
}
|
|
20718
|
+
for (const b of buckets.values()) b.count = byRule.get(b.ruleId) ?? 0;
|
|
20719
|
+
return [...buckets.values()].sort((a, b) => b.weight - a.weight || b.categoryCount - a.categoryCount).slice(0, MAX_RECOMMENDATIONS);
|
|
20720
|
+
}
|
|
20721
|
+
function buildRecommendations(findings) {
|
|
20722
|
+
return bucketizeRecommendations(findings).map((b) => {
|
|
20723
|
+
const copy = recommendationCopy(b.category);
|
|
20724
|
+
return {
|
|
20725
|
+
severity: b.severity,
|
|
20726
|
+
title: copy.title,
|
|
20727
|
+
description: copy.advice,
|
|
20728
|
+
context: `${b.ruleId} \xB7 ${String(b.count)} finding${b.count === 1 ? "" : "s"}`,
|
|
20729
|
+
action: copy.action
|
|
20730
|
+
};
|
|
20731
|
+
});
|
|
20732
|
+
}
|
|
20733
|
+
function recommendationCopy(category) {
|
|
20734
|
+
const template = REC_TEMPLATE_BY_STRING[category] ?? {
|
|
20735
|
+
title: `${category} finding`,
|
|
20736
|
+
action: "Review"
|
|
20737
|
+
};
|
|
20738
|
+
return {
|
|
20739
|
+
...template,
|
|
20740
|
+
advice: ADVICE_BY_STRING[category] ?? "Review this finding against your policy."
|
|
20741
|
+
};
|
|
20742
|
+
}
|
|
20743
|
+
var ADVICE_BY_STRING = ADVICE;
|
|
20744
|
+
var REC_TEMPLATE_BY_STRING = REC_TEMPLATE;
|
|
20745
|
+
function healthScore(summary) {
|
|
20746
|
+
const handled = summary.byAction.block + summary.byAction.redact + summary.byAction.warn;
|
|
20747
|
+
const handledRatio = summary.findings === 0 ? 1 : handled / summary.findings;
|
|
20748
|
+
return Math.round(100 * (0.6 * summary.coverage + 0.4 * handledRatio));
|
|
20749
|
+
}
|
|
20750
|
+
function findingStatus(summary) {
|
|
20751
|
+
return {
|
|
20752
|
+
score: healthScore(summary),
|
|
20753
|
+
unreviewed: { ...summary.bySeverity },
|
|
20754
|
+
openFindings: summary.findings
|
|
20755
|
+
};
|
|
20756
|
+
}
|
|
20757
|
+
|
|
20604
20758
|
// ../packages/schema/src/token/cost-model.ts
|
|
20605
20759
|
var PROVIDER_PLATFORM = /* @__PURE__ */ new Map([
|
|
20606
20760
|
["anthropic", "anthropic"],
|
|
@@ -20986,13 +21140,11 @@ var FindingGroup = external_exports.object({
|
|
|
20986
21140
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
20987
21141
|
instances: external_exports.array(FindingInstance),
|
|
20988
21142
|
// Derived from instances' statuses with open-dominates precedence (see
|
|
20989
|
-
//
|
|
21143
|
+
// foldGroupStatus). Undefined only when no instance carries a status.
|
|
20990
21144
|
status: FindingStatus.optional(),
|
|
20991
|
-
// The distinct people across the WHOLE group, not just the
|
|
20992
|
-
//
|
|
20993
|
-
//
|
|
20994
|
-
// instance carries a user, or when the store supplied whole-group folds
|
|
20995
|
-
// without one.
|
|
21145
|
+
// The distinct people across the WHOLE group, not just the instances
|
|
21146
|
+
// carried here. Undefined when no instance carries a user, or when the
|
|
21147
|
+
// store supplied whole-group folds without one.
|
|
20996
21148
|
users: external_exports.array(FindingUser).optional()
|
|
20997
21149
|
}).meta({ id: "FindingGroup" });
|
|
20998
21150
|
var FindingStats = external_exports.object({
|
|
@@ -21021,21 +21173,31 @@ var FindingFacets = external_exports.object({
|
|
|
21021
21173
|
// counted under no value.
|
|
21022
21174
|
status: external_exports.array(FindingFacetItem),
|
|
21023
21175
|
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
21024
|
-
// reads, which can filter by it; the
|
|
21176
|
+
// reads, which can filter by it; the type-level read omits the dimension
|
|
21025
21177
|
// because a group spans tools.
|
|
21026
21178
|
tool: external_exports.array(FindingFacetItem).optional()
|
|
21027
21179
|
}).meta({ id: "FindingFacets" });
|
|
21028
|
-
var
|
|
21029
|
-
|
|
21180
|
+
var FindingTypeSummary = FindingGroup.omit({ instances: true, match: true }).meta({
|
|
21181
|
+
id: "FindingTypeSummary"
|
|
21182
|
+
});
|
|
21183
|
+
var DEFAULT_FINDING_TYPES_LIMIT = 50;
|
|
21184
|
+
var MAX_FINDING_TYPES_LIMIT = 100;
|
|
21185
|
+
var ListFindingTypesQuery = external_exports.object({
|
|
21030
21186
|
// NOTE: severity filters by Severity (critical/high/medium/low), not by
|
|
21031
|
-
// FindingAction.
|
|
21187
|
+
// FindingAction. It narrows TYPES: a type's severity is the one its newest
|
|
21188
|
+
// firing version carries, and this list pages types.
|
|
21189
|
+
//
|
|
21190
|
+
// That is NOT a claim the findings of a type share it. A rule can hold several
|
|
21191
|
+
// definition versions at different severities, so a type kept by this filter
|
|
21192
|
+
// can hold findings that individually do not match — see totals.findings on
|
|
21193
|
+
// ListFindingTypesResponse, which counts them all.
|
|
21032
21194
|
severity: external_exports.array(Severity).optional(),
|
|
21033
21195
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
21034
21196
|
provider: external_exports.array(FindingProvider).optional(),
|
|
21035
21197
|
action: external_exports.array(FindingAction).optional(),
|
|
21036
|
-
// Matches a
|
|
21037
|
-
// individual
|
|
21038
|
-
//
|
|
21198
|
+
// Matches a type's DERIVED status (see FindingGroup.status), not its
|
|
21199
|
+
// individual findings' — so a filtered row's status always reads one of the
|
|
21200
|
+
// requested values.
|
|
21039
21201
|
status: external_exports.array(FindingStatus).optional(),
|
|
21040
21202
|
q: external_exports.string().optional(),
|
|
21041
21203
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
@@ -21045,23 +21207,37 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
21045
21207
|
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
21046
21208
|
// means all time — this list has no default window.
|
|
21047
21209
|
from: external_exports.iso.datetime().optional(),
|
|
21048
|
-
// A
|
|
21049
|
-
//
|
|
21050
|
-
//
|
|
21051
|
-
//
|
|
21052
|
-
//
|
|
21210
|
+
// A RULE id that must appear in the page even when the cursor has already
|
|
21211
|
+
// advanced past its sort position. This is what keeps the selected type
|
|
21212
|
+
// visible in the list once it paginates: the target is appended out of sort
|
|
21213
|
+
// order rather than scanned forward for. Never affects totals, facets or the
|
|
21214
|
+
// cursor. Unlike the grouped read this replaces, it names a rule only — an
|
|
21215
|
+
// instance id is resolved by `findingInstance`, which is a primary-key seek
|
|
21216
|
+
// and so is not bounded by what any page happens to hold.
|
|
21053
21217
|
includeId: external_exports.string().optional(),
|
|
21054
|
-
|
|
21055
|
-
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
21218
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_TYPES_LIMIT).optional(),
|
|
21056
21219
|
cursor: external_exports.string().optional()
|
|
21057
21220
|
});
|
|
21058
|
-
var
|
|
21221
|
+
var ListFindingTypesResponse = external_exports.object({
|
|
21059
21222
|
totals: external_exports.object({
|
|
21223
|
+
// Findings belonging to the matching TYPES — not findings that each match
|
|
21224
|
+
// the filters. The filters here select types, so a type that survives
|
|
21225
|
+
// contributes its whole instanceCount.
|
|
21226
|
+
//
|
|
21227
|
+
// `status` is the one exception, narrowed per finding via
|
|
21228
|
+
// countInstancesByStatus. `severity`, `provider` and `action` are not, so
|
|
21229
|
+
// this can exceed what the instance read reports for the same filters: a
|
|
21230
|
+
// rule whose severity moved between versions is kept on its newest and
|
|
21231
|
+
// still counts its older findings. Narrowing the other three needs
|
|
21232
|
+
// per-dimension counts the aggregate does not carry today.
|
|
21060
21233
|
findings: external_exports.number().int().nonnegative(),
|
|
21061
|
-
|
|
21234
|
+
// Counts TYPES, which is the unit this read pages. The instance read's
|
|
21235
|
+
// own totals count findings; the two deliberately answer different
|
|
21236
|
+
// questions and are never summed.
|
|
21237
|
+
types: external_exports.number().int().nonnegative()
|
|
21062
21238
|
}),
|
|
21063
21239
|
facets: FindingFacets,
|
|
21064
|
-
items: external_exports.array(
|
|
21240
|
+
items: external_exports.array(FindingTypeSummary),
|
|
21065
21241
|
nextCursor: external_exports.string().nullable(),
|
|
21066
21242
|
// Present only on session-scoped queries (`sessionId` set): per ruleId, how
|
|
21067
21243
|
// many times that rule fired in the session's persisted transcript. Findings
|
|
@@ -21069,7 +21245,7 @@ var ListGroupedFindingsResponse = external_exports.object({
|
|
|
21069
21245
|
// every firing, so the two numbers legitimately differ — this map lets a
|
|
21070
21246
|
// session-scoped view show both.
|
|
21071
21247
|
sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
|
|
21072
|
-
}).meta({ id: "
|
|
21248
|
+
}).meta({ id: "ListFindingTypesResponse" });
|
|
21073
21249
|
var ApplyFindingActionRequest = external_exports.object({
|
|
21074
21250
|
// 'quarantined' is system-assigned (see FindingAction) — clients may not set
|
|
21075
21251
|
// it, so it is excluded from the request contract. The mapping helper
|
|
@@ -21099,12 +21275,13 @@ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
|
21099
21275
|
var MAX_FLAT_FINDINGS_LIMIT = 200;
|
|
21100
21276
|
var ListFindingInstancesQuery = external_exports.object({
|
|
21101
21277
|
severity: external_exports.array(Severity).optional(),
|
|
21102
|
-
// Rule ids, the same vocabulary the
|
|
21278
|
+
// Rule ids, the same vocabulary the types list's `subtype` carries. Pinning
|
|
21279
|
+
// ONE of them is how the master/detail view scopes its right-hand panel.
|
|
21103
21280
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
21104
21281
|
provider: external_exports.array(FindingProvider).optional(),
|
|
21105
21282
|
action: external_exports.array(FindingAction).optional(),
|
|
21106
21283
|
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
21107
|
-
// the
|
|
21284
|
+
// the types query's type-level fold.
|
|
21108
21285
|
status: external_exports.array(FindingStatus).optional(),
|
|
21109
21286
|
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
21110
21287
|
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
@@ -21121,37 +21298,47 @@ var ListFindingInstancesQuery = external_exports.object({
|
|
|
21121
21298
|
});
|
|
21122
21299
|
var ListFindingInstancesResponse = external_exports.object({
|
|
21123
21300
|
// Instances matching the filters across the whole scope, not just this
|
|
21124
|
-
// page — cursor-independent, like the
|
|
21301
|
+
// page — cursor-independent, like the types list's totals.
|
|
21125
21302
|
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
21126
|
-
// Counts in INSTANCES here, where the
|
|
21303
|
+
// Counts in INSTANCES here, where the types response counts types. Each
|
|
21127
21304
|
// dimension still excludes its own filter.
|
|
21128
21305
|
facets: FindingFacets,
|
|
21129
21306
|
items: external_exports.array(FindingInstanceDetail),
|
|
21130
21307
|
nextCursor: external_exports.string().nullable()
|
|
21131
21308
|
}).meta({ id: "ListFindingInstancesResponse" });
|
|
21132
|
-
var
|
|
21133
|
-
//
|
|
21134
|
-
//
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
// Folded from the instances' derived statuses with the same
|
|
21140
|
-
// open-dominates precedence a group uses.
|
|
21141
|
-
status: FindingStatus.optional(),
|
|
21142
|
-
// Distinct rules seen at this location, capped — the row shows them as
|
|
21143
|
-
// chips, and the count is what conveys scale.
|
|
21144
|
-
ruleIds: external_exports.array(external_exports.string())
|
|
21145
|
-
}).meta({ id: "FindingLocationFile" });
|
|
21146
|
-
var FindingLocationRepo = external_exports.object({
|
|
21309
|
+
var FindingLocationSummary = external_exports.object({
|
|
21310
|
+
// Opaque, stable, minted from the pair by encodeLocationId. It exists
|
|
21311
|
+
// because a location's identity is two values and a URL param carries one:
|
|
21312
|
+
// `?loc=` names a location the way `?rule=` names a type. Only ever compared
|
|
21313
|
+
// for EQUALITY — the page's selection check, this read's `includeId`, the
|
|
21314
|
+
// client's page dedupe — never decoded, and never a sort key.
|
|
21315
|
+
id: external_exports.string(),
|
|
21147
21316
|
/** Empty when the instances carried no repo attribute. */
|
|
21148
21317
|
repo: external_exports.string(),
|
|
21318
|
+
// Empty when the instances carried no file path (a prompt, or a tool call
|
|
21319
|
+
// with no file attribution). Both halves empty is a real location — usually
|
|
21320
|
+
// the largest one in a store — and is selectable like any other.
|
|
21321
|
+
file: external_exports.string(),
|
|
21149
21322
|
instanceCount: external_exports.number().int().nonnegative(),
|
|
21323
|
+
// The WORST severity present, not the first row's. It is this list's primary
|
|
21324
|
+
// sort key, so it is also what explains why a row is where it is, and it is
|
|
21325
|
+
// how a reader decides what to open without opening everything.
|
|
21150
21326
|
maxSeverity: Severity,
|
|
21151
21327
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
21328
|
+
// Folded from the instances' derived statuses with the same open-dominates
|
|
21329
|
+
// precedence a group uses, so it answers "is anything left to do here" and
|
|
21330
|
+
// not much more: a location holding 1 open among 40 resolved reads like one
|
|
21331
|
+
// holding 40 open. That loss is accepted — the panel beside this list
|
|
21332
|
+
// carries each finding's own status, and instanceCount sits next to the
|
|
21333
|
+
// badge.
|
|
21152
21334
|
status: FindingStatus.optional(),
|
|
21153
|
-
|
|
21154
|
-
|
|
21335
|
+
// Every distinct rule seen at this location, UNCAPPED — so the length is a
|
|
21336
|
+
// tally rather than a sample and a row can say how many there are. Bounded
|
|
21337
|
+
// by the ruleset, not by the store. The view bounds what it DISPLAYS.
|
|
21338
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
21339
|
+
}).meta({ id: "FindingLocationSummary" });
|
|
21340
|
+
var DEFAULT_FINDING_LOCATIONS_LIMIT = 50;
|
|
21341
|
+
var MAX_FINDING_LOCATIONS_LIMIT = 100;
|
|
21155
21342
|
var ListFindingLocationsQuery = external_exports.object({
|
|
21156
21343
|
severity: external_exports.array(Severity).optional(),
|
|
21157
21344
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
@@ -21164,18 +21351,42 @@ var ListFindingLocationsQuery = external_exports.object({
|
|
|
21164
21351
|
q: external_exports.string().optional(),
|
|
21165
21352
|
sessionId: external_exports.string().optional(),
|
|
21166
21353
|
from: external_exports.iso.datetime().optional(),
|
|
21167
|
-
|
|
21354
|
+
// A LOCATION id (see FindingLocationSummary.id) that must appear in the page
|
|
21355
|
+
// even when the cursor has already advanced past its sort position — the
|
|
21356
|
+
// counterpart of ListFindingTypesQuery.includeId, and needed far more often
|
|
21357
|
+
// here. Selecting a row pushes the URL, which re-renders the server and resets
|
|
21358
|
+
// the client's page cache to page 0; with distinct (repo, file) pairs running
|
|
21359
|
+
// into the thousands, a selection sitting off page 0 is the ordinary case
|
|
21360
|
+
// rather than a deep-link corner. Never affects totals, facets or the cursor.
|
|
21361
|
+
includeId: external_exports.string().optional(),
|
|
21362
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_LOCATIONS_LIMIT).optional(),
|
|
21363
|
+
cursor: external_exports.string().optional()
|
|
21168
21364
|
});
|
|
21169
21365
|
var ListFindingLocationsResponse = external_exports.object({
|
|
21170
21366
|
totals: external_exports.object({
|
|
21367
|
+
// Findings matching the filters across the whole scope. Unlike the types
|
|
21368
|
+
// read's same-named field this needs no caveat: the filters here narrow
|
|
21369
|
+
// per finding, so this is the sum of every row's instanceCount.
|
|
21171
21370
|
findings: external_exports.number().int().nonnegative(),
|
|
21172
|
-
|
|
21173
|
-
|
|
21371
|
+
// Counts LOCATIONS, the unit this read pages — the number the paginator
|
|
21372
|
+
// states. The facets beside it count FINDINGS (see below); a surface
|
|
21373
|
+
// showing both says which is which.
|
|
21374
|
+
locations: external_exports.number().int().nonnegative()
|
|
21174
21375
|
}),
|
|
21175
|
-
|
|
21176
|
-
|
|
21177
|
-
|
|
21178
|
-
|
|
21376
|
+
// Counts in FINDINGS, where the types response counts types, each dimension
|
|
21377
|
+
// still excluding its own filter. Deliberately not locations: counting those
|
|
21378
|
+
// needs a set of location keys per dimension per value — memory tracking the
|
|
21379
|
+
// store times the vocabulary, in a read whose scan promises flat memory —
|
|
21380
|
+
// and the cheap per-location version is not an approximation but WRONG. A
|
|
21381
|
+
// location holding {claudecode, block} and {codex, warn} would survive
|
|
21382
|
+
// provider=claudecode AND action=warn, under which no single finding
|
|
21383
|
+
// matches, so the facet would contradict the instanceCount this whole view
|
|
21384
|
+
// rests on. Findings also keep the toolbar in the same unit as the page
|
|
21385
|
+
// tally and the panel it sits above.
|
|
21386
|
+
facets: FindingFacets,
|
|
21387
|
+
/** Sorted by max severity, then most recent, then (repo, file). */
|
|
21388
|
+
items: external_exports.array(FindingLocationSummary),
|
|
21389
|
+
nextCursor: external_exports.string().nullable()
|
|
21179
21390
|
}).meta({ id: "ListFindingLocationsResponse" });
|
|
21180
21391
|
|
|
21181
21392
|
// ../packages/schema/src/zod/meta.ts
|
|
@@ -22344,6 +22555,14 @@ var ControlPlaneErrorBody = external_exports.object({
|
|
|
22344
22555
|
message: external_exports.string().optional()
|
|
22345
22556
|
}).optional()
|
|
22346
22557
|
});
|
|
22558
|
+
var RemoteFailureKind = external_exports.enum([
|
|
22559
|
+
"unauthorized",
|
|
22560
|
+
"forbidden",
|
|
22561
|
+
"route-absent",
|
|
22562
|
+
"invalid-request",
|
|
22563
|
+
"rejected",
|
|
22564
|
+
"unreachable"
|
|
22565
|
+
]);
|
|
22347
22566
|
var AttachDeviceRequest = external_exports.object({
|
|
22348
22567
|
// This machine's own continuity id, so re-attaching ROTATES the credential
|
|
22349
22568
|
// on one machine record instead of producing a second one. Client-minted
|
|
@@ -23049,139 +23268,62 @@ function deriveFindingStatus(row) {
|
|
|
23049
23268
|
if (row.latestResolutionStatus === "dismissed") return "dismissed";
|
|
23050
23269
|
return "open";
|
|
23051
23270
|
}
|
|
23052
|
-
function distinctUsers(instances) {
|
|
23053
|
-
const seen = /* @__PURE__ */ new Set();
|
|
23054
|
-
const users = [];
|
|
23055
|
-
for (const i of instances) {
|
|
23056
|
-
if (i.user === void 0 || seen.has(i.user.id)) continue;
|
|
23057
|
-
seen.add(i.user.id);
|
|
23058
|
-
users.push(i.user);
|
|
23059
|
-
}
|
|
23060
|
-
return users;
|
|
23061
|
-
}
|
|
23062
23271
|
function sortUsers(users) {
|
|
23063
23272
|
return [...users].sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
23064
23273
|
}
|
|
23065
|
-
function
|
|
23066
|
-
const overrides = opts.overrides;
|
|
23274
|
+
function buildFindingTypes(aggregates, opts = {}) {
|
|
23067
23275
|
const packNames = opts.packNames;
|
|
23068
|
-
const
|
|
23069
|
-
const
|
|
23070
|
-
|
|
23071
|
-
const
|
|
23072
|
-
|
|
23073
|
-
else byRuleId.set(row.ruleId, [row]);
|
|
23074
|
-
}
|
|
23075
|
-
const groups = [];
|
|
23076
|
-
for (const [ruleId, ruleRows] of byRuleId) {
|
|
23077
|
-
const instances = ruleRows.map((r) => {
|
|
23078
|
-
const effectiveDbAction = overrides?.get(r.id) ?? r.actionTaken;
|
|
23079
|
-
return {
|
|
23080
|
-
id: r.id,
|
|
23081
|
-
provider: toApiProvider(r.sourceTool),
|
|
23082
|
-
repo: r.repo,
|
|
23083
|
-
file: r.file,
|
|
23084
|
-
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
23085
|
-
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
23086
|
-
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
23087
|
-
...r.user === void 0 ? {} : { user: r.user },
|
|
23088
|
-
action: toApiAction(effectiveDbAction),
|
|
23089
|
-
detectedAt: r.occurredAt,
|
|
23090
|
-
confidence: r.confidence,
|
|
23091
|
-
status: r.status
|
|
23092
|
-
};
|
|
23093
|
-
});
|
|
23094
|
-
const agg = aggregates?.get(ruleId);
|
|
23095
|
-
const users = agg ? sortUsers(agg.users ?? []) : distinctUsers(instances);
|
|
23096
|
-
const latestDetectedAt = agg?.latestDetectedAt ?? ruleRows.reduce(
|
|
23097
|
-
(max, r) => r.occurredAt > max ? r.occurredAt : max,
|
|
23098
|
-
ruleRows[0]?.occurredAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
23099
|
-
);
|
|
23100
|
-
const seenProviders = /* @__PURE__ */ new Set();
|
|
23101
|
-
const providers = (agg ? [...new Set(agg.sourceTools.map(toApiProvider))].sort() : instances.map((i) => i.provider)).filter((p) => {
|
|
23102
|
-
if (seenProviders.has(p)) return false;
|
|
23103
|
-
seenProviders.add(p);
|
|
23104
|
-
return true;
|
|
23105
|
-
});
|
|
23106
|
-
const actionSet = new Set(
|
|
23107
|
-
agg ? agg.actionsTaken.map(toApiAction) : instances.map((i) => i.action)
|
|
23108
|
-
);
|
|
23276
|
+
const types = [];
|
|
23277
|
+
for (const [ruleId, agg] of aggregates) {
|
|
23278
|
+
const users = sortUsers(agg.users ?? []);
|
|
23279
|
+
const providers = [...new Set(agg.sourceTools.map(toApiProvider))].sort();
|
|
23280
|
+
const actionSet = new Set(agg.actionsTaken.map(toApiAction));
|
|
23109
23281
|
const aggregateAction = actionSet.size === 1 ? [...actionSet][0] ?? null : null;
|
|
23110
|
-
const
|
|
23111
|
-
const
|
|
23112
|
-
id: ruleId,
|
|
23113
|
-
name: packNames?.get(ruleId) ?? null
|
|
23114
|
-
};
|
|
23115
|
-
const apiCategory = toApiCategory(ruleRows[0]?.category ?? "custom");
|
|
23116
|
-
const policy = { id: `category:${apiCategory}`, name: apiCategory };
|
|
23117
|
-
const match = {
|
|
23118
|
-
maskedValue: ruleRows[0]?.maskedMatch ?? "",
|
|
23119
|
-
contextPrefix: ""
|
|
23120
|
-
// empty (pending privacy review)
|
|
23121
|
-
};
|
|
23122
|
-
const status = foldGroupStatus(
|
|
23123
|
-
agg ? agg.statusInputs.map(deriveFindingStatus) : instances.map((i) => i.status)
|
|
23124
|
-
);
|
|
23125
|
-
const group = {
|
|
23282
|
+
const apiCategory = toApiCategory(agg.category ?? "custom");
|
|
23283
|
+
const type = {
|
|
23126
23284
|
id: ruleId,
|
|
23127
23285
|
category: apiCategory,
|
|
23128
23286
|
subtype: ruleId,
|
|
23129
23287
|
// human label comes with pack metadata later
|
|
23130
|
-
severity,
|
|
23131
|
-
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
instanceCount: agg?.instanceCount ?? instances.length,
|
|
23288
|
+
severity: agg.severity ?? "low",
|
|
23289
|
+
detection: { id: ruleId, name: packNames?.get(ruleId) ?? null },
|
|
23290
|
+
policy: { id: `category:${apiCategory}`, name: apiCategory },
|
|
23291
|
+
instanceCount: agg.instanceCount,
|
|
23135
23292
|
providers,
|
|
23136
23293
|
aggregateAction,
|
|
23137
|
-
latestDetectedAt,
|
|
23138
|
-
|
|
23139
|
-
status,
|
|
23294
|
+
latestDetectedAt: agg.latestDetectedAt,
|
|
23295
|
+
status: foldGroupStatus(agg.statusInputs.map(deriveFindingStatus)),
|
|
23140
23296
|
...users.length > 0 ? { users } : {}
|
|
23141
23297
|
};
|
|
23142
|
-
|
|
23143
|
-
|
|
23144
|
-
|
|
23145
|
-
haystackCache.set(group, buildHaystack(group, agg.searchText));
|
|
23146
|
-
}
|
|
23298
|
+
actionsCache.set(type, [...actionSet]);
|
|
23299
|
+
if (agg.searchText !== void 0) {
|
|
23300
|
+
haystackCache.set(type, buildHaystack(type, agg.searchText));
|
|
23147
23301
|
}
|
|
23148
|
-
|
|
23302
|
+
types.push(type);
|
|
23149
23303
|
}
|
|
23150
|
-
return
|
|
23304
|
+
return types;
|
|
23151
23305
|
}
|
|
23152
23306
|
var haystackCache = /* @__PURE__ */ new WeakMap();
|
|
23153
|
-
function buildHaystack(
|
|
23307
|
+
function buildHaystack(t, extra) {
|
|
23154
23308
|
return [
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
...g.instances.map((i) => i.repo),
|
|
23161
|
-
...g.instances.map((i) => i.file),
|
|
23162
|
-
...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
|
|
23163
|
-
...g.instances.map((i) => i.id),
|
|
23164
|
-
// The people: the whole group's list when the store folded one, plus the
|
|
23165
|
-
// preview's own — the two overlap, and a haystack does not mind.
|
|
23166
|
-
...(g.users ?? []).map((u) => u.name),
|
|
23167
|
-
...g.instances.map((i) => i.user?.name ?? ""),
|
|
23309
|
+
t.subtype,
|
|
23310
|
+
t.category,
|
|
23311
|
+
t.policy.name,
|
|
23312
|
+
t.id,
|
|
23313
|
+
...(t.users ?? []).map((u) => u.name),
|
|
23168
23314
|
...extra === void 0 ? [] : [extra]
|
|
23169
23315
|
].join(" ").toLowerCase();
|
|
23170
23316
|
}
|
|
23171
|
-
function
|
|
23172
|
-
const cached2 = haystackCache.get(
|
|
23317
|
+
function typeHaystack(t) {
|
|
23318
|
+
const cached2 = haystackCache.get(t);
|
|
23173
23319
|
if (cached2 !== void 0) return cached2;
|
|
23174
|
-
const haystack = buildHaystack(
|
|
23175
|
-
haystackCache.set(
|
|
23320
|
+
const haystack = buildHaystack(t);
|
|
23321
|
+
haystackCache.set(t, haystack);
|
|
23176
23322
|
return haystack;
|
|
23177
23323
|
}
|
|
23178
23324
|
var actionsCache = /* @__PURE__ */ new WeakMap();
|
|
23179
|
-
function
|
|
23180
|
-
|
|
23181
|
-
if (cached2 !== void 0) return cached2;
|
|
23182
|
-
const actions = [...new Set(g.instances.map((i) => i.action))];
|
|
23183
|
-
actionsCache.set(g, actions);
|
|
23184
|
-
return actions;
|
|
23325
|
+
function typeActions(t) {
|
|
23326
|
+
return actionsCache.get(t) ?? [];
|
|
23185
23327
|
}
|
|
23186
23328
|
function countInstancesByStatus(statusInputs, statuses) {
|
|
23187
23329
|
const statusSet = new Set(statuses);
|
|
@@ -23192,8 +23334,8 @@ function countInstancesByStatus(statusInputs, statuses) {
|
|
|
23192
23334
|
}
|
|
23193
23335
|
return sum;
|
|
23194
23336
|
}
|
|
23195
|
-
function applyFindingFilters(
|
|
23196
|
-
let filtered =
|
|
23337
|
+
function applyFindingFilters(types, opts) {
|
|
23338
|
+
let filtered = types;
|
|
23197
23339
|
if (opts.severity && opts.severity.length > 0) {
|
|
23198
23340
|
const sevSet = new Set(opts.severity);
|
|
23199
23341
|
filtered = filtered.filter((g) => sevSet.has(g.severity));
|
|
@@ -23204,7 +23346,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
23204
23346
|
}
|
|
23205
23347
|
if (opts.actions && opts.actions.length > 0) {
|
|
23206
23348
|
const actionSet = new Set(opts.actions);
|
|
23207
|
-
filtered = filtered.filter((
|
|
23349
|
+
filtered = filtered.filter((t) => typeActions(t).some((a) => actionSet.has(a)));
|
|
23208
23350
|
}
|
|
23209
23351
|
if (opts.subtype && opts.subtype.length > 0) {
|
|
23210
23352
|
const subtypeSet = new Set(opts.subtype);
|
|
@@ -23216,7 +23358,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
23216
23358
|
}
|
|
23217
23359
|
if (opts.q) {
|
|
23218
23360
|
const q = opts.q.toLowerCase();
|
|
23219
|
-
filtered = filtered.filter((
|
|
23361
|
+
filtered = filtered.filter((t) => typeHaystack(t).includes(q));
|
|
23220
23362
|
}
|
|
23221
23363
|
return filtered;
|
|
23222
23364
|
}
|
|
@@ -23231,11 +23373,11 @@ function compareFindingGroupOrder(a, b) {
|
|
|
23231
23373
|
if (recencyDiff !== 0) return recencyDiff;
|
|
23232
23374
|
return a.id.localeCompare(b.id);
|
|
23233
23375
|
}
|
|
23234
|
-
function
|
|
23235
|
-
return [...
|
|
23376
|
+
function sortFindingTypes(types) {
|
|
23377
|
+
return [...types].sort(compareFindingGroupOrder);
|
|
23236
23378
|
}
|
|
23237
|
-
function computeFindingFacets(
|
|
23238
|
-
const forSeverity = applyFindingFilters(
|
|
23379
|
+
function computeFindingFacets(allTypes, opts) {
|
|
23380
|
+
const forSeverity = applyFindingFilters(allTypes, {
|
|
23239
23381
|
providers: opts.providers,
|
|
23240
23382
|
actions: opts.actions,
|
|
23241
23383
|
statuses: opts.statuses,
|
|
@@ -23246,7 +23388,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23246
23388
|
for (const g of forSeverity) {
|
|
23247
23389
|
severityMap.set(g.severity, (severityMap.get(g.severity) ?? 0) + 1);
|
|
23248
23390
|
}
|
|
23249
|
-
const forProvider = applyFindingFilters(
|
|
23391
|
+
const forProvider = applyFindingFilters(allTypes, {
|
|
23250
23392
|
actions: opts.actions,
|
|
23251
23393
|
statuses: opts.statuses,
|
|
23252
23394
|
q: opts.q,
|
|
@@ -23257,7 +23399,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23257
23399
|
for (const g of forProvider) {
|
|
23258
23400
|
for (const p of g.providers) providerMap.set(p, (providerMap.get(p) ?? 0) + 1);
|
|
23259
23401
|
}
|
|
23260
|
-
const forAction = applyFindingFilters(
|
|
23402
|
+
const forAction = applyFindingFilters(allTypes, {
|
|
23261
23403
|
providers: opts.providers,
|
|
23262
23404
|
statuses: opts.statuses,
|
|
23263
23405
|
q: opts.q,
|
|
@@ -23266,9 +23408,9 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23266
23408
|
});
|
|
23267
23409
|
const actionMap = /* @__PURE__ */ new Map();
|
|
23268
23410
|
for (const g of forAction) {
|
|
23269
|
-
for (const a of
|
|
23411
|
+
for (const a of typeActions(g)) actionMap.set(a, (actionMap.get(a) ?? 0) + 1);
|
|
23270
23412
|
}
|
|
23271
|
-
const forSubtype = applyFindingFilters(
|
|
23413
|
+
const forSubtype = applyFindingFilters(allTypes, {
|
|
23272
23414
|
providers: opts.providers,
|
|
23273
23415
|
actions: opts.actions,
|
|
23274
23416
|
statuses: opts.statuses,
|
|
@@ -23277,7 +23419,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23277
23419
|
});
|
|
23278
23420
|
const subtypeMap = /* @__PURE__ */ new Map();
|
|
23279
23421
|
for (const g of forSubtype) subtypeMap.set(g.subtype, (subtypeMap.get(g.subtype) ?? 0) + 1);
|
|
23280
|
-
const forStatus = applyFindingFilters(
|
|
23422
|
+
const forStatus = applyFindingFilters(allTypes, {
|
|
23281
23423
|
providers: opts.providers,
|
|
23282
23424
|
actions: opts.actions,
|
|
23283
23425
|
q: opts.q,
|
|
@@ -23325,10 +23467,20 @@ function matchesDimension(row, opts, dimension) {
|
|
|
23325
23467
|
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
23326
23468
|
case "tools":
|
|
23327
23469
|
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
23470
|
+
// An EMPTY value is a real filter here, not an absent one. The location
|
|
23471
|
+
// list buckets a finding whose event recorded no repo — or no file — under
|
|
23472
|
+
// the empty string, and selecting that bucket has to narrow the panel to
|
|
23473
|
+
// exactly it. Only `undefined` means "no filter"; a caller that wants every
|
|
23474
|
+
// row omits the key, which every call site already does.
|
|
23475
|
+
//
|
|
23476
|
+
// Reading '' as unset is what this replaced, and it failed in the one place
|
|
23477
|
+
// it mattered: the no-repo/no-file bucket is often the largest in a real
|
|
23478
|
+
// store, and its panel dropped both predicates and returned the WHOLE scope
|
|
23479
|
+
// — a row reading 3 findings beside a panel listing every finding there is.
|
|
23328
23480
|
case "repo":
|
|
23329
|
-
return opts.repo === void 0 ||
|
|
23481
|
+
return opts.repo === void 0 || row.repo === opts.repo;
|
|
23330
23482
|
case "file":
|
|
23331
|
-
return opts.file === void 0 ||
|
|
23483
|
+
return opts.file === void 0 || row.file === opts.file;
|
|
23332
23484
|
case "q":
|
|
23333
23485
|
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
23334
23486
|
}
|
|
@@ -23442,6 +23594,23 @@ function addToLocation(acc, row) {
|
|
|
23442
23594
|
acc.statuses.push(row.status);
|
|
23443
23595
|
acc.ruleIds.add(row.ruleId);
|
|
23444
23596
|
}
|
|
23597
|
+
function compareLocationOrder(a, b) {
|
|
23598
|
+
const rankA = SEVERITY_ORDER2[a.maxSeverity] ?? -1;
|
|
23599
|
+
const rankB = SEVERITY_ORDER2[b.maxSeverity] ?? -1;
|
|
23600
|
+
if (rankA !== rankB) return rankA - rankB;
|
|
23601
|
+
if (a.latestDetectedAt !== b.latestDetectedAt) {
|
|
23602
|
+
return a.latestDetectedAt < b.latestDetectedAt ? 1 : -1;
|
|
23603
|
+
}
|
|
23604
|
+
if (a.repo !== b.repo) return a.repo < b.repo ? -1 : 1;
|
|
23605
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
23606
|
+
return 0;
|
|
23607
|
+
}
|
|
23608
|
+
function encodeLocationId(repo, file2) {
|
|
23609
|
+
return `${encodePart(repo)}/${encodePart(file2)}`;
|
|
23610
|
+
}
|
|
23611
|
+
function encodePart(value) {
|
|
23612
|
+
return encodeURIComponent(value.replace(/[\uD800-\uDFFF]/gu, "\uFFFD"));
|
|
23613
|
+
}
|
|
23445
23614
|
|
|
23446
23615
|
// ../packages/schema/src/zod/installed-pack.ts
|
|
23447
23616
|
var InstalledPack = external_exports.object({
|
|
@@ -23899,7 +24068,7 @@ function isVaultConsentValid(consent) {
|
|
|
23899
24068
|
// ../packages/schema/src/zod/local.ts
|
|
23900
24069
|
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23901
24070
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23902
|
-
var HISTORY_SYNC_PAYLOAD_VERSION =
|
|
24071
|
+
var HISTORY_SYNC_PAYLOAD_VERSION = 3;
|
|
23903
24072
|
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23904
24073
|
var ControlPlaneConnection = external_exports.object({
|
|
23905
24074
|
endpoint: external_exports.string().min(1),
|
|
@@ -23972,11 +24141,11 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23972
24141
|
// covers the current payload and must be re-granted.
|
|
23973
24142
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23974
24143
|
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23975
|
-
// with the payload shape and the endpoint they agreed to. Since payload
|
|
23976
|
-
// that covers
|
|
23977
|
-
// carry prompt/reply text in `content
|
|
23978
|
-
// Absent until granted, and a grant for a different endpoint
|
|
23979
|
-
// payload no longer counts.
|
|
24144
|
+
// with the payload shape and the endpoint they agreed to. Since payload v3
|
|
24145
|
+
// that covers the pre-attach backlog AND undelivered captures alike, and both
|
|
24146
|
+
// carry prompt/reply/tool-output text in `content`; the key name predates
|
|
24147
|
+
// both widenings. Absent until granted, and a grant for a different endpoint
|
|
24148
|
+
// or an older payload no longer counts.
|
|
23980
24149
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23981
24150
|
});
|
|
23982
24151
|
function defaultWorkspaceSettings() {
|
|
@@ -24110,6 +24279,9 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
24110
24279
|
"dataSharesInPlace",
|
|
24111
24280
|
"redactFallback"
|
|
24112
24281
|
]).meta({ id: "ManagedSettingKey" });
|
|
24282
|
+
function isManagedSettingKey(value) {
|
|
24283
|
+
return ManagedSettingKey.safeParse(value).success;
|
|
24284
|
+
}
|
|
24113
24285
|
var ManagedSettingsValues = external_exports.object({
|
|
24114
24286
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
24115
24287
|
controlPlane: external_exports.object({
|
|
@@ -24134,7 +24306,27 @@ var ManagedSettings = external_exports.object({
|
|
|
24134
24306
|
// Which of those the user may not change. A key here with no matching value
|
|
24135
24307
|
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
24136
24308
|
// the user may still override. The two are separable on purpose.
|
|
24137
|
-
|
|
24309
|
+
//
|
|
24310
|
+
// Parsed as NAMES rather than as the enum, and split below: a name this
|
|
24311
|
+
// build does not know is dropped from the locked set and reported, never a
|
|
24312
|
+
// reason to refuse the file. The same shape reaches an older build whenever
|
|
24313
|
+
// an administrator locks a key a newer build added, and refusing it there
|
|
24314
|
+
// ran that build entirely unmanaged — every pin and lock gone — on exactly
|
|
24315
|
+
// the fleets most likely to carry a version skew. A name outside the enum
|
|
24316
|
+
// is still never HONOURED: the lockable set stays explicit above.
|
|
24317
|
+
lockedFields: external_exports.array(external_exports.string()).default([])
|
|
24318
|
+
}).transform(({ lockedFields, ...rest }) => {
|
|
24319
|
+
const known = [];
|
|
24320
|
+
const unknown2 = [];
|
|
24321
|
+
for (const name of lockedFields) {
|
|
24322
|
+
if (isManagedSettingKey(name)) known.push(name);
|
|
24323
|
+
else unknown2.push(name);
|
|
24324
|
+
}
|
|
24325
|
+
return {
|
|
24326
|
+
...rest,
|
|
24327
|
+
lockedFields: known,
|
|
24328
|
+
...unknown2.length > 0 ? { unknownLockedFields: unknown2 } : {}
|
|
24329
|
+
};
|
|
24138
24330
|
}).meta({ id: "ManagedSettings" });
|
|
24139
24331
|
var NO_MANAGED_CONTEXT = { present: false, lockedFields: [] };
|
|
24140
24332
|
|
|
@@ -24262,7 +24454,11 @@ var FindingsTimeseriesPoint = external_exports.object({
|
|
|
24262
24454
|
timestamp: external_exports.iso.date(),
|
|
24263
24455
|
critical: external_exports.number().int().nonnegative(),
|
|
24264
24456
|
high: external_exports.number().int().nonnegative(),
|
|
24265
|
-
medium: external_exports.number().int().nonnegative()
|
|
24457
|
+
medium: external_exports.number().int().nonnegative(),
|
|
24458
|
+
// Optional and additive, so a producer written against the earlier
|
|
24459
|
+
// three-series contract keeps validating. A consumer plotting it resolves the
|
|
24460
|
+
// absent case itself — the chart point requires a number.
|
|
24461
|
+
low: external_exports.number().int().nonnegative().optional()
|
|
24266
24462
|
}).meta({ id: "FindingsTimeseriesPoint" });
|
|
24267
24463
|
var FindingsTimeseriesResponse = external_exports.object({
|
|
24268
24464
|
range: TimeRange,
|
|
@@ -24288,6 +24484,10 @@ var ResolvedFeedItem = external_exports.object({
|
|
|
24288
24484
|
findingKey: external_exports.string(),
|
|
24289
24485
|
ruleId: external_exports.string(),
|
|
24290
24486
|
severity: Severity,
|
|
24487
|
+
// Repository slug, and the file path RELATIVE to it. The pair is what
|
|
24488
|
+
// identifies the file: a bare path matches the same name in every repo.
|
|
24489
|
+
// Optional and additive; empty when the event carried no repo.
|
|
24490
|
+
repo: external_exports.string().optional(),
|
|
24291
24491
|
path: external_exports.string(),
|
|
24292
24492
|
// ISO-8601 datetime (matches FindingInstance.detectedAt / the rest of the
|
|
24293
24493
|
// findings domain). The reader `.toISOString()`s the DB epoch-ms values.
|
|
@@ -24622,51 +24822,6 @@ function pluginRef(agent) {
|
|
|
24622
24822
|
return `${agent.pluginName}@${agent.marketplace}`;
|
|
24623
24823
|
}
|
|
24624
24824
|
|
|
24625
|
-
// ../packages/local-ops/src/semver.ts
|
|
24626
|
-
function parse3(version2) {
|
|
24627
|
-
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-.]+))?$/.exec(version2.trim());
|
|
24628
|
-
if (!match) return null;
|
|
24629
|
-
return {
|
|
24630
|
-
core: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
24631
|
-
pre: match[4] ? match[4].split(".") : []
|
|
24632
|
-
};
|
|
24633
|
-
}
|
|
24634
|
-
function isSemver(version2) {
|
|
24635
|
-
return parse3(version2) !== null;
|
|
24636
|
-
}
|
|
24637
|
-
function comparePre(a, b) {
|
|
24638
|
-
if (a.length === 0 && b.length === 0) return 0;
|
|
24639
|
-
if (a.length === 0) return 1;
|
|
24640
|
-
if (b.length === 0) return -1;
|
|
24641
|
-
const len = Math.min(a.length, b.length);
|
|
24642
|
-
for (let i = 0; i < len; i++) {
|
|
24643
|
-
const ai = a[i] ?? "";
|
|
24644
|
-
const bi = b[i] ?? "";
|
|
24645
|
-
if (ai === bi) continue;
|
|
24646
|
-
const an = /^\d+$/.test(ai);
|
|
24647
|
-
const bn = /^\d+$/.test(bi);
|
|
24648
|
-
if (an && !bn) return -1;
|
|
24649
|
-
if (!an && bn) return 1;
|
|
24650
|
-
if (an && bn) return Number(ai) < Number(bi) ? -1 : 1;
|
|
24651
|
-
return ai < bi ? -1 : 1;
|
|
24652
|
-
}
|
|
24653
|
-
return a.length === b.length ? 0 : a.length < b.length ? -1 : 1;
|
|
24654
|
-
}
|
|
24655
|
-
function compareSemver(a, b) {
|
|
24656
|
-
const pa = parse3(a);
|
|
24657
|
-
const pb = parse3(b);
|
|
24658
|
-
if (!pa || !pb) return 0;
|
|
24659
|
-
for (let i = 0; i < 3; i++) {
|
|
24660
|
-
const av = pa.core[i] ?? 0;
|
|
24661
|
-
const bv = pb.core[i] ?? 0;
|
|
24662
|
-
if (av !== bv) return av < bv ? -1 : 1;
|
|
24663
|
-
}
|
|
24664
|
-
return comparePre(pa.pre, pb.pre);
|
|
24665
|
-
}
|
|
24666
|
-
function isNewer(latest, installed) {
|
|
24667
|
-
return compareSemver(latest, installed) > 0;
|
|
24668
|
-
}
|
|
24669
|
-
|
|
24670
24825
|
// ../packages/local-ops/src/updates.ts
|
|
24671
24826
|
var CLI_PACKAGE = "@akasecurity/cli";
|
|
24672
24827
|
function isRecord(value) {
|
|
@@ -25130,6 +25285,100 @@ function installAgentPlugin(agentId, mode = "capture") {
|
|
|
25130
25285
|
return runSteps(resolved.cliBin, manager3.installSteps(resolved.ref), mode);
|
|
25131
25286
|
}
|
|
25132
25287
|
|
|
25288
|
+
// ../packages/local-ops/src/background-schedule.ts
|
|
25289
|
+
import { createHash } from "crypto";
|
|
25290
|
+
import { mkdirSync, readFileSync as readFileSync3, rmSync, writeFileSync } from "fs";
|
|
25291
|
+
import { homedir as homedir3 } from "os";
|
|
25292
|
+
import { posix } from "path";
|
|
25293
|
+
var BACKGROUND_SYNC_LABEL = "com.akasecurity.aka.background-sync";
|
|
25294
|
+
var BACKGROUND_SYNC_INTERVAL_SECONDS = 30 * 60;
|
|
25295
|
+
function launchAgentsDir(deps) {
|
|
25296
|
+
return posix.join((deps.homeDir ?? homedir3)(), "Library", "LaunchAgents");
|
|
25297
|
+
}
|
|
25298
|
+
function backgroundSyncLabel(base) {
|
|
25299
|
+
return `${BACKGROUND_SYNC_LABEL}.${createHash("sha256").update(base).digest("hex").slice(0, 12)}`;
|
|
25300
|
+
}
|
|
25301
|
+
function plistPath(base, deps) {
|
|
25302
|
+
return posix.join(launchAgentsDir(deps), `${backgroundSyncLabel(base)}.plist`);
|
|
25303
|
+
}
|
|
25304
|
+
function escapeXml(value) {
|
|
25305
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
25306
|
+
}
|
|
25307
|
+
function renderPlist(label, programArguments) {
|
|
25308
|
+
const args = programArguments.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
25309
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
25310
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
25311
|
+
<plist version="1.0">
|
|
25312
|
+
<dict>
|
|
25313
|
+
<key>Label</key>
|
|
25314
|
+
<string>${escapeXml(label)}</string>
|
|
25315
|
+
<key>ProgramArguments</key>
|
|
25316
|
+
<array>
|
|
25317
|
+
${args}
|
|
25318
|
+
</array>
|
|
25319
|
+
<key>StartInterval</key>
|
|
25320
|
+
<integer>${String(BACKGROUND_SYNC_INTERVAL_SECONDS)}</integer>
|
|
25321
|
+
<key>RunAtLoad</key>
|
|
25322
|
+
<false/>
|
|
25323
|
+
<key>ProcessType</key>
|
|
25324
|
+
<string>Background</string>
|
|
25325
|
+
</dict>
|
|
25326
|
+
</plist>
|
|
25327
|
+
`;
|
|
25328
|
+
}
|
|
25329
|
+
function defaultReadFile(path) {
|
|
25330
|
+
try {
|
|
25331
|
+
return readFileSync3(path, "utf8");
|
|
25332
|
+
} catch {
|
|
25333
|
+
return null;
|
|
25334
|
+
}
|
|
25335
|
+
}
|
|
25336
|
+
function defaultRunLaunchctl(args) {
|
|
25337
|
+
return runCapture("launchctl", args).ok;
|
|
25338
|
+
}
|
|
25339
|
+
function guiDomain() {
|
|
25340
|
+
const getuid = process.getuid;
|
|
25341
|
+
return `gui/${String(typeof getuid === "function" ? getuid() : 0)}`;
|
|
25342
|
+
}
|
|
25343
|
+
function installBackgroundSync(base, deps = {}) {
|
|
25344
|
+
try {
|
|
25345
|
+
if ((deps.platform ?? process.platform) !== "darwin") return;
|
|
25346
|
+
const reinvoke = (deps.reinvoke ?? reinvokeArgv)("sync-history", ["--run", "--home", base]);
|
|
25347
|
+
if (reinvoke === null) return;
|
|
25348
|
+
const label = backgroundSyncLabel(base);
|
|
25349
|
+
const content = renderPlist(label, [reinvoke.command, ...reinvoke.args]);
|
|
25350
|
+
const path = plistPath(base, deps);
|
|
25351
|
+
if ((deps.readFile ?? defaultReadFile)(path) === content) return;
|
|
25352
|
+
(deps.mkdir ?? ((dir) => {
|
|
25353
|
+
mkdirSync(dir, { recursive: true });
|
|
25354
|
+
}))(launchAgentsDir(deps));
|
|
25355
|
+
(deps.writeFile ?? ((p, data) => {
|
|
25356
|
+
writeFileSync(p, data);
|
|
25357
|
+
}))(path, content);
|
|
25358
|
+
const domain2 = guiDomain();
|
|
25359
|
+
const launchctl = deps.runLaunchctl ?? defaultRunLaunchctl;
|
|
25360
|
+
launchctl(["bootout", `${domain2}/${label}`]);
|
|
25361
|
+
if (!launchctl(["bootstrap", domain2, path])) {
|
|
25362
|
+
process.stderr.write(`[aka] background-sync: launchctl bootstrap failed for ${label}
|
|
25363
|
+
`);
|
|
25364
|
+
}
|
|
25365
|
+
} catch {
|
|
25366
|
+
}
|
|
25367
|
+
}
|
|
25368
|
+
function uninstallBackgroundSync(base, deps = {}) {
|
|
25369
|
+
try {
|
|
25370
|
+
if ((deps.platform ?? process.platform) !== "darwin") return;
|
|
25371
|
+
(deps.runLaunchctl ?? defaultRunLaunchctl)([
|
|
25372
|
+
"bootout",
|
|
25373
|
+
`${guiDomain()}/${backgroundSyncLabel(base)}`
|
|
25374
|
+
]);
|
|
25375
|
+
(deps.removeFile ?? ((p) => {
|
|
25376
|
+
rmSync(p, { force: true });
|
|
25377
|
+
}))(plistPath(base, deps));
|
|
25378
|
+
} catch {
|
|
25379
|
+
}
|
|
25380
|
+
}
|
|
25381
|
+
|
|
25133
25382
|
// ../packages/local-ops/src/claude-plugin.ts
|
|
25134
25383
|
var manager = createCliPluginManager("claude");
|
|
25135
25384
|
var claudeAvailable = manager.available;
|
|
@@ -26949,7 +27198,7 @@ var CPU_CORROBORATION_SHARE = 0.2;
|
|
|
26949
27198
|
var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
|
|
26950
27199
|
|
|
26951
27200
|
// ../packages/persistence/src/attached-derived.ts
|
|
26952
|
-
import { rmSync } from "fs";
|
|
27201
|
+
import { rmSync as rmSync2 } from "fs";
|
|
26953
27202
|
import { join as join3 } from "path";
|
|
26954
27203
|
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
26955
27204
|
var ATTACHED_SYNC_STATE_FILENAME = "attached-sync-state.json";
|
|
@@ -26966,14 +27215,14 @@ var ATTACHED_DERIVED_FILENAMES = [
|
|
|
26966
27215
|
function clearAttachmentDerivedState(dataDir2) {
|
|
26967
27216
|
for (const name of ATTACHED_DERIVED_FILENAMES) {
|
|
26968
27217
|
try {
|
|
26969
|
-
|
|
27218
|
+
rmSync2(join3(dataDir2, name), { force: true });
|
|
26970
27219
|
} catch {
|
|
26971
27220
|
}
|
|
26972
27221
|
}
|
|
26973
27222
|
}
|
|
26974
27223
|
|
|
26975
27224
|
// ../packages/persistence/src/control-plane-credential.ts
|
|
26976
|
-
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync as
|
|
27225
|
+
import { chmodSync as chmodSync2, lstatSync as lstatSync2, readFileSync as readFileSync4, rmSync as rmSync4, statSync } from "fs";
|
|
26977
27226
|
import { join as join4 } from "path";
|
|
26978
27227
|
|
|
26979
27228
|
// ../packages/persistence/src/paths.ts
|
|
@@ -26981,10 +27230,10 @@ import {
|
|
|
26981
27230
|
chmodSync,
|
|
26982
27231
|
linkSync,
|
|
26983
27232
|
lstatSync,
|
|
26984
|
-
mkdirSync,
|
|
27233
|
+
mkdirSync as mkdirSync2,
|
|
26985
27234
|
renameSync,
|
|
26986
|
-
rmSync as
|
|
26987
|
-
writeFileSync
|
|
27235
|
+
rmSync as rmSync3,
|
|
27236
|
+
writeFileSync as writeFileSync2
|
|
26988
27237
|
} from "fs";
|
|
26989
27238
|
import { threadId } from "worker_threads";
|
|
26990
27239
|
var DATA_DIR_MODE = 448;
|
|
@@ -27008,7 +27257,7 @@ function tightenDir(dir) {
|
|
|
27008
27257
|
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
27009
27258
|
}
|
|
27010
27259
|
function mkdirOwnerOnlySync(dir, recursive2 = false) {
|
|
27011
|
-
|
|
27260
|
+
mkdirSync2(dir, { recursive: recursive2, mode: DATA_DIR_MODE });
|
|
27012
27261
|
}
|
|
27013
27262
|
function ensureDataDirSync(dir) {
|
|
27014
27263
|
mkdirOwnerOnlySync(dir, true);
|
|
@@ -27024,12 +27273,12 @@ function tightenPerms(file2) {
|
|
|
27024
27273
|
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
27025
27274
|
}
|
|
27026
27275
|
function writeExclusiveOwnerOnlySync(file2, data) {
|
|
27027
|
-
|
|
27276
|
+
writeFileSync2(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
27028
27277
|
}
|
|
27029
27278
|
function writeOwnerOnlyFileSync(file2, data) {
|
|
27030
27279
|
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
27031
27280
|
try {
|
|
27032
|
-
|
|
27281
|
+
rmSync3(tmp, { force: true });
|
|
27033
27282
|
} catch {
|
|
27034
27283
|
}
|
|
27035
27284
|
try {
|
|
@@ -27037,7 +27286,7 @@ function writeOwnerOnlyFileSync(file2, data) {
|
|
|
27037
27286
|
renameSync(tmp, file2);
|
|
27038
27287
|
} finally {
|
|
27039
27288
|
try {
|
|
27040
|
-
|
|
27289
|
+
rmSync3(tmp, { force: true });
|
|
27041
27290
|
} catch {
|
|
27042
27291
|
}
|
|
27043
27292
|
}
|
|
@@ -27066,7 +27315,7 @@ var KeyUnclaimableError = class extends Error {
|
|
|
27066
27315
|
function createOwnerOnlyFileSync(file2, data) {
|
|
27067
27316
|
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
27068
27317
|
try {
|
|
27069
|
-
|
|
27318
|
+
rmSync3(tmp, { force: true });
|
|
27070
27319
|
} catch {
|
|
27071
27320
|
}
|
|
27072
27321
|
let created;
|
|
@@ -27075,7 +27324,7 @@ function createOwnerOnlyFileSync(file2, data) {
|
|
|
27075
27324
|
created = publishByLink(tmp, file2, data);
|
|
27076
27325
|
} finally {
|
|
27077
27326
|
try {
|
|
27078
|
-
|
|
27327
|
+
rmSync3(tmp, { force: true });
|
|
27079
27328
|
} catch {
|
|
27080
27329
|
}
|
|
27081
27330
|
}
|
|
@@ -27144,7 +27393,7 @@ function readControlPlaneCredentialFile(settingsDir2, connection) {
|
|
|
27144
27393
|
if (gate === "absent") return { usable: false, reason: "absent" };
|
|
27145
27394
|
if (gate === "untrusted") return { usable: false, reason: "untrusted-file" };
|
|
27146
27395
|
try {
|
|
27147
|
-
raw =
|
|
27396
|
+
raw = readFileSync4(file2, "utf8");
|
|
27148
27397
|
} catch (err) {
|
|
27149
27398
|
const code = err.code;
|
|
27150
27399
|
return { usable: false, reason: code === "ENOENT" ? "absent" : "unreadable" };
|
|
@@ -27170,6 +27419,10 @@ function readControlPlaneCredentialFile(settingsDir2, connection) {
|
|
|
27170
27419
|
}
|
|
27171
27420
|
return { usable: true, credential: result.data };
|
|
27172
27421
|
}
|
|
27422
|
+
function readControlPlaneCredential(settingsDir2, connection) {
|
|
27423
|
+
const read = readControlPlaneCredentialFile(settingsDir2, connection);
|
|
27424
|
+
return read.usable ? read.credential : null;
|
|
27425
|
+
}
|
|
27173
27426
|
function writeControlPlaneCredential(settingsDir2, credential) {
|
|
27174
27427
|
if (!isSafeEndpoint(credential.endpoint)) {
|
|
27175
27428
|
throw new Error(
|
|
@@ -27186,7 +27439,7 @@ function writeControlPlaneCredential(settingsDir2, credential) {
|
|
|
27186
27439
|
function removeControlPlaneCredential(settingsDir2) {
|
|
27187
27440
|
const file2 = controlPlaneCredentialPath(settingsDir2);
|
|
27188
27441
|
const existed = lstatSync2(file2, { throwIfNoEntry: false }) !== void 0;
|
|
27189
|
-
|
|
27442
|
+
rmSync4(file2, { force: true });
|
|
27190
27443
|
return existed;
|
|
27191
27444
|
}
|
|
27192
27445
|
|
|
@@ -27196,9 +27449,9 @@ import { dirname as dirname4, join as join9, sep as sep2 } from "path";
|
|
|
27196
27449
|
import { DatabaseSync } from "node:sqlite";
|
|
27197
27450
|
|
|
27198
27451
|
// ../packages/persistence/src/ids.ts
|
|
27199
|
-
import { createHash } from "crypto";
|
|
27452
|
+
import { createHash as createHash2 } from "crypto";
|
|
27200
27453
|
function sha256Hex(input2) {
|
|
27201
|
-
return
|
|
27454
|
+
return createHash2("sha256").update(input2).digest("hex");
|
|
27202
27455
|
}
|
|
27203
27456
|
function inventoryId(objectType, identityKey) {
|
|
27204
27457
|
return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
|
|
@@ -27238,7 +27491,7 @@ function captureWireId(sessionId, contentHash, filePath = null) {
|
|
|
27238
27491
|
|
|
27239
27492
|
// ../packages/persistence/src/internal/snapshot.ts
|
|
27240
27493
|
import { randomUUID } from "crypto";
|
|
27241
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, renameSync as renameSync2, rmSync as
|
|
27494
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, renameSync as renameSync2, rmSync as rmSync5, statSync as statSync2 } from "fs";
|
|
27242
27495
|
import { basename, dirname as dirname3, join as join5 } from "path";
|
|
27243
27496
|
function backupPath(file2, tag) {
|
|
27244
27497
|
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
@@ -27249,7 +27502,7 @@ var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
|
|
|
27249
27502
|
var SNAPSHOT_STAGING_COPY = "copy";
|
|
27250
27503
|
function createSnapshotStaging(backup) {
|
|
27251
27504
|
const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
|
|
27252
|
-
|
|
27505
|
+
rmSync5(stage, { recursive: true, force: true });
|
|
27253
27506
|
mkdirOwnerOnlySync(stage);
|
|
27254
27507
|
tightenDir(stage);
|
|
27255
27508
|
return { stage, copy: join5(stage, SNAPSHOT_STAGING_COPY) };
|
|
@@ -27278,7 +27531,7 @@ function reapStalePartials(file2) {
|
|
|
27278
27531
|
try {
|
|
27279
27532
|
const idle = idleMs(staging);
|
|
27280
27533
|
if (idle !== null && idle > STALE_PARTIAL_MS) {
|
|
27281
|
-
|
|
27534
|
+
rmSync5(staging, { recursive: true, force: true });
|
|
27282
27535
|
}
|
|
27283
27536
|
} catch {
|
|
27284
27537
|
}
|
|
@@ -27292,13 +27545,13 @@ function snapshotStore(db, backup) {
|
|
|
27292
27545
|
renameSync2(copy, backup);
|
|
27293
27546
|
} catch (error61) {
|
|
27294
27547
|
try {
|
|
27295
|
-
|
|
27548
|
+
rmSync5(stage, { recursive: true, force: true });
|
|
27296
27549
|
} catch {
|
|
27297
27550
|
}
|
|
27298
27551
|
throw error61;
|
|
27299
27552
|
}
|
|
27300
27553
|
try {
|
|
27301
|
-
|
|
27554
|
+
rmSync5(stage, { recursive: true, force: true });
|
|
27302
27555
|
} catch {
|
|
27303
27556
|
}
|
|
27304
27557
|
}
|
|
@@ -27313,7 +27566,7 @@ function moveStoreAside(file2, backup) {
|
|
|
27313
27566
|
renameSync2(sidecar, moved);
|
|
27314
27567
|
undo.push([moved, sidecar]);
|
|
27315
27568
|
} catch {
|
|
27316
|
-
|
|
27569
|
+
rmSync5(sidecar, { force: true });
|
|
27317
27570
|
}
|
|
27318
27571
|
}
|
|
27319
27572
|
} catch (error61) {
|
|
@@ -27329,14 +27582,14 @@ function moveStoreAside(file2, backup) {
|
|
|
27329
27582
|
}
|
|
27330
27583
|
function discardStore(file2, backup) {
|
|
27331
27584
|
try {
|
|
27332
|
-
|
|
27585
|
+
rmSync5(file2, { force: true });
|
|
27333
27586
|
for (const sidecar of dbSidecars(file2)) {
|
|
27334
|
-
|
|
27587
|
+
rmSync5(sidecar, { force: true });
|
|
27335
27588
|
}
|
|
27336
27589
|
} catch (error61) {
|
|
27337
27590
|
if (existsSync3(file2)) {
|
|
27338
27591
|
try {
|
|
27339
|
-
|
|
27592
|
+
rmSync5(backup, { force: true });
|
|
27340
27593
|
} catch {
|
|
27341
27594
|
}
|
|
27342
27595
|
}
|
|
@@ -28280,7 +28533,8 @@ var SqliteActivityRepository = class {
|
|
|
28280
28533
|
SELECT 1 FROM audit_events d
|
|
28281
28534
|
WHERE d.root_session_id = audit_events.id
|
|
28282
28535
|
AND (d.content LIKE ? ESCAPE '\\'
|
|
28283
|
-
OR json_extract(d.attributes, '$.detail')
|
|
28536
|
+
OR coalesce(json_extract(d.attributes, '$.detail'),
|
|
28537
|
+
json_extract(d.attributes, '$.target')) LIKE ? ESCAPE '\\')))`
|
|
28284
28538
|
);
|
|
28285
28539
|
params.push(pattern, pattern, pattern, pattern, pattern, pattern);
|
|
28286
28540
|
}
|
|
@@ -29618,23 +29872,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
29618
29872
|
)`;
|
|
29619
29873
|
|
|
29620
29874
|
// ../packages/persistence/src/repositories/findings.ts
|
|
29621
|
-
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
29622
|
-
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
29623
|
-
var LOCATION_RULE_IDS_CAP = 20;
|
|
29624
|
-
function compareLocationOrder(a, b) {
|
|
29625
|
-
return compareFindingGroupOrder(
|
|
29626
|
-
{
|
|
29627
|
-
severity: a.maxSeverity,
|
|
29628
|
-
latestDetectedAt: a.latestDetectedAt,
|
|
29629
|
-
id: ""
|
|
29630
|
-
},
|
|
29631
|
-
{
|
|
29632
|
-
severity: b.maxSeverity,
|
|
29633
|
-
latestDetectedAt: b.latestDetectedAt,
|
|
29634
|
-
id: ""
|
|
29635
|
-
}
|
|
29636
|
-
);
|
|
29637
|
-
}
|
|
29638
29875
|
var CONCAT_SEP = ",";
|
|
29639
29876
|
var TUPLE_SEP = "|";
|
|
29640
29877
|
function splitConcat(value) {
|
|
@@ -29686,13 +29923,48 @@ function decodeGroupCursor(cursor) {
|
|
|
29686
29923
|
return null;
|
|
29687
29924
|
}
|
|
29688
29925
|
function firstAfter(sorted, cursor) {
|
|
29689
|
-
const index = sorted.findIndex((
|
|
29926
|
+
const index = sorted.findIndex((t) => compareFindingGroupOrder(t, cursor) > 0);
|
|
29690
29927
|
return index === -1 ? sorted.length : index;
|
|
29691
29928
|
}
|
|
29692
29929
|
function findDeepLinked(sorted, page, id) {
|
|
29693
|
-
if (page.some((
|
|
29694
|
-
return sorted.find((
|
|
29930
|
+
if (page.some((t) => t.id === id)) return void 0;
|
|
29931
|
+
return sorted.find((t) => t.id === id);
|
|
29932
|
+
}
|
|
29933
|
+
function encodeLocationCursor(location) {
|
|
29934
|
+
const payload = {
|
|
29935
|
+
sev: location.maxSeverity,
|
|
29936
|
+
t: location.latestDetectedAt,
|
|
29937
|
+
r: location.repo,
|
|
29938
|
+
f: location.file
|
|
29939
|
+
};
|
|
29940
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
29695
29941
|
}
|
|
29942
|
+
function decodeLocationCursor(cursor) {
|
|
29943
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
29944
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.r === "string" && typeof parsed2.f === "string") {
|
|
29945
|
+
return { maxSeverity: parsed2.sev, latestDetectedAt: parsed2.t, repo: parsed2.r, file: parsed2.f };
|
|
29946
|
+
}
|
|
29947
|
+
return null;
|
|
29948
|
+
}
|
|
29949
|
+
function firstLocationAfter(sorted, cursor) {
|
|
29950
|
+
const index = sorted.findIndex((l) => compareLocationOrder(l, cursor) > 0);
|
|
29951
|
+
return index === -1 ? sorted.length : index;
|
|
29952
|
+
}
|
|
29953
|
+
function findDeepLinkedLocation(sorted, page, id) {
|
|
29954
|
+
if (page.some((l) => l.id === id)) return void 0;
|
|
29955
|
+
return sorted.find((l) => l.id === id);
|
|
29956
|
+
}
|
|
29957
|
+
var FINDING_ROW_COLUMNS_SQL = `f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
29958
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
29959
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
29960
|
+
e.started_at AS occurred_at,
|
|
29961
|
+
e.source_tool AS source_tool,
|
|
29962
|
+
e.repo AS repo,
|
|
29963
|
+
e.file_path AS file,
|
|
29964
|
+
e.tool_name AS tool_name,
|
|
29965
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
29966
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
29967
|
+
${latestResolutionStatusSql("f")} AS latest_status`;
|
|
29696
29968
|
var DAY_MS3 = 864e5;
|
|
29697
29969
|
var SqliteFindingsRepository = class {
|
|
29698
29970
|
constructor(db) {
|
|
@@ -29813,30 +30085,26 @@ var SqliteFindingsRepository = class {
|
|
|
29813
30085
|
);
|
|
29814
30086
|
}
|
|
29815
30087
|
/**
|
|
29816
|
-
*
|
|
29817
|
-
*
|
|
29818
|
-
*
|
|
29819
|
-
*
|
|
29820
|
-
*
|
|
29821
|
-
*
|
|
29822
|
-
*
|
|
29823
|
-
*
|
|
29824
|
-
*
|
|
29825
|
-
*
|
|
29826
|
-
*
|
|
29827
|
-
*
|
|
30088
|
+
* Finding TYPES for the dashboard — one row per rule, scoped to the four
|
|
30089
|
+
* capture kinds (audit_events also holds structural/reconciler/scan rows this
|
|
30090
|
+
* list must never surface), with per-filter-excluded facets, the requested
|
|
30091
|
+
* filters applied, and sorted by severity then recency. Filtering and faceting
|
|
30092
|
+
* run in JS via the shared @akasecurity/schema helpers. `totals` reflect the
|
|
30093
|
+
* full filtered set; `items` is the requested page (default 50), keyset-paged.
|
|
30094
|
+
* Under a `status` filter, `totals.findings` counts only findings whose
|
|
30095
|
+
* derived status was requested.
|
|
30096
|
+
*
|
|
30097
|
+
* ONE read, which materializes no findings: a single aggregate per rule_id,
|
|
30098
|
+
* folding EVERY finding into the numbers a type row and the filters need
|
|
30099
|
+
* (count, severity, category, providers, actions, statuses, latest, search
|
|
30100
|
+
* text). The findings OF a type come from listFindingInstances scoped to
|
|
30101
|
+
* `subtype`, so neither list bounds the other and no per-type cap exists.
|
|
29828
30102
|
*
|
|
29829
|
-
* Two reads, neither of which materializes a row per finding:
|
|
29830
|
-
* 1. one aggregate row per rule_id, folding EVERY instance into the numbers
|
|
29831
|
-
* the group and the filters need (count, providers, actions, statuses,
|
|
29832
|
-
* latest, search text);
|
|
29833
|
-
* 2. each group's newest PREVIEW_INSTANCES_PER_GROUP instances, which
|
|
29834
|
-
* populate `instances` for the table's expanded rows.
|
|
29835
30103
|
* The aggregates carry raw DB values and are translated by the same
|
|
29836
|
-
* @akasecurity/schema mappers
|
|
29837
|
-
* rule is ever restated in SQL.
|
|
30104
|
+
* @akasecurity/schema mappers every other path uses, so no enum mapping or
|
|
30105
|
+
* status rule is ever restated in SQL.
|
|
29838
30106
|
*/
|
|
29839
|
-
|
|
30107
|
+
listFindingTypes(query) {
|
|
29840
30108
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
29841
30109
|
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
29842
30110
|
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
@@ -29849,12 +30117,7 @@ var SqliteFindingsRepository = class {
|
|
|
29849
30117
|
predicate,
|
|
29850
30118
|
params: sessionParams
|
|
29851
30119
|
});
|
|
29852
|
-
const
|
|
29853
|
-
sessionId: query.sessionId,
|
|
29854
|
-
from: query.from
|
|
29855
|
-
});
|
|
29856
|
-
const groupable = rows.map(toFlatFindingRow);
|
|
29857
|
-
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
30120
|
+
const allTypes = buildFindingTypes(aggregates);
|
|
29858
30121
|
const filterOpts = {
|
|
29859
30122
|
severity: query.severity,
|
|
29860
30123
|
providers: query.provider,
|
|
@@ -29863,30 +30126,25 @@ var SqliteFindingsRepository = class {
|
|
|
29863
30126
|
subtype: query.subtype,
|
|
29864
30127
|
q: query.q
|
|
29865
30128
|
};
|
|
29866
|
-
const facets = computeFindingFacets(
|
|
29867
|
-
const sorted =
|
|
30129
|
+
const facets = computeFindingFacets(allTypes, filterOpts);
|
|
30130
|
+
const sorted = sortFindingTypes(applyFindingFilters(allTypes, filterOpts));
|
|
29868
30131
|
const statusFilter = query.status ?? [];
|
|
29869
30132
|
const totals = {
|
|
29870
|
-
findings: sorted.reduce((acc,
|
|
29871
|
-
if (statusFilter.length === 0) return acc +
|
|
29872
|
-
const agg = aggregates.get(
|
|
29873
|
-
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ??
|
|
30133
|
+
findings: sorted.reduce((acc, t) => {
|
|
30134
|
+
if (statusFilter.length === 0) return acc + t.instanceCount;
|
|
30135
|
+
const agg = aggregates.get(t.id);
|
|
30136
|
+
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ?? t.instanceCount : t.instanceCount);
|
|
29874
30137
|
}, 0),
|
|
29875
|
-
|
|
30138
|
+
types: sorted.length
|
|
29876
30139
|
};
|
|
29877
|
-
const limit = query.limit ??
|
|
30140
|
+
const limit = query.limit ?? DEFAULT_FINDING_TYPES_LIMIT;
|
|
29878
30141
|
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
29879
30142
|
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
29880
30143
|
const page = sorted.slice(start, start + limit);
|
|
29881
30144
|
const lastOnPage = page.at(-1);
|
|
29882
30145
|
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
29883
30146
|
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
29884
|
-
const
|
|
29885
|
-
const narrow = (g) => statusSet ? {
|
|
29886
|
-
...g,
|
|
29887
|
-
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
29888
|
-
} : g;
|
|
29889
|
-
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
30147
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []];
|
|
29890
30148
|
return Promise.resolve({
|
|
29891
30149
|
totals,
|
|
29892
30150
|
facets,
|
|
@@ -29897,7 +30155,7 @@ var SqliteFindingsRepository = class {
|
|
|
29897
30155
|
}
|
|
29898
30156
|
/**
|
|
29899
30157
|
* One row per rule_id, folding EVERY instance of the group into the values
|
|
29900
|
-
*
|
|
30158
|
+
* buildFindingTypes cannot recover from an aggregate. Bounded by the number of
|
|
29901
30159
|
* distinct rule_ids (the installed packs' rules), not by the store's size.
|
|
29902
30160
|
*
|
|
29903
30161
|
* A single scan, folded in two levels: the inner SELECT groups by
|
|
@@ -29991,13 +30249,25 @@ var SqliteFindingsRepository = class {
|
|
|
29991
30249
|
});
|
|
29992
30250
|
}
|
|
29993
30251
|
/**
|
|
29994
|
-
* The same findings folded by
|
|
30252
|
+
* The same findings folded by WHERE they live — one row per (repo, file) pair.
|
|
29995
30253
|
*
|
|
29996
30254
|
* The grouping keys come from the capturing event's attributes, which is what
|
|
29997
|
-
* the local store relates a finding to
|
|
29998
|
-
*
|
|
29999
|
-
* empty-string bucket, which
|
|
30000
|
-
*
|
|
30255
|
+
* the local store relates a finding to; there is no finding↔asset row to group
|
|
30256
|
+
* by instead. A repo or file the event did not record folds into the
|
|
30257
|
+
* empty-string bucket, which is a real location like any other: it is listed,
|
|
30258
|
+
* it is selectable, and its `?loc=` token is as good as any other row's.
|
|
30259
|
+
*
|
|
30260
|
+
* ONE flat list rather than repos nesting files. A rollup can only be paged by
|
|
30261
|
+
* repo, which leaves the file list inside it unbounded — the shape the by-type
|
|
30262
|
+
* list was rebuilt to remove — and two-level pagination inside an
|
|
30263
|
+
* expand/collapse table is what pushed that view to master/detail in the first
|
|
30264
|
+
* place.
|
|
30265
|
+
*
|
|
30266
|
+
* Every filter narrows the FINDINGS and the locations fall out of what
|
|
30267
|
+
* survives, so each row's `instanceCount` is exactly what listFindingInstances
|
|
30268
|
+
* reports for the same filters scoped to that pair. The view depends on it:
|
|
30269
|
+
* one toolbar sits over both panels precisely because a location owns none of
|
|
30270
|
+
* its fields.
|
|
30001
30271
|
*/
|
|
30002
30272
|
listFindingLocations(query) {
|
|
30003
30273
|
const opts = {
|
|
@@ -30009,13 +30279,16 @@ var SqliteFindingsRepository = class {
|
|
|
30009
30279
|
tools: query.tool,
|
|
30010
30280
|
q: query.q
|
|
30011
30281
|
};
|
|
30012
|
-
const limit = query.limit ??
|
|
30282
|
+
const limit = query.limit ?? DEFAULT_FINDING_LOCATIONS_LIMIT;
|
|
30283
|
+
const cursor = query.cursor === void 0 ? null : decodeLocationCursor(query.cursor);
|
|
30013
30284
|
const byRepo = /* @__PURE__ */ new Map();
|
|
30285
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
30014
30286
|
let total = 0;
|
|
30015
30287
|
for (const row of this.scanFindingRows({
|
|
30016
30288
|
sessionId: query.sessionId,
|
|
30017
30289
|
from: query.from
|
|
30018
30290
|
})) {
|
|
30291
|
+
accumulator.add(row);
|
|
30019
30292
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
30020
30293
|
total += 1;
|
|
30021
30294
|
let files = byRepo.get(row.repo);
|
|
@@ -30030,103 +30303,35 @@ var SqliteFindingsRepository = class {
|
|
|
30030
30303
|
}
|
|
30031
30304
|
addToLocation(acc, row);
|
|
30032
30305
|
}
|
|
30033
|
-
|
|
30034
|
-
const
|
|
30035
|
-
|
|
30036
|
-
|
|
30037
|
-
|
|
30038
|
-
|
|
30039
|
-
|
|
30040
|
-
|
|
30041
|
-
|
|
30042
|
-
|
|
30043
|
-
|
|
30044
|
-
|
|
30045
|
-
|
|
30046
|
-
|
|
30047
|
-
|
|
30048
|
-
|
|
30049
|
-
|
|
30050
|
-
|
|
30051
|
-
|
|
30052
|
-
|
|
30053
|
-
|
|
30054
|
-
|
|
30055
|
-
);
|
|
30056
|
-
const statuses = fileRows.map((f) => f.status);
|
|
30057
|
-
const folded = foldGroupStatus(statuses);
|
|
30058
|
-
return {
|
|
30059
|
-
repo,
|
|
30060
|
-
instanceCount: rollup.instanceCount,
|
|
30061
|
-
maxSeverity: rollup.maxSeverity,
|
|
30062
|
-
latestDetectedAt: rollup.latestDetectedAt,
|
|
30063
|
-
...folded === void 0 ? {} : { status: folded },
|
|
30064
|
-
files: fileRows
|
|
30065
|
-
};
|
|
30066
|
-
});
|
|
30067
|
-
repos.sort(compareLocationOrder);
|
|
30306
|
+
const sorted = [];
|
|
30307
|
+
for (const [repo, files] of byRepo) {
|
|
30308
|
+
for (const [file2, acc] of files) {
|
|
30309
|
+
const status = foldGroupStatus(acc.statuses);
|
|
30310
|
+
sorted.push({
|
|
30311
|
+
id: encodeLocationId(repo, file2),
|
|
30312
|
+
repo,
|
|
30313
|
+
file: file2,
|
|
30314
|
+
instanceCount: acc.instanceCount,
|
|
30315
|
+
maxSeverity: acc.maxSeverity,
|
|
30316
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
30317
|
+
...status === void 0 ? {} : { status },
|
|
30318
|
+
ruleIds: [...acc.ruleIds]
|
|
30319
|
+
});
|
|
30320
|
+
}
|
|
30321
|
+
}
|
|
30322
|
+
sorted.sort(compareLocationOrder);
|
|
30323
|
+
const start = cursor === null ? 0 : firstLocationAfter(sorted, cursor);
|
|
30324
|
+
const page = sorted.slice(start, start + limit);
|
|
30325
|
+
const lastOnPage = page.at(-1);
|
|
30326
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeLocationCursor(lastOnPage) : null;
|
|
30327
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinkedLocation(sorted, page, query.includeId);
|
|
30068
30328
|
return Promise.resolve({
|
|
30069
|
-
totals: { findings: total,
|
|
30070
|
-
|
|
30071
|
-
|
|
30329
|
+
totals: { findings: total, locations: sorted.length },
|
|
30330
|
+
facets: accumulator.facets(),
|
|
30331
|
+
items: [...page, ...deepLinked ? [deepLinked] : []],
|
|
30332
|
+
nextCursor
|
|
30072
30333
|
});
|
|
30073
30334
|
}
|
|
30074
|
-
/**
|
|
30075
|
-
* Each group's newest instances, for the table's expanded rows.
|
|
30076
|
-
*
|
|
30077
|
-
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
30078
|
-
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
30079
|
-
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
30080
|
-
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
30081
|
-
* sorts the survivors again for the page order. Both sorts grow with the
|
|
30082
|
-
* store while the answer does not.
|
|
30083
|
-
*
|
|
30084
|
-
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
30085
|
-
* (or the session or window index the scope names — see `findingScanSql`),
|
|
30086
|
-
* which is already the order the page wants, and keeps rows per rule until
|
|
30087
|
-
* each rule has as many as it can show. The aggregate the caller already holds
|
|
30088
|
-
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
30089
|
-
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
30090
|
-
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
30091
|
-
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
30092
|
-
* store with many firing rules widens it. The bound that DOES hold
|
|
30093
|
-
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
30094
|
-
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
30095
|
-
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
30096
|
-
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
30097
|
-
* wanted instances sitting at the tail of the scope — is one pass over
|
|
30098
|
-
* everything in scope with a block sort of the id tie-break only, never a
|
|
30099
|
-
* sort of the scope, which is still that floor.
|
|
30100
|
-
*
|
|
30101
|
-
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
30102
|
-
* run without a shared snapshot, so a capture landing between them can add a
|
|
30103
|
-
* rule here that has no counts there, and the counts are what the group is
|
|
30104
|
-
* built from.
|
|
30105
|
-
*/
|
|
30106
|
-
previewRows(aggregates, scope) {
|
|
30107
|
-
const wanted = /* @__PURE__ */ new Map();
|
|
30108
|
-
let remaining = 0;
|
|
30109
|
-
for (const [ruleId, agg] of aggregates) {
|
|
30110
|
-
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
30111
|
-
wanted.set(ruleId, n);
|
|
30112
|
-
remaining += n;
|
|
30113
|
-
}
|
|
30114
|
-
const rows = [];
|
|
30115
|
-
if (remaining === 0) return rows;
|
|
30116
|
-
const { sql, params } = this.findingScanSql(scope);
|
|
30117
|
-
const taken = /* @__PURE__ */ new Map();
|
|
30118
|
-
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
30119
|
-
const want = wanted.get(r.rule_id);
|
|
30120
|
-
if (want === void 0) continue;
|
|
30121
|
-
const have = taken.get(r.rule_id) ?? 0;
|
|
30122
|
-
if (have >= want) continue;
|
|
30123
|
-
taken.set(r.rule_id, have + 1);
|
|
30124
|
-
rows.push(r);
|
|
30125
|
-
remaining -= 1;
|
|
30126
|
-
if (remaining === 0) break;
|
|
30127
|
-
}
|
|
30128
|
-
return rows;
|
|
30129
|
-
}
|
|
30130
30335
|
/**
|
|
30131
30336
|
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
30132
30337
|
*
|
|
@@ -30153,6 +30358,33 @@ var SqliteFindingsRepository = class {
|
|
|
30153
30358
|
yield toFlatFindingRow(r);
|
|
30154
30359
|
}
|
|
30155
30360
|
}
|
|
30361
|
+
/**
|
|
30362
|
+
* One finding by its own id, or null when no such row exists.
|
|
30363
|
+
*
|
|
30364
|
+
* A primary-key seek on `inspection_findings`, so its cost does not grow with
|
|
30365
|
+
* the store — and, unlike anything derived from a list page, it resolves a
|
|
30366
|
+
* finding of ANY age. That is what the Findings page's one-shot `?finding=`
|
|
30367
|
+
* deep link needs: the id it carries may name a finding thousands of rows
|
|
30368
|
+
* older than anything a first page holds.
|
|
30369
|
+
*
|
|
30370
|
+
* Deliberately UNFILTERED — no capture-kind, session or time predicate. It
|
|
30371
|
+
* RESOLVES an id; whether that row would survive the list's current filters is
|
|
30372
|
+
* a different question, and hiding the target because a filter excludes it is
|
|
30373
|
+
* worse than showing it.
|
|
30374
|
+
*
|
|
30375
|
+
* `groupId` on the result IS the rule id, so this one read answers both "which
|
|
30376
|
+
* type should the list select?" and "what does the drawer show?".
|
|
30377
|
+
*/
|
|
30378
|
+
findingInstance(id) {
|
|
30379
|
+
const row = this.db.prepare(
|
|
30380
|
+
`SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
30381
|
+
FROM inspection_findings f
|
|
30382
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30383
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
30384
|
+
WHERE f.id = ?`
|
|
30385
|
+
).get(id);
|
|
30386
|
+
return Promise.resolve(row === void 0 ? null : toInstanceDetail(toFlatFindingRow(row)));
|
|
30387
|
+
}
|
|
30156
30388
|
/**
|
|
30157
30389
|
* The one statement both instance-level scans run: every finding in scope,
|
|
30158
30390
|
* joined to its event and definition, newest first.
|
|
@@ -30186,17 +30418,7 @@ var SqliteFindingsRepository = class {
|
|
|
30186
30418
|
conditions.push("e.started_at >= ?");
|
|
30187
30419
|
params.push(isoToEpochMillis(scope.from));
|
|
30188
30420
|
}
|
|
30189
|
-
const sql = `SELECT
|
|
30190
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
30191
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
30192
|
-
e.started_at AS occurred_at,
|
|
30193
|
-
e.source_tool AS source_tool,
|
|
30194
|
-
e.repo AS repo,
|
|
30195
|
-
e.file_path AS file,
|
|
30196
|
-
e.tool_name AS tool_name,
|
|
30197
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
30198
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
30199
|
-
${latestResolutionStatusSql("f")} AS latest_status
|
|
30421
|
+
const sql = `SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
30200
30422
|
FROM audit_events e
|
|
30201
30423
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
30202
30424
|
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -30210,6 +30432,26 @@ var SqliteFindingsRepository = class {
|
|
|
30210
30432
|
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
30211
30433
|
const rows = this.db.prepare(
|
|
30212
30434
|
`SELECT rule_id,
|
|
30435
|
+
-- BARE columns beside max(latest_at), which is deliberate and
|
|
30436
|
+
-- is SQLite's documented behaviour: with a single min()/max()
|
|
30437
|
+
-- in an aggregate query, every bare column takes its value from
|
|
30438
|
+
-- the row that produced the extremum. So these are the severity
|
|
30439
|
+
-- and category of the definition whose finding is NEWEST, which
|
|
30440
|
+
-- is what the row-based build they replaced read off its first
|
|
30441
|
+
-- (newest-first) row.
|
|
30442
|
+
--
|
|
30443
|
+
-- min() is WRONG here and was the defect: inspection_definitions
|
|
30444
|
+
-- holds one row per rule VERSION (see its writer \u2014 a version bump
|
|
30445
|
+
-- mints a new row), so a rule whose severity moved between
|
|
30446
|
+
-- versions has several, and min() picks the ALPHABETICALLY
|
|
30447
|
+
-- smallest \u2014 'low' over 'medium', but 'critical' over 'high'.
|
|
30448
|
+
-- That is arbitrary in direction, and it feeds the badge, the
|
|
30449
|
+
-- filter, the facet counts and the primary sort key.
|
|
30450
|
+
--
|
|
30451
|
+
-- Adding a second min()/max() aggregate here would make these
|
|
30452
|
+
-- bare columns ambiguous again; keep max(latest_at) the only one.
|
|
30453
|
+
severity,
|
|
30454
|
+
category,
|
|
30213
30455
|
sum(tuple_count) AS instance_count,
|
|
30214
30456
|
max(latest_at) AS latest_at,
|
|
30215
30457
|
group_concat(source_tools) AS source_tools,
|
|
@@ -30220,6 +30462,14 @@ var SqliteFindingsRepository = class {
|
|
|
30220
30462
|
group_concat(tool_names) AS tool_names
|
|
30221
30463
|
FROM (
|
|
30222
30464
|
SELECT d.rule_id AS rule_id,
|
|
30465
|
+
-- Severity and category are columns of the DEFINITION, and
|
|
30466
|
+
-- a rule can have SEVERAL definitions (one per version), so
|
|
30467
|
+
-- these are grouped on below and resolved to the newest
|
|
30468
|
+
-- firing version by the outer query's bare-column select.
|
|
30469
|
+
-- They ride the aggregate because the type build has no rows
|
|
30470
|
+
-- to read them off \u2014 see buildFindingTypes.
|
|
30471
|
+
d.severity AS severity,
|
|
30472
|
+
d.category AS category,
|
|
30223
30473
|
e.event_type || '${TUPLE_SEP}' ||
|
|
30224
30474
|
(CASE WHEN f.finding_key IS NULL THEN '' ELSE 'k' END) || '${TUPLE_SEP}' ||
|
|
30225
30475
|
coalesce(latest.status, '') AS status_tuple,
|
|
@@ -30234,7 +30484,7 @@ var SqliteFindingsRepository = class {
|
|
|
30234
30484
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
30235
30485
|
ON latest.finding_key = f.finding_key
|
|
30236
30486
|
${scope.predicate}
|
|
30237
|
-
GROUP BY d.rule_id, status_tuple
|
|
30487
|
+
GROUP BY d.rule_id, d.severity, d.category, status_tuple
|
|
30238
30488
|
)
|
|
30239
30489
|
GROUP BY rule_id`
|
|
30240
30490
|
).all(scope.params);
|
|
@@ -30243,6 +30493,8 @@ var SqliteFindingsRepository = class {
|
|
|
30243
30493
|
r.rule_id,
|
|
30244
30494
|
{
|
|
30245
30495
|
instanceCount: r.instance_count,
|
|
30496
|
+
severity: r.severity,
|
|
30497
|
+
category: r.category,
|
|
30246
30498
|
sourceTools: splitConcat(r.source_tools),
|
|
30247
30499
|
actionsTaken: splitConcat(r.actions_taken),
|
|
30248
30500
|
statusInputs: splitConcat(r.status_inputs).map((tuple2) => {
|
|
@@ -30259,7 +30511,7 @@ var SqliteFindingsRepository = class {
|
|
|
30259
30511
|
latestDetectedAt: epochMillisToIso(r.latest_at),
|
|
30260
30512
|
// Free text only — joined and substring-matched, so group_concat's
|
|
30261
30513
|
// commas need no unpicking (a repo/path containing one still matches).
|
|
30262
|
-
// Left undefined (not '') when unfetched, so
|
|
30514
|
+
// Left undefined (not '') when unfetched, so buildFindingTypes can
|
|
30263
30515
|
// tell "no q this request" from "a group with no repo/file at all"
|
|
30264
30516
|
// and skip priming a haystack nothing will read.
|
|
30265
30517
|
...withSearchText ? {
|
|
@@ -30411,6 +30663,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
30411
30663
|
this.markOwedStmt = db.prepare(
|
|
30412
30664
|
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
30413
30665
|
);
|
|
30666
|
+
this.markCaptureBacklogOwedStmt = db.prepare(
|
|
30667
|
+
`UPDATE audit_events SET outbox_owed = 1
|
|
30668
|
+
WHERE synced_at IS NULL
|
|
30669
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
30670
|
+
AND started_at < :before`
|
|
30671
|
+
);
|
|
30414
30672
|
this.stampStmt = db.prepare(
|
|
30415
30673
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
30416
30674
|
);
|
|
@@ -30459,7 +30717,9 @@ var SqliteHistorySyncRepository = class {
|
|
|
30459
30717
|
);
|
|
30460
30718
|
this.disownCapturesStmt = db.prepare(
|
|
30461
30719
|
`UPDATE audit_events SET outbox_owed = NULL
|
|
30462
|
-
WHERE outbox_owed IS NOT NULL
|
|
30720
|
+
WHERE outbox_owed IS NOT NULL
|
|
30721
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
30722
|
+
AND started_at < :attachedAt`
|
|
30463
30723
|
);
|
|
30464
30724
|
this.rearmStmt = db.prepare(
|
|
30465
30725
|
`UPDATE audit_events SET synced_at = NULL
|
|
@@ -30535,6 +30795,7 @@ var SqliteHistorySyncRepository = class {
|
|
|
30535
30795
|
freezeBoundaryStmt;
|
|
30536
30796
|
captureRowsStmt;
|
|
30537
30797
|
markOwedStmt;
|
|
30798
|
+
markCaptureBacklogOwedStmt;
|
|
30538
30799
|
captureSkipCountStmt;
|
|
30539
30800
|
disownCapturesStmt;
|
|
30540
30801
|
partitionStmt;
|
|
@@ -30598,6 +30859,23 @@ var SqliteHistorySyncRepository = class {
|
|
|
30598
30859
|
markCaptureOwed(id) {
|
|
30599
30860
|
this.markOwedStmt.run({ id });
|
|
30600
30861
|
}
|
|
30862
|
+
/**
|
|
30863
|
+
* Mark every capture already on disk as owed, as of `before`.
|
|
30864
|
+
*
|
|
30865
|
+
* The consent-time backfill, called once from `aka attach` when a human
|
|
30866
|
+
* grants existing-history consent — never from an ongoing drain pass, and
|
|
30867
|
+
* never inferred from a boundary that could later move. `before` is the
|
|
30868
|
+
* caller's own "now" at the moment consent was granted, so what this marks
|
|
30869
|
+
* is exactly the backlog the consent prompt already counted, not whatever a
|
|
30870
|
+
* later re-attach or key rotation might widen it to.
|
|
30871
|
+
*
|
|
30872
|
+
* Returns how many rows matched, for the caller to log or test against. Not a
|
|
30873
|
+
* count of NEWLY marked rows — a row still unsynced from an earlier call
|
|
30874
|
+
* matches again and is counted again, the same as `UPDATE`'s own `changes`.
|
|
30875
|
+
*/
|
|
30876
|
+
markCaptureBacklogOwed(before) {
|
|
30877
|
+
return Number(this.markCaptureBacklogOwedStmt.run({ before }).changes);
|
|
30878
|
+
}
|
|
30601
30879
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
30602
30880
|
markSynced(ids, atMs) {
|
|
30603
30881
|
this.stampAll(ids, atMs);
|
|
@@ -30714,15 +30992,42 @@ var SqliteHistorySyncRepository = class {
|
|
|
30714
30992
|
*
|
|
30715
30993
|
* Delivery is a fact about ONE recipient: rows sent to the deployment a
|
|
30716
30994
|
* machine has just left are undelivered as far as the new one is concerned.
|
|
30717
|
-
* All
|
|
30718
|
-
* attributed to the wrong deployment,
|
|
30995
|
+
* All four in one transaction, so a crash between them cannot leave stamps
|
|
30996
|
+
* attributed to the wrong deployment, a boundary that belongs to another, or
|
|
30997
|
+
* a disown with no re-mark to follow it.
|
|
30719
30998
|
*
|
|
30720
30999
|
* The boundary is written HERE and only here, which is what freezes it: a
|
|
30721
31000
|
* re-attach to the SAME deployment (a key rotation) leaves the fingerprint
|
|
30722
31001
|
* unchanged, so this never runs and the backlog does not widen back over rows
|
|
30723
31002
|
* the live path has since delivered.
|
|
31003
|
+
*
|
|
31004
|
+
* `backfillCapturesBefore` is the caller's OWN "now" at the instant a human
|
|
31005
|
+
* granted existing-history consent for the deployment this call is arming —
|
|
31006
|
+
* a DIFFERENT instant from `backlogBefore`: the re-mark boundary is the GRANT
|
|
31007
|
+
* instant, `backlogBefore` is the ATTACH instant, and the two can be far
|
|
31008
|
+
* apart. Passed only when that grant is valid, since this method has no way
|
|
31009
|
+
* to check consent itself and must not mark a row owed for a machine that
|
|
31010
|
+
* never agreed to it. Applied AFTER the disown above, in the SAME
|
|
31011
|
+
* transaction: what the disown clears is every marker below `backlogBefore`,
|
|
31012
|
+
* which includes this deployment's OWN pre-attach rows — `aka attach` calls
|
|
31013
|
+
* `seedCaptureBacklogOwed` at the attach instant, so every row it marks sits
|
|
31014
|
+
* on the cleared side of that bound — and the re-mark in the same
|
|
31015
|
+
* transaction is what puts those rows back. A crash between the two cannot
|
|
31016
|
+
* strand the ledger disowned with nothing re-marked — the transaction either
|
|
31017
|
+
* lands whole or not at all, and a fingerprint mismatch that has not yet
|
|
31018
|
+
* committed re-enters this method on the very next pass. Omit it (the
|
|
31019
|
+
* structural-only tests do) to exercise the disown in isolation.
|
|
31020
|
+
*
|
|
31021
|
+
* The disown is bounded by `backlogBefore`, which is what keeps it from
|
|
31022
|
+
* touching a marker the NEW deployment's OWN live path has already set: B's
|
|
31023
|
+
* live path can mark a capture owed from the moment `aka attach` writes the
|
|
31024
|
+
* descriptor, before the drain's first pass ever reaches this method, and
|
|
31025
|
+
* such a row sits at or after the bound rather than below it. What keeps the
|
|
31026
|
+
* disown from eating THIS SAME CALL's own re-mark is the order, not the
|
|
31027
|
+
* bound — disown runs first, re-mark second, both inside the one
|
|
31028
|
+
* transaction above.
|
|
30724
31029
|
*/
|
|
30725
|
-
rearmFor(fingerprint, backlogBefore) {
|
|
31030
|
+
rearmFor(fingerprint, backlogBefore, backfillCapturesBefore) {
|
|
30726
31031
|
this.ensureRowStmt.run();
|
|
30727
31032
|
withTransaction(
|
|
30728
31033
|
this.db,
|
|
@@ -30730,7 +31035,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
30730
31035
|
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
30731
31036
|
this.rearmStmt.run();
|
|
30732
31037
|
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
30733
|
-
this.disownCapturesStmt.run();
|
|
31038
|
+
this.disownCapturesStmt.run({ attachedAt: backlogBefore });
|
|
31039
|
+
}
|
|
31040
|
+
if (backfillCapturesBefore !== void 0) {
|
|
31041
|
+
this.markCaptureBacklogOwedStmt.run({ before: backfillCapturesBefore });
|
|
30734
31042
|
}
|
|
30735
31043
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
30736
31044
|
},
|
|
@@ -30928,19 +31236,19 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
30928
31236
|
};
|
|
30929
31237
|
|
|
30930
31238
|
// ../packages/persistence/src/repositories/installed-packs.ts
|
|
30931
|
-
import { createHash as
|
|
31239
|
+
import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
|
|
30932
31240
|
|
|
30933
31241
|
// ../packages/persistence/src/policy-floor.ts
|
|
30934
|
-
import { readFileSync as
|
|
31242
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
30935
31243
|
import { join as join8 } from "path";
|
|
30936
31244
|
|
|
30937
31245
|
// ../packages/persistence/src/local-layout.ts
|
|
30938
31246
|
import { renameSync as renameSync3 } from "fs";
|
|
30939
31247
|
import { mkdir } from "fs/promises";
|
|
30940
|
-
import { homedir as
|
|
31248
|
+
import { homedir as homedir4 } from "os";
|
|
30941
31249
|
import { join as join6 } from "path";
|
|
30942
31250
|
function defaultDataDir() {
|
|
30943
|
-
return join6(
|
|
31251
|
+
return join6(homedir4(), ".aka");
|
|
30944
31252
|
}
|
|
30945
31253
|
function settingsDir(base = defaultDataDir()) {
|
|
30946
31254
|
return join6(base, "settings");
|
|
@@ -30963,7 +31271,7 @@ function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
|
30963
31271
|
}
|
|
30964
31272
|
|
|
30965
31273
|
// ../packages/persistence/src/settings.ts
|
|
30966
|
-
import { readFileSync as
|
|
31274
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
30967
31275
|
import { join as join7 } from "path";
|
|
30968
31276
|
|
|
30969
31277
|
// ../packages/persistence/src/file-lock.ts
|
|
@@ -30972,10 +31280,10 @@ import {
|
|
|
30972
31280
|
closeSync,
|
|
30973
31281
|
existsSync as existsSync4,
|
|
30974
31282
|
openSync,
|
|
30975
|
-
readFileSync as
|
|
30976
|
-
rmSync as
|
|
31283
|
+
readFileSync as readFileSync5,
|
|
31284
|
+
rmSync as rmSync6,
|
|
30977
31285
|
statSync as statSync3,
|
|
30978
|
-
writeFileSync as
|
|
31286
|
+
writeFileSync as writeFileSync3
|
|
30979
31287
|
} from "fs";
|
|
30980
31288
|
import { hostname as hostname3 } from "os";
|
|
30981
31289
|
var LOCK_SUFFIX = ".lock";
|
|
@@ -31008,7 +31316,7 @@ function lockPathFor(file2) {
|
|
|
31008
31316
|
function readLockBody(lock) {
|
|
31009
31317
|
let raw;
|
|
31010
31318
|
try {
|
|
31011
|
-
raw =
|
|
31319
|
+
raw = readFileSync5(lock, "utf8");
|
|
31012
31320
|
} catch {
|
|
31013
31321
|
return null;
|
|
31014
31322
|
}
|
|
@@ -31039,7 +31347,7 @@ function directoryAcceptsCreates(lock) {
|
|
|
31039
31347
|
return false;
|
|
31040
31348
|
} finally {
|
|
31041
31349
|
try {
|
|
31042
|
-
|
|
31350
|
+
rmSync6(probe, { force: true });
|
|
31043
31351
|
} catch {
|
|
31044
31352
|
}
|
|
31045
31353
|
}
|
|
@@ -31065,14 +31373,14 @@ function tryAcquire(lock, file2) {
|
|
|
31065
31373
|
}
|
|
31066
31374
|
const body = { pid: process.pid, token, at: Date.now(), host: hostname3() };
|
|
31067
31375
|
try {
|
|
31068
|
-
|
|
31376
|
+
writeFileSync3(fd, `${JSON.stringify(body)}
|
|
31069
31377
|
`);
|
|
31070
31378
|
} catch {
|
|
31071
31379
|
try {
|
|
31072
31380
|
closeSync(fd);
|
|
31073
31381
|
} catch {
|
|
31074
31382
|
}
|
|
31075
|
-
|
|
31383
|
+
rmSync6(lock, { force: true });
|
|
31076
31384
|
return null;
|
|
31077
31385
|
}
|
|
31078
31386
|
try {
|
|
@@ -31108,13 +31416,13 @@ function breakIfStale(lock, staleMs) {
|
|
|
31108
31416
|
}
|
|
31109
31417
|
try {
|
|
31110
31418
|
if (!existsSync4(lock) || !isAbandoned(readLockBody(lock), lock, staleMs)) return false;
|
|
31111
|
-
|
|
31419
|
+
rmSync6(lock, { force: true });
|
|
31112
31420
|
return true;
|
|
31113
31421
|
} catch {
|
|
31114
31422
|
return false;
|
|
31115
31423
|
} finally {
|
|
31116
31424
|
try {
|
|
31117
|
-
|
|
31425
|
+
rmSync6(breaker2, { force: true });
|
|
31118
31426
|
} catch {
|
|
31119
31427
|
}
|
|
31120
31428
|
}
|
|
@@ -31123,7 +31431,7 @@ var BREAKER_ABANDONED_MS = 1e4;
|
|
|
31123
31431
|
function reapAbandonedBreaker(breaker2) {
|
|
31124
31432
|
try {
|
|
31125
31433
|
if (Date.now() - statSync3(breaker2).mtimeMs >= BREAKER_ABANDONED_MS) {
|
|
31126
|
-
|
|
31434
|
+
rmSync6(breaker2, { force: true });
|
|
31127
31435
|
}
|
|
31128
31436
|
} catch {
|
|
31129
31437
|
}
|
|
@@ -31134,7 +31442,7 @@ function abandonWindow(staleMs) {
|
|
|
31134
31442
|
function release(lock, token) {
|
|
31135
31443
|
try {
|
|
31136
31444
|
if (readLockBody(lock)?.token !== token) return;
|
|
31137
|
-
|
|
31445
|
+
rmSync6(lock, { force: true });
|
|
31138
31446
|
} catch {
|
|
31139
31447
|
}
|
|
31140
31448
|
}
|
|
@@ -31181,25 +31489,26 @@ function isThenable(value) {
|
|
|
31181
31489
|
}
|
|
31182
31490
|
|
|
31183
31491
|
// ../packages/persistence/src/managed-settings.ts
|
|
31184
|
-
import { readFileSync as
|
|
31185
|
-
import { posix, win32 } from "path";
|
|
31492
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
31493
|
+
import { posix as posix2, win32 } from "path";
|
|
31186
31494
|
function managedSettingsPaths(platform3 = process.platform) {
|
|
31187
31495
|
if (platform3 === "darwin") {
|
|
31188
31496
|
return [
|
|
31189
|
-
|
|
31190
|
-
|
|
31497
|
+
posix2.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
31498
|
+
posix2.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
31191
31499
|
];
|
|
31192
31500
|
}
|
|
31193
31501
|
if (platform3 === "win32") {
|
|
31194
31502
|
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
31195
31503
|
}
|
|
31196
|
-
return [
|
|
31504
|
+
return [posix2.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
31197
31505
|
}
|
|
31198
|
-
|
|
31506
|
+
var testOnlyManagedPaths = null;
|
|
31507
|
+
function readManagedSettings(paths = testOnlyManagedPaths ?? managedSettingsPaths()) {
|
|
31199
31508
|
for (const path of paths) {
|
|
31200
31509
|
let text;
|
|
31201
31510
|
try {
|
|
31202
|
-
text =
|
|
31511
|
+
text = readFileSync6(path, "utf8");
|
|
31203
31512
|
} catch {
|
|
31204
31513
|
continue;
|
|
31205
31514
|
}
|
|
@@ -31215,7 +31524,8 @@ function managedContextOf(managed) {
|
|
|
31215
31524
|
return {
|
|
31216
31525
|
present: true,
|
|
31217
31526
|
...managed.organization === void 0 ? {} : { organization: managed.organization },
|
|
31218
|
-
lockedFields: managed.lockedFields
|
|
31527
|
+
lockedFields: managed.lockedFields,
|
|
31528
|
+
...managed.unknownLockedFields === void 0 ? {} : { unknownLockedFields: managed.unknownLockedFields }
|
|
31219
31529
|
};
|
|
31220
31530
|
}
|
|
31221
31531
|
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
@@ -31368,7 +31678,7 @@ function applyOnboarding(answers, base = defaultDataDir(), managedOverride) {
|
|
|
31368
31678
|
function readJson(file2) {
|
|
31369
31679
|
let text;
|
|
31370
31680
|
try {
|
|
31371
|
-
text =
|
|
31681
|
+
text = readFileSync7(file2, "utf8");
|
|
31372
31682
|
} catch {
|
|
31373
31683
|
return null;
|
|
31374
31684
|
}
|
|
@@ -31408,7 +31718,7 @@ var PolicyFloorError = class extends Error {
|
|
|
31408
31718
|
};
|
|
31409
31719
|
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
31410
31720
|
try {
|
|
31411
|
-
const raw =
|
|
31721
|
+
const raw = readFileSync8(join8(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
31412
31722
|
const parsed2 = JSON.parse(raw);
|
|
31413
31723
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
31414
31724
|
return PolicyBundle.parse(parsed2.bundle);
|
|
@@ -31532,7 +31842,7 @@ function inventorySignature(packs2) {
|
|
|
31532
31842
|
return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
|
|
31533
31843
|
}
|
|
31534
31844
|
function hashRules(rulesJson) {
|
|
31535
|
-
return
|
|
31845
|
+
return createHash3("sha256").update(rulesJson).digest("hex");
|
|
31536
31846
|
}
|
|
31537
31847
|
function parseVersion(v) {
|
|
31538
31848
|
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
@@ -33780,7 +34090,7 @@ function toUtcDateString(ms) {
|
|
|
33780
34090
|
return new Date(ms).toISOString().slice(0, 10);
|
|
33781
34091
|
}
|
|
33782
34092
|
function isTimeseriesSeverity(s) {
|
|
33783
|
-
return s === "critical" || s === "high" || s === "medium";
|
|
34093
|
+
return s === "critical" || s === "high" || s === "medium" || s === "low";
|
|
33784
34094
|
}
|
|
33785
34095
|
var SqliteSecurityRepository = class {
|
|
33786
34096
|
constructor(db, now = () => Date.now()) {
|
|
@@ -33909,12 +34219,16 @@ var SqliteSecurityRepository = class {
|
|
|
33909
34219
|
const now = this.now();
|
|
33910
34220
|
const windowStart = startOfUtcDay2(now) - (lenDays - 1) * DAY_MS4;
|
|
33911
34221
|
const rows = this.findingsInRange(windowStart, now);
|
|
33912
|
-
const points = Array.from(
|
|
33913
|
-
|
|
33914
|
-
|
|
33915
|
-
|
|
33916
|
-
|
|
33917
|
-
|
|
34222
|
+
const points = Array.from(
|
|
34223
|
+
{ length: numBuckets },
|
|
34224
|
+
(_, i) => ({
|
|
34225
|
+
timestamp: toUtcDateString(windowStart + i * bucketMs),
|
|
34226
|
+
critical: 0,
|
|
34227
|
+
high: 0,
|
|
34228
|
+
medium: 0,
|
|
34229
|
+
low: 0
|
|
34230
|
+
})
|
|
34231
|
+
);
|
|
33918
34232
|
for (const r of rows) {
|
|
33919
34233
|
const idx = Math.floor((r.occurredAt - windowStart) / bucketMs);
|
|
33920
34234
|
const bucket = points[idx];
|
|
@@ -34132,6 +34446,7 @@ var SqliteSecurityRepository = class {
|
|
|
34132
34446
|
`SELECT f.finding_key AS finding_key,
|
|
34133
34447
|
d.rule_id AS rule_id,
|
|
34134
34448
|
d.severity AS severity,
|
|
34449
|
+
e.repo AS repo,
|
|
34135
34450
|
e.file_path AS path,
|
|
34136
34451
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
34137
34452
|
latest.resolved_at AS latest_resolved_at
|
|
@@ -34151,6 +34466,7 @@ var SqliteSecurityRepository = class {
|
|
|
34151
34466
|
const items = rows.map((r) => ({
|
|
34152
34467
|
findingKey: r.finding_key,
|
|
34153
34468
|
ruleId: r.rule_id,
|
|
34469
|
+
repo: r.repo ?? "",
|
|
34154
34470
|
severity: r.severity,
|
|
34155
34471
|
path: r.path ?? "",
|
|
34156
34472
|
resolvedAt: new Date(r.latest_resolved_at).toISOString(),
|
|
@@ -34160,13 +34476,66 @@ var SqliteSecurityRepository = class {
|
|
|
34160
34476
|
}));
|
|
34161
34477
|
return Promise.resolve({ items });
|
|
34162
34478
|
}
|
|
34479
|
+
/**
|
|
34480
|
+
* Per-rule tallies of the findings that are still OPEN, whole-store.
|
|
34481
|
+
*
|
|
34482
|
+
* Scoped by status rather than by time, because the card this feeds is a to-do
|
|
34483
|
+
* list: a secret committed three weeks ago and never rotated is still the most
|
|
34484
|
+
* important thing to fix, and any window hides it. It carried a "newest N
|
|
34485
|
+
* findings" cap and then a range; the first meant a different span on every
|
|
34486
|
+
* machine, and the second reported "no recommendations" over live exposure.
|
|
34487
|
+
*
|
|
34488
|
+
* `open` mirrors `deriveFindingStatus` — at-rest, minus resolved and dismissed —
|
|
34489
|
+
* so a row's count is exactly what `?status=open&type=<rule>` returns. Note that
|
|
34490
|
+
* is NOT `severitySummary`'s `openAtRest`, which keeps dismissed findings (a
|
|
34491
|
+
* dismissal is a judgement, not a remediation) and drops untracked legacy rows.
|
|
34492
|
+
* The two answer different questions and only this one has to match a link.
|
|
34493
|
+
*
|
|
34494
|
+
* Aggregated in SQL: the result is O(distinct rule × category × severity), so a
|
|
34495
|
+
* whole-store scope costs a grouped scan rather than a row per finding.
|
|
34496
|
+
*/
|
|
34497
|
+
recommendationInputs() {
|
|
34498
|
+
const rows = allRows(
|
|
34499
|
+
this.db.prepare(
|
|
34500
|
+
`SELECT d.rule_id AS rule_id,
|
|
34501
|
+
d.category AS category,
|
|
34502
|
+
d.severity AS severity,
|
|
34503
|
+
COUNT(*) AS count
|
|
34504
|
+
FROM inspection_findings f
|
|
34505
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
34506
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
34507
|
+
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
34508
|
+
ON latest.finding_key = f.finding_key
|
|
34509
|
+
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
34510
|
+
AND e.event_type = 'code_change'
|
|
34511
|
+
AND (
|
|
34512
|
+
f.finding_key IS NULL
|
|
34513
|
+
OR latest.status IS NULL
|
|
34514
|
+
OR latest.status NOT IN ('resolved', 'dismissed')
|
|
34515
|
+
)
|
|
34516
|
+
GROUP BY d.rule_id, d.category, d.severity`
|
|
34517
|
+
)
|
|
34518
|
+
);
|
|
34519
|
+
return Promise.resolve(
|
|
34520
|
+
rows.map((r) => ({
|
|
34521
|
+
ruleId: r.rule_id,
|
|
34522
|
+
category: r.category,
|
|
34523
|
+
severity: r.severity,
|
|
34524
|
+
count: r.count
|
|
34525
|
+
}))
|
|
34526
|
+
);
|
|
34527
|
+
}
|
|
34163
34528
|
// Findings whose parent event occurred in [fromMs, toMs), with the parent's
|
|
34164
34529
|
// epoch-millis timestamp. started_at is an INTEGER column, so the bounds stay
|
|
34165
34530
|
// numeric and the JS aggregations bucket/split on ms directly.
|
|
34166
34531
|
findingsInRange(fromMs, toMs) {
|
|
34167
34532
|
const rows = allRows(
|
|
34168
34533
|
this.db.prepare(
|
|
34169
|
-
`
|
|
34534
|
+
// `rule_id`/`category` cost nothing extra: inspection_definitions is already
|
|
34535
|
+
// joined for `severity`, so they are two more columns off a row this read
|
|
34536
|
+
// already fetches. They feed the recommended-actions rollup.
|
|
34537
|
+
`SELECT e.started_at AS occurred_at, d.severity AS severity, f.action_taken AS action_taken,
|
|
34538
|
+
d.rule_id AS rule_id, d.category AS category
|
|
34170
34539
|
FROM inspection_findings f
|
|
34171
34540
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
34172
34541
|
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -34179,7 +34548,9 @@ var SqliteSecurityRepository = class {
|
|
|
34179
34548
|
return rows.map((r) => ({
|
|
34180
34549
|
occurredAt: r.occurred_at,
|
|
34181
34550
|
severity: r.severity,
|
|
34182
|
-
actionTaken: r.action_taken
|
|
34551
|
+
actionTaken: r.action_taken,
|
|
34552
|
+
ruleId: r.rule_id,
|
|
34553
|
+
category: r.category
|
|
34183
34554
|
}));
|
|
34184
34555
|
}
|
|
34185
34556
|
};
|
|
@@ -35349,19 +35720,56 @@ function openLocalDatabase(dir) {
|
|
|
35349
35720
|
};
|
|
35350
35721
|
}
|
|
35351
35722
|
|
|
35723
|
+
// ../packages/persistence/src/egress-wire.ts
|
|
35724
|
+
import { createHash as createHash4 } from "crypto";
|
|
35725
|
+
function hashProjectKey(projectKey) {
|
|
35726
|
+
return createHash4("sha256").update(projectKey, "utf8").digest("hex");
|
|
35727
|
+
}
|
|
35728
|
+
function toIngestHit(hit) {
|
|
35729
|
+
return {
|
|
35730
|
+
host: hit.host,
|
|
35731
|
+
kind: hit.kind,
|
|
35732
|
+
name: hit.name,
|
|
35733
|
+
category: hit.category,
|
|
35734
|
+
trust: hit.trust,
|
|
35735
|
+
network: hit.network,
|
|
35736
|
+
method: hit.method,
|
|
35737
|
+
transport: hit.transport,
|
|
35738
|
+
url: hit.url,
|
|
35739
|
+
template: hit.template,
|
|
35740
|
+
dataClass: hit.dataClass,
|
|
35741
|
+
site: {
|
|
35742
|
+
file: hit.site.file,
|
|
35743
|
+
line: hit.site.line,
|
|
35744
|
+
dynamic: hit.site.dynamic,
|
|
35745
|
+
vendored: hit.site.vendored
|
|
35746
|
+
}
|
|
35747
|
+
};
|
|
35748
|
+
}
|
|
35749
|
+
function toEgressIngestRequest(input2) {
|
|
35750
|
+
const { hits, droppedFiles } = capHits(input2.hits, input2.reconcile.mode);
|
|
35751
|
+
const reconcile = withoutDroppedFiles(input2.reconcile, droppedFiles);
|
|
35752
|
+
return {
|
|
35753
|
+
projectKey: hashProjectKey(input2.projectKey),
|
|
35754
|
+
project: input2.project,
|
|
35755
|
+
reconcile,
|
|
35756
|
+
hits: hits.map(toIngestHit)
|
|
35757
|
+
};
|
|
35758
|
+
}
|
|
35759
|
+
|
|
35352
35760
|
// ../packages/persistence/src/finding-key.ts
|
|
35353
|
-
import { createHash as
|
|
35761
|
+
import { createHash as createHash5 } from "crypto";
|
|
35354
35762
|
function normalizeFilePath(filePath) {
|
|
35355
35763
|
return filePath.replaceAll("\\", "/");
|
|
35356
35764
|
}
|
|
35357
35765
|
function computeFindingKey(input2) {
|
|
35358
35766
|
const normalizedPath = normalizeFilePath(input2.filePath);
|
|
35359
|
-
return
|
|
35767
|
+
return createHash5("sha256").update(`${input2.ruleId}\0${normalizedPath}\0${input2.valueFingerprint}`).digest("hex");
|
|
35360
35768
|
}
|
|
35361
35769
|
|
|
35362
35770
|
// ../packages/persistence/src/fingerprint.ts
|
|
35363
35771
|
import { createHmac, randomBytes } from "crypto";
|
|
35364
|
-
import { existsSync as existsSync5, readFileSync as
|
|
35772
|
+
import { existsSync as existsSync5, readFileSync as readFileSync9 } from "fs";
|
|
35365
35773
|
import { join as join10 } from "path";
|
|
35366
35774
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
35367
35775
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
@@ -35466,7 +35874,7 @@ function occupantMessage(file2, kind) {
|
|
|
35466
35874
|
function readFingerprintKey(dataDir2) {
|
|
35467
35875
|
let raw;
|
|
35468
35876
|
try {
|
|
35469
|
-
raw =
|
|
35877
|
+
raw = readFileSync9(keyFilePath(dataDir2), "utf8");
|
|
35470
35878
|
} catch (err) {
|
|
35471
35879
|
if (err.code === "ENOENT") return null;
|
|
35472
35880
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -35501,15 +35909,31 @@ function keyStateOf(key) {
|
|
|
35501
35909
|
return key === null ? { status: "absent" } : { status: "present", version: key.version };
|
|
35502
35910
|
}
|
|
35503
35911
|
|
|
35504
|
-
// ../packages/persistence/src/history-
|
|
35912
|
+
// ../packages/persistence/src/history-backfill.ts
|
|
35505
35913
|
import { existsSync as existsSync6 } from "fs";
|
|
35506
35914
|
import { join as join11 } from "path";
|
|
35915
|
+
function seedCaptureBacklogOwed(dataDir2, beforeMs) {
|
|
35916
|
+
if (!existsSync6(join11(dataDir2, DB_FILENAME))) return;
|
|
35917
|
+
try {
|
|
35918
|
+
const db = openLocalDatabase(dataDir2);
|
|
35919
|
+
try {
|
|
35920
|
+
db.historySync.markCaptureBacklogOwed(beforeMs);
|
|
35921
|
+
} finally {
|
|
35922
|
+
db.close();
|
|
35923
|
+
}
|
|
35924
|
+
} catch {
|
|
35925
|
+
}
|
|
35926
|
+
}
|
|
35927
|
+
|
|
35928
|
+
// ../packages/persistence/src/history-preview.ts
|
|
35929
|
+
import { existsSync as existsSync7 } from "fs";
|
|
35930
|
+
import { join as join12 } from "path";
|
|
35507
35931
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
35508
35932
|
var PREVIEW_BUSY_TIMEOUT_MS = 250;
|
|
35509
35933
|
var MS_PER_DAY = 864e5;
|
|
35510
35934
|
function readLocalHistoryPreview(dataDir2, nowMs = Date.now()) {
|
|
35511
|
-
const file2 =
|
|
35512
|
-
if (!
|
|
35935
|
+
const file2 = join12(dataDir2, DB_FILENAME);
|
|
35936
|
+
if (!existsSync7(file2)) return { sessions: 0, days: 0 };
|
|
35513
35937
|
let db;
|
|
35514
35938
|
try {
|
|
35515
35939
|
db = new DatabaseSync3(file2, { readOnly: true });
|
|
@@ -35535,8 +35959,8 @@ function readLocalHistoryPreview(dataDir2, nowMs = Date.now()) {
|
|
|
35535
35959
|
}
|
|
35536
35960
|
|
|
35537
35961
|
// ../packages/persistence/src/store-symlinks.ts
|
|
35538
|
-
import { existsSync as
|
|
35539
|
-
import { dirname as dirname5, join as
|
|
35962
|
+
import { existsSync as existsSync8, lstatSync as lstatSync3, readlinkSync, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
35963
|
+
import { dirname as dirname5, join as join13, resolve } from "path";
|
|
35540
35964
|
var STORE_DB = "the store database (including the prompt corpus)";
|
|
35541
35965
|
var STORE_SETTINGS = "your settings file";
|
|
35542
35966
|
function storeContents(home) {
|
|
@@ -35545,7 +35969,7 @@ function storeContents(home) {
|
|
|
35545
35969
|
[settingsDir(home), STORE_SETTINGS],
|
|
35546
35970
|
[dataDir(home), STORE_DB],
|
|
35547
35971
|
[keysDir(home), "the vault key"],
|
|
35548
|
-
[
|
|
35972
|
+
[join13(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
35549
35973
|
[dbPath(home), STORE_DB]
|
|
35550
35974
|
]);
|
|
35551
35975
|
}
|
|
@@ -35563,7 +35987,7 @@ function symlinkedStorePaths(home, platform3 = process.platform) {
|
|
|
35563
35987
|
holds,
|
|
35564
35988
|
// existsSync follows the link, so a target that is gone reads as
|
|
35565
35989
|
// absent here while lstat above still sees the link itself.
|
|
35566
|
-
missing: !
|
|
35990
|
+
missing: !existsSync8(path),
|
|
35567
35991
|
mode: targetMode(path, platform3)
|
|
35568
35992
|
}
|
|
35569
35993
|
];
|
|
@@ -35699,8 +36123,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
35699
36123
|
// ../packages/persistence/src/vault/key-provider.ts
|
|
35700
36124
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
35701
36125
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
35702
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
35703
|
-
import { join as
|
|
36126
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync10, renameSync as renameSync4, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
36127
|
+
import { join as join14 } from "path";
|
|
35704
36128
|
var VAULT_OCCUPANT_REASON = {
|
|
35705
36129
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
35706
36130
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -35799,16 +36223,16 @@ function claimRotationLock(lock, owner) {
|
|
|
35799
36223
|
throw asError(err);
|
|
35800
36224
|
}
|
|
35801
36225
|
try {
|
|
35802
|
-
|
|
36226
|
+
writeFileSync4(join14(lock, LOCK_OWNER_FILE), `${owner}
|
|
35803
36227
|
`, { mode: DATA_FILE_MODE });
|
|
35804
36228
|
return true;
|
|
35805
36229
|
} catch (err) {
|
|
35806
|
-
|
|
36230
|
+
rmSync7(lock, { recursive: true, force: true });
|
|
35807
36231
|
throw asError(err);
|
|
35808
36232
|
}
|
|
35809
36233
|
}
|
|
35810
36234
|
function acquireRotationLock(keysDir2) {
|
|
35811
|
-
const lock =
|
|
36235
|
+
const lock = join14(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
35812
36236
|
const owner = randomBytes2(16).toString("hex");
|
|
35813
36237
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
35814
36238
|
let held;
|
|
@@ -35829,17 +36253,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
35829
36253
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
35830
36254
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
35831
36255
|
}
|
|
35832
|
-
|
|
36256
|
+
rmSync7(aside, { recursive: true, force: true });
|
|
35833
36257
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
35834
36258
|
return { lock, owner };
|
|
35835
36259
|
}
|
|
35836
36260
|
function releaseRotationLock(lease) {
|
|
35837
36261
|
try {
|
|
35838
|
-
if (
|
|
36262
|
+
if (readFileSync10(join14(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
35839
36263
|
} catch {
|
|
35840
36264
|
return;
|
|
35841
36265
|
}
|
|
35842
|
-
|
|
36266
|
+
rmSync7(lease.lock, { recursive: true, force: true });
|
|
35843
36267
|
}
|
|
35844
36268
|
function withRotationLock(keysDir2, work) {
|
|
35845
36269
|
ensureDataDirSync(keysDir2);
|
|
@@ -35856,7 +36280,7 @@ var FileKeyProvider = class {
|
|
|
35856
36280
|
this.#keysDir = keysDir2;
|
|
35857
36281
|
}
|
|
35858
36282
|
get filePath() {
|
|
35859
|
-
return
|
|
36283
|
+
return join14(this.#keysDir, VAULT_KEY_FILENAME);
|
|
35860
36284
|
}
|
|
35861
36285
|
loadOrCreate() {
|
|
35862
36286
|
return asAsync(() => {
|
|
@@ -35886,7 +36310,7 @@ var FileKeyProvider = class {
|
|
|
35886
36310
|
#read() {
|
|
35887
36311
|
let raw;
|
|
35888
36312
|
try {
|
|
35889
|
-
raw =
|
|
36313
|
+
raw = readFileSync10(this.filePath, "utf8");
|
|
35890
36314
|
} catch (err) {
|
|
35891
36315
|
if (err.code === "ENOENT") return null;
|
|
35892
36316
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -36521,12 +36945,12 @@ var SecretVault = class {
|
|
|
36521
36945
|
};
|
|
36522
36946
|
|
|
36523
36947
|
// ../packages/persistence/src/warn-era-cap.ts
|
|
36524
|
-
import { existsSync as
|
|
36525
|
-
import { join as
|
|
36948
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
36949
|
+
import { join as join15 } from "path";
|
|
36526
36950
|
|
|
36527
36951
|
// ../packages/plugin-sdk/src/config.ts
|
|
36528
|
-
import { existsSync as
|
|
36529
|
-
import { join as
|
|
36952
|
+
import { existsSync as existsSync10 } from "fs";
|
|
36953
|
+
import { join as join16 } from "path";
|
|
36530
36954
|
|
|
36531
36955
|
// ../packages/plugin-sdk/src/provider-env.ts
|
|
36532
36956
|
var booleanish = external_exports.string().optional().transform((v) => {
|
|
@@ -36547,9 +36971,9 @@ var providerEnvShape = {
|
|
|
36547
36971
|
var ProviderEnvSchema = external_exports.object(providerEnvShape);
|
|
36548
36972
|
|
|
36549
36973
|
// ../packages/plugin-sdk/src/config-inventory.ts
|
|
36550
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
36551
|
-
import { homedir as
|
|
36552
|
-
import { basename as basename3, join as
|
|
36974
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync12, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
|
|
36975
|
+
import { homedir as homedir5 } from "os";
|
|
36976
|
+
import { basename as basename3, join as join18 } from "path";
|
|
36553
36977
|
|
|
36554
36978
|
// ../rules/code-flaws/auth-jwt-no-verify.json
|
|
36555
36979
|
var auth_jwt_no_verify_default = {
|
|
@@ -38584,8 +39008,8 @@ function bundledDetections() {
|
|
|
38584
39008
|
}
|
|
38585
39009
|
|
|
38586
39010
|
// ../packages/plugin-sdk/src/repo.ts
|
|
38587
|
-
import { existsSync as
|
|
38588
|
-
import { basename as basename2, dirname as dirname6, isAbsolute, join as
|
|
39011
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, statSync as statSync6 } from "fs";
|
|
39012
|
+
import { basename as basename2, dirname as dirname6, isAbsolute, join as join17, sep as sep3 } from "path";
|
|
38589
39013
|
function resolveRepoIdentity(cwd) {
|
|
38590
39014
|
try {
|
|
38591
39015
|
const root = findGitRoot(cwd);
|
|
@@ -38623,36 +39047,36 @@ function resolveHeadRoot(cwd) {
|
|
|
38623
39047
|
function findGitRoot(start) {
|
|
38624
39048
|
let dir = start;
|
|
38625
39049
|
for (; ; ) {
|
|
38626
|
-
if (
|
|
39050
|
+
if (existsSync11(join17(dir, ".git"))) return dir;
|
|
38627
39051
|
const parent = dirname6(dir);
|
|
38628
39052
|
if (parent === dir) return void 0;
|
|
38629
39053
|
dir = parent;
|
|
38630
39054
|
}
|
|
38631
39055
|
}
|
|
38632
39056
|
function resolveGitContext(root) {
|
|
38633
|
-
const dotGit =
|
|
39057
|
+
const dotGit = join17(root, ".git");
|
|
38634
39058
|
try {
|
|
38635
39059
|
if (statSync6(dotGit).isDirectory()) {
|
|
38636
|
-
return { configPath:
|
|
39060
|
+
return { configPath: join17(dotGit, "config"), headRoot: root };
|
|
38637
39061
|
}
|
|
38638
39062
|
} catch {
|
|
38639
39063
|
return void 0;
|
|
38640
39064
|
}
|
|
38641
39065
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
38642
39066
|
if (!target) return void 0;
|
|
38643
|
-
const gitdir = isAbsolute(target) ? target :
|
|
38644
|
-
if (
|
|
38645
|
-
return { configPath:
|
|
39067
|
+
const gitdir = isAbsolute(target) ? target : join17(root, target);
|
|
39068
|
+
if (existsSync11(join17(gitdir, "config"))) {
|
|
39069
|
+
return { configPath: join17(gitdir, "config"), headRoot: root };
|
|
38646
39070
|
}
|
|
38647
|
-
const commonRaw = safeRead(
|
|
39071
|
+
const commonRaw = safeRead(join17(gitdir, "commondir"))?.trim();
|
|
38648
39072
|
if (!commonRaw) return void 0;
|
|
38649
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
39073
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join17(gitdir, commonRaw);
|
|
38650
39074
|
const headRoot = basename2(commonGitDir) === ".git" ? dirname6(commonGitDir) : root;
|
|
38651
|
-
return { configPath:
|
|
39075
|
+
return { configPath: join17(commonGitDir, "config"), headRoot };
|
|
38652
39076
|
}
|
|
38653
39077
|
function safeRead(path) {
|
|
38654
39078
|
try {
|
|
38655
|
-
return
|
|
39079
|
+
return readFileSync11(path, "utf8");
|
|
38656
39080
|
} catch {
|
|
38657
39081
|
return void 0;
|
|
38658
39082
|
}
|
|
@@ -38690,20 +39114,104 @@ function slugFromUrl(url2) {
|
|
|
38690
39114
|
}
|
|
38691
39115
|
|
|
38692
39116
|
// ../packages/plugin-sdk/src/events.ts
|
|
38693
|
-
import { createHash as
|
|
39117
|
+
import { createHash as createHash6, randomUUID as randomUUID13 } from "crypto";
|
|
38694
39118
|
|
|
38695
39119
|
// ../packages/plugin-sdk/src/isolated-scan.ts
|
|
38696
|
-
import { existsSync as
|
|
39120
|
+
import { existsSync as existsSync12 } from "fs";
|
|
38697
39121
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
38698
39122
|
import { Worker } from "worker_threads";
|
|
38699
39123
|
|
|
39124
|
+
// ../packages/plugin-sdk/src/host-floor.ts
|
|
39125
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
39126
|
+
import { join as join20 } from "path";
|
|
39127
|
+
|
|
39128
|
+
// ../packages/plugin-sdk/src/model-governance.ts
|
|
39129
|
+
import {
|
|
39130
|
+
closeSync as closeSync2,
|
|
39131
|
+
fstatSync,
|
|
39132
|
+
mkdirSync as mkdirSync3,
|
|
39133
|
+
openSync as openSync2,
|
|
39134
|
+
readFileSync as readFileSync13,
|
|
39135
|
+
readSync,
|
|
39136
|
+
writeFileSync as writeFileSync6
|
|
39137
|
+
} from "fs";
|
|
39138
|
+
import { join as join19 } from "path";
|
|
39139
|
+
var TAIL_BYTES = 256 * 1024;
|
|
39140
|
+
|
|
39141
|
+
// ../packages/plugin-sdk/src/host-floor.ts
|
|
39142
|
+
var HOST_FEATURE = {
|
|
39143
|
+
ModelSwitch: "model-switch",
|
|
39144
|
+
VaultPointerDisplay: "vault-pointer-display"
|
|
39145
|
+
};
|
|
39146
|
+
var HOST_FLOORS = {
|
|
39147
|
+
[HOST_FEATURE.ModelSwitch]: {
|
|
39148
|
+
label: "model-switch protection",
|
|
39149
|
+
hookEvents: ["PreModelSwitch", "PostModelSwitch"],
|
|
39150
|
+
since: "2.1.251"
|
|
39151
|
+
},
|
|
39152
|
+
[HOST_FEATURE.VaultPointerDisplay]: {
|
|
39153
|
+
label: "vault pointer display",
|
|
39154
|
+
hookEvents: ["MessageDisplay"],
|
|
39155
|
+
since: "2.1.152"
|
|
39156
|
+
}
|
|
39157
|
+
};
|
|
39158
|
+
function hostFloorGaps(hostVersion) {
|
|
39159
|
+
if (hostVersion === void 0) return [];
|
|
39160
|
+
const gaps = [];
|
|
39161
|
+
for (const [feature, row] of Object.entries(HOST_FLOORS)) {
|
|
39162
|
+
if (compareBinaryVersions(hostVersion, row.since) < 0) {
|
|
39163
|
+
gaps.push({ feature, label: row.label, since: row.since });
|
|
39164
|
+
}
|
|
39165
|
+
}
|
|
39166
|
+
return gaps;
|
|
39167
|
+
}
|
|
39168
|
+
function requiredHostVersion(gaps) {
|
|
39169
|
+
let highest;
|
|
39170
|
+
for (const gap of gaps) {
|
|
39171
|
+
if (highest === void 0 || compareBinaryVersions(gap.since, highest) > 0) highest = gap.since;
|
|
39172
|
+
}
|
|
39173
|
+
return highest;
|
|
39174
|
+
}
|
|
39175
|
+
var MAX_TESTED_HOST = "2.1.260";
|
|
39176
|
+
function hostCeilingNotice(hostVersion) {
|
|
39177
|
+
if (hostVersion === void 0) return null;
|
|
39178
|
+
if (compareBinaryVersions(hostVersion, MAX_TESTED_HOST) <= 0) return null;
|
|
39179
|
+
return `This Claude Code (${hostVersion}) is newer than AKA has been tested against (${MAX_TESTED_HOST}). If something here looks wrong, that is the likely cause \u2014 we'll look into it.`;
|
|
39180
|
+
}
|
|
39181
|
+
function hostCompatibilityLines(cache) {
|
|
39182
|
+
if (cache === null) return [];
|
|
39183
|
+
const lines = [`Claude Code: ${cache.version} (last seen)`];
|
|
39184
|
+
const gaps = hostFloorGaps(cache.version);
|
|
39185
|
+
const required2 = requiredHostVersion(gaps);
|
|
39186
|
+
if (required2 !== void 0) {
|
|
39187
|
+
lines.push(` inactive: ${gaps.map((g) => g.label).join(", ")}`);
|
|
39188
|
+
lines.push(` update Claude Code to ${required2} or newer to turn them on`);
|
|
39189
|
+
}
|
|
39190
|
+
const ceiling = hostCeilingNotice(cache.version);
|
|
39191
|
+
if (ceiling !== null) lines.push(` ${ceiling}`);
|
|
39192
|
+
return lines;
|
|
39193
|
+
}
|
|
39194
|
+
var HOST_VERSION_MARKER = "host-version.json";
|
|
39195
|
+
function readHostVersionCache(dataDir2) {
|
|
39196
|
+
try {
|
|
39197
|
+
const parsed2 = JSON.parse(readFileSync14(join20(dataDir2, HOST_VERSION_MARKER), "utf8"));
|
|
39198
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
39199
|
+
const { version: version2, observedAt } = parsed2;
|
|
39200
|
+
if (typeof version2 !== "string" || !isParseableBinaryVersion(version2)) return null;
|
|
39201
|
+
if (typeof observedAt !== "number" || !Number.isFinite(observedAt)) return null;
|
|
39202
|
+
return { version: version2, observedAt };
|
|
39203
|
+
} catch {
|
|
39204
|
+
return null;
|
|
39205
|
+
}
|
|
39206
|
+
}
|
|
39207
|
+
|
|
38700
39208
|
// ../packages/plugin-sdk/src/ignore-layers.ts
|
|
38701
39209
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
38702
|
-
import { readFileSync as
|
|
38703
|
-
import { join as
|
|
39210
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
39211
|
+
import { join as join21 } from "path";
|
|
38704
39212
|
function readIgnoreLayer(dir, filename, anchorLen) {
|
|
38705
39213
|
try {
|
|
38706
|
-
return { matcher: (0, import_ignore.default)().add(
|
|
39214
|
+
return { matcher: (0, import_ignore.default)().add(readFileSync15(join21(dir, filename), "utf8")), anchorLen };
|
|
38707
39215
|
} catch {
|
|
38708
39216
|
return void 0;
|
|
38709
39217
|
}
|
|
@@ -38733,22 +39241,9 @@ function withLayer(layers, layer) {
|
|
|
38733
39241
|
// ../packages/plugin-sdk/src/inventory-resolver.ts
|
|
38734
39242
|
import { arch, hostname as hostname4, platform, release as release2 } from "os";
|
|
38735
39243
|
|
|
38736
|
-
// ../packages/plugin-sdk/src/model-governance.ts
|
|
38737
|
-
import {
|
|
38738
|
-
closeSync as closeSync2,
|
|
38739
|
-
fstatSync,
|
|
38740
|
-
mkdirSync as mkdirSync2,
|
|
38741
|
-
openSync as openSync2,
|
|
38742
|
-
readFileSync as readFileSync13,
|
|
38743
|
-
readSync,
|
|
38744
|
-
writeFileSync as writeFileSync5
|
|
38745
|
-
} from "fs";
|
|
38746
|
-
import { join as join19 } from "path";
|
|
38747
|
-
var TAIL_BYTES = 256 * 1024;
|
|
38748
|
-
|
|
38749
39244
|
// ../packages/plugin-sdk/src/nudge.ts
|
|
38750
|
-
import { mkdirSync as
|
|
38751
|
-
import { join as
|
|
39245
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
|
|
39246
|
+
import { join as join22 } from "path";
|
|
38752
39247
|
|
|
38753
39248
|
// ../packages/plugin-sdk/src/paths.ts
|
|
38754
39249
|
import { readdirSync as readdirSync4, realpathSync as realpathSync4 } from "fs";
|
|
@@ -38785,8 +39280,8 @@ function resolveNonGitProject(startDir, recognizeMarker) {
|
|
|
38785
39280
|
}
|
|
38786
39281
|
|
|
38787
39282
|
// ../packages/plugin-sdk/src/project-files.ts
|
|
38788
|
-
import { existsSync as
|
|
38789
|
-
import { basename as basename5, join as
|
|
39283
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5 } from "fs";
|
|
39284
|
+
import { basename as basename5, join as join23 } from "path";
|
|
38790
39285
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
38791
39286
|
".git",
|
|
38792
39287
|
"node_modules",
|
|
@@ -38883,8 +39378,8 @@ function resolveProjectFiles(cwd, opts = {}) {
|
|
|
38883
39378
|
}
|
|
38884
39379
|
if (entry.isDirectory()) {
|
|
38885
39380
|
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
|
|
38886
|
-
const fullPath =
|
|
38887
|
-
if (
|
|
39381
|
+
const fullPath = join23(dir, entry.name);
|
|
39382
|
+
if (existsSync13(join23(fullPath, ".git"))) continue;
|
|
38888
39383
|
if (depth >= bounds.maxDepth) {
|
|
38889
39384
|
walk.omitted = true;
|
|
38890
39385
|
continue;
|
|
@@ -38956,8 +39451,8 @@ import { randomUUID as randomUUID14 } from "crypto";
|
|
|
38956
39451
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
38957
39452
|
|
|
38958
39453
|
// ../packages/plugin-sdk/src/throttle.ts
|
|
38959
|
-
import { mkdirSync as
|
|
38960
|
-
import { join as
|
|
39454
|
+
import { mkdirSync as mkdirSync5, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
39455
|
+
import { join as join24 } from "path";
|
|
38961
39456
|
|
|
38962
39457
|
// ../packages/local-ops/src/egress-record.ts
|
|
38963
39458
|
function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
@@ -38991,24 +39486,68 @@ function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
|
38991
39486
|
if (file2.startsWith(".") || file2.includes("/.")) continue;
|
|
38992
39487
|
files.push({ ...hit, file: file2, vendored: isVendoredPath(file2) });
|
|
38993
39488
|
}
|
|
38994
|
-
const
|
|
39489
|
+
const input2 = {
|
|
38995
39490
|
projectKey,
|
|
38996
39491
|
project,
|
|
38997
39492
|
projectId,
|
|
38998
39493
|
reconcile: { mode: "walk", walkedPrefix: toPosix(relative(root, abs)) },
|
|
38999
39494
|
hits: resolveEgress(files)
|
|
39000
|
-
}
|
|
39001
|
-
|
|
39495
|
+
};
|
|
39496
|
+
const summary = db.shares.recordProjectEgress(input2);
|
|
39497
|
+
return { ...summary, project: input2.project, input: input2 };
|
|
39002
39498
|
} catch {
|
|
39003
39499
|
return null;
|
|
39004
39500
|
}
|
|
39005
39501
|
}
|
|
39006
39502
|
|
|
39007
39503
|
// ../packages/local-ops/src/fs-scan.ts
|
|
39008
|
-
import { createHash as
|
|
39009
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
39010
|
-
import {
|
|
39504
|
+
import { createHash as createHash7, randomUUID as randomUUID15 } from "crypto";
|
|
39505
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync17, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
|
|
39506
|
+
import { homedir as homedir6 } from "os";
|
|
39507
|
+
import { basename as basename6, extname, join as join25, resolve as resolve3, sep as sep5 } from "path";
|
|
39011
39508
|
var AKAIGNORE_FILENAME = ".akaignore";
|
|
39509
|
+
function protectedPaths(home, akaHome) {
|
|
39510
|
+
const akaHomeDirname = basename6(defaultDataDir());
|
|
39511
|
+
return [
|
|
39512
|
+
...new Set(
|
|
39513
|
+
[
|
|
39514
|
+
resolve3(home, ".claude", "ide"),
|
|
39515
|
+
resolve3(home, ".claude", ".credentials.json"),
|
|
39516
|
+
resolve3(home, ".codex", "auth.json"),
|
|
39517
|
+
// The home this invocation was pointed at, the machine's own default,
|
|
39518
|
+
// and the default under whichever OS home this call was given — a
|
|
39519
|
+
// caller that redirects `home` gets that home's AKA directory covered
|
|
39520
|
+
// with it. All three are the same path for a caller on the real home.
|
|
39521
|
+
resolve3(akaHome),
|
|
39522
|
+
defaultDataDir(),
|
|
39523
|
+
resolve3(home, akaHomeDirname)
|
|
39524
|
+
].map(canonicalize)
|
|
39525
|
+
)
|
|
39526
|
+
];
|
|
39527
|
+
}
|
|
39528
|
+
function canonicalize(path) {
|
|
39529
|
+
try {
|
|
39530
|
+
return realpathSync5.native(path);
|
|
39531
|
+
} catch {
|
|
39532
|
+
return resolve3(path);
|
|
39533
|
+
}
|
|
39534
|
+
}
|
|
39535
|
+
function isProtected(canonical, roots) {
|
|
39536
|
+
return roots.some((root) => canonical === root || canonical.startsWith(root + sep5));
|
|
39537
|
+
}
|
|
39538
|
+
var PROTECTED_TARGET_ERROR_CODE = "AKA_PROTECTED_SCAN_TARGET";
|
|
39539
|
+
var ProtectedTargetError = class extends Error {
|
|
39540
|
+
code = PROTECTED_TARGET_ERROR_CODE;
|
|
39541
|
+
path;
|
|
39542
|
+
constructor(path) {
|
|
39543
|
+
super(`refusing to scan ${path}: it holds credentials this scanner must never read`);
|
|
39544
|
+
this.name = "ProtectedTargetError";
|
|
39545
|
+
this.path = path;
|
|
39546
|
+
}
|
|
39547
|
+
};
|
|
39548
|
+
function isProtectedTarget(err) {
|
|
39549
|
+
return typeof err === "object" && err !== null && err.code === PROTECTED_TARGET_ERROR_CODE;
|
|
39550
|
+
}
|
|
39012
39551
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
39013
39552
|
"node_modules",
|
|
39014
39553
|
".git",
|
|
@@ -39020,21 +39559,24 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
39020
39559
|
"out"
|
|
39021
39560
|
]);
|
|
39022
39561
|
var MAX_BYTES = 1e6;
|
|
39023
|
-
function* collectFiles(target) {
|
|
39562
|
+
function* collectFiles(target, opts = {}) {
|
|
39563
|
+
const protectedRoots = protectedPaths(opts.home ?? homedir6(), opts.akaHome ?? defaultDataDir());
|
|
39564
|
+
const canonicalTarget = canonicalize(target);
|
|
39024
39565
|
let st;
|
|
39025
39566
|
try {
|
|
39026
39567
|
st = statSync10(target);
|
|
39027
39568
|
} catch {
|
|
39028
39569
|
return;
|
|
39029
39570
|
}
|
|
39571
|
+
if (isProtected(canonicalTarget, protectedRoots)) throw new ProtectedTargetError(target);
|
|
39030
39572
|
if (st.isFile()) {
|
|
39031
39573
|
if (st.size <= MAX_BYTES) yield { path: target, gitignored: false };
|
|
39032
39574
|
return;
|
|
39033
39575
|
}
|
|
39034
39576
|
if (!st.isDirectory()) return;
|
|
39035
|
-
yield* visit2(target, "", [], [], false);
|
|
39577
|
+
yield* visit2(target, canonicalTarget, "", [], [], false, protectedRoots);
|
|
39036
39578
|
}
|
|
39037
|
-
function* visit2(dir, dirRel, markLayers, skipLayers, inIgnoredDir) {
|
|
39579
|
+
function* visit2(dir, dirCanonical, dirRel, markLayers, skipLayers, inIgnoredDir, protectedRoots) {
|
|
39038
39580
|
let dirents;
|
|
39039
39581
|
try {
|
|
39040
39582
|
dirents = readdirSync6(dir, { withFileTypes: true });
|
|
@@ -39051,15 +39593,26 @@ function* visit2(dir, dirRel, markLayers, skipLayers, inIgnoredDir) {
|
|
|
39051
39593
|
readIgnoreLayer(dir, AKAIGNORE_FILENAME, dirRel.length)
|
|
39052
39594
|
);
|
|
39053
39595
|
for (const entry of dirents) {
|
|
39054
|
-
const path =
|
|
39596
|
+
const path = join25(dir, entry.name);
|
|
39597
|
+
const canonical = dirCanonical === dir ? path : join25(dirCanonical, entry.name);
|
|
39055
39598
|
if (entry.isDirectory()) {
|
|
39599
|
+
if (isProtected(canonical, protectedRoots)) continue;
|
|
39056
39600
|
const skipState = evaluateIgnore(dirSkipLayers, dirRel, entry.name, true);
|
|
39057
39601
|
if (skipState !== "unignored" && (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".") || skipState === "ignored")) {
|
|
39058
39602
|
continue;
|
|
39059
39603
|
}
|
|
39060
39604
|
const dirIgnored = inIgnoredDir || evaluateIgnore(dirMarkLayers, dirRel, entry.name, true) === "ignored";
|
|
39061
|
-
yield* visit2(
|
|
39605
|
+
yield* visit2(
|
|
39606
|
+
path,
|
|
39607
|
+
canonical,
|
|
39608
|
+
childRel(dirRel, entry.name),
|
|
39609
|
+
dirMarkLayers,
|
|
39610
|
+
dirSkipLayers,
|
|
39611
|
+
dirIgnored,
|
|
39612
|
+
protectedRoots
|
|
39613
|
+
);
|
|
39062
39614
|
} else if (entry.isFile()) {
|
|
39615
|
+
if (isProtected(canonical, protectedRoots)) continue;
|
|
39063
39616
|
if (evaluateIgnore(dirSkipLayers, dirRel, entry.name, false) === "ignored") continue;
|
|
39064
39617
|
let size;
|
|
39065
39618
|
try {
|
|
@@ -39102,10 +39655,12 @@ async function scanPathIntoStore(db, target, opts = {}) {
|
|
|
39102
39655
|
}
|
|
39103
39656
|
return fingerprintKey;
|
|
39104
39657
|
}
|
|
39105
|
-
for (const { path: file2, gitignored } of collectFiles(resolve3(target)
|
|
39658
|
+
for (const { path: file2, gitignored } of collectFiles(resolve3(target), {
|
|
39659
|
+
akaHome: opts.akaHome
|
|
39660
|
+
})) {
|
|
39106
39661
|
let text;
|
|
39107
39662
|
try {
|
|
39108
|
-
text =
|
|
39663
|
+
text = readFileSync17(file2, "utf8");
|
|
39109
39664
|
} catch {
|
|
39110
39665
|
continue;
|
|
39111
39666
|
}
|
|
@@ -39128,7 +39683,7 @@ async function scanPathIntoStore(db, target, opts = {}) {
|
|
|
39128
39683
|
sourceTool: opts.sourceTool ?? SOURCE_TOOL.Cli,
|
|
39129
39684
|
kind: "code_change",
|
|
39130
39685
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39131
|
-
contentHash:
|
|
39686
|
+
contentHash: createHash7("sha256").update(text).digest("hex"),
|
|
39132
39687
|
// Only the enforced spans. `redact` folds overlapping findings into one
|
|
39133
39688
|
// disjoint region, so narrowing its input narrows those regions too —
|
|
39134
39689
|
// which is the intended reading rather than a hazard: an enforced span is
|
|
@@ -39202,19 +39757,53 @@ function recordProjectInventory(db, target) {
|
|
|
39202
39757
|
}
|
|
39203
39758
|
}
|
|
39204
39759
|
|
|
39760
|
+
// ../packages/local-ops/src/shares-forward.ts
|
|
39761
|
+
var FORWARD_FAILURE_LINES = {
|
|
39762
|
+
unauthorized: "key rejected; re-attach with a valid plugin key",
|
|
39763
|
+
forbidden: "key is valid but not permitted for Data Shares ingest; a key minted before Data Shares ingest existed needs a re-attach, otherwise ask your org admin",
|
|
39764
|
+
"route-absent": "the deployment predates Data Shares ingest; upgrade it, then re-run the scan",
|
|
39765
|
+
"invalid-request": "this build assembled a request the contract refuses; please report it",
|
|
39766
|
+
rejected: "the deployment refused the request body; this build and the deployment are out of step \u2014 upgrade one of them",
|
|
39767
|
+
unreachable: "control plane unreachable (timeout or server error); the next scan retries"
|
|
39768
|
+
};
|
|
39769
|
+
async function forwardProjectEgress(base, input2, deps) {
|
|
39770
|
+
let endpointName = null;
|
|
39771
|
+
try {
|
|
39772
|
+
const settings = readWorkspaceSettings(base);
|
|
39773
|
+
const connection = settings.controlPlane;
|
|
39774
|
+
if (!isAttached(settings) || connection === void 0) return { status: "not-attached" };
|
|
39775
|
+
const endpoint = controlPlaneName(connection);
|
|
39776
|
+
endpointName = endpoint;
|
|
39777
|
+
if (deps.enabled === false) return { status: "disabled", endpoint, reason: "opt-out" };
|
|
39778
|
+
if (!settings.dataSharesInPlace) {
|
|
39779
|
+
return { status: "disabled", endpoint, reason: "data-shares-off" };
|
|
39780
|
+
}
|
|
39781
|
+
const credential = readControlPlaneCredential(settingsDir(base), connection);
|
|
39782
|
+
if (credential === null) return { status: "no-credential", endpoint };
|
|
39783
|
+
const request = toEgressIngestRequest(input2);
|
|
39784
|
+
const result = await deps.send(
|
|
39785
|
+
{ endpoint: connection.endpoint, apiKey: credential.apiKey },
|
|
39786
|
+
request
|
|
39787
|
+
);
|
|
39788
|
+
return result.ok ? { status: "forwarded", endpoint, callSites: request.hits.length } : { status: "failed", endpoint, kind: result.kind };
|
|
39789
|
+
} catch {
|
|
39790
|
+
return endpointName === null ? { status: "not-attached" } : { status: "failed", endpoint: endpointName, kind: "unreachable" };
|
|
39791
|
+
}
|
|
39792
|
+
}
|
|
39793
|
+
|
|
39205
39794
|
// ../packages/local-ops/src/update-cache.ts
|
|
39206
39795
|
import { spawn } from "child_process";
|
|
39207
|
-
import { existsSync as
|
|
39208
|
-
import { join as
|
|
39796
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18, rmSync as rmSync8 } from "fs";
|
|
39797
|
+
import { join as join26 } from "path";
|
|
39209
39798
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
39210
39799
|
function cachePath(home) {
|
|
39211
|
-
return
|
|
39800
|
+
return join26(dataDir(home), "update-check.json");
|
|
39212
39801
|
}
|
|
39213
39802
|
function readCache(home) {
|
|
39214
39803
|
const path = cachePath(home);
|
|
39215
|
-
if (!
|
|
39804
|
+
if (!existsSync14(path)) return null;
|
|
39216
39805
|
try {
|
|
39217
|
-
const raw = JSON.parse(
|
|
39806
|
+
const raw = JSON.parse(readFileSync18(path, "utf8"));
|
|
39218
39807
|
if (!isRecord(raw) || typeof raw.checkedAt !== "number" || !isRecord(raw.report)) return null;
|
|
39219
39808
|
return raw;
|
|
39220
39809
|
} catch {
|
|
@@ -39222,7 +39811,7 @@ function readCache(home) {
|
|
|
39222
39811
|
}
|
|
39223
39812
|
}
|
|
39224
39813
|
function writeCache(home, cache) {
|
|
39225
|
-
if (!
|
|
39814
|
+
if (!existsSync14(dataDir(home))) return;
|
|
39226
39815
|
try {
|
|
39227
39816
|
writeOwnerOnlyFileSync(cachePath(home), `${JSON.stringify(cache, null, 2)}
|
|
39228
39817
|
`);
|
|
@@ -39234,7 +39823,7 @@ function isStale(cache, ttlMs = CHECK_TTL_MS) {
|
|
|
39234
39823
|
}
|
|
39235
39824
|
function clearCache(home) {
|
|
39236
39825
|
try {
|
|
39237
|
-
|
|
39826
|
+
rmSync8(cachePath(home), { force: true });
|
|
39238
39827
|
} catch {
|
|
39239
39828
|
}
|
|
39240
39829
|
}
|
|
@@ -39336,7 +39925,13 @@ var COMMAND_SPECS = [
|
|
|
39336
39925
|
name: "scan",
|
|
39337
39926
|
argHint: "[path]",
|
|
39338
39927
|
summary: "Scan a file or directory and record findings (default: .)",
|
|
39339
|
-
completesFiles: true
|
|
39928
|
+
completesFiles: true,
|
|
39929
|
+
flags: [
|
|
39930
|
+
{
|
|
39931
|
+
name: "--no-forward",
|
|
39932
|
+
summary: "Record locally only; skip forwarding the Data Shares register to an attached deployment."
|
|
39933
|
+
}
|
|
39934
|
+
]
|
|
39340
39935
|
},
|
|
39341
39936
|
{ name: "stats", summary: "Print findings / enforcement / detections from the local store" },
|
|
39342
39937
|
{
|
|
@@ -39410,12 +40005,15 @@ var GLOBAL_FLAGS = ["--home", "--no-update-check", "--version", "--help"];
|
|
|
39410
40005
|
function commandsHelp() {
|
|
39411
40006
|
const label = (s) => s.argHint ? `${s.name} ${s.argHint}` : s.name;
|
|
39412
40007
|
const width = Math.max(...COMMAND_SPECS.map((s) => label(s).length)) + 1;
|
|
39413
|
-
return COMMAND_SPECS.
|
|
40008
|
+
return COMMAND_SPECS.flatMap((s) => [
|
|
40009
|
+
` ${label(s).padEnd(width)}${s.summary}`,
|
|
40010
|
+
// Under its own command, indented past the summary column, because a
|
|
40011
|
+
// command-only flag listed in the shared Options block would read as one
|
|
40012
|
+
// every command takes.
|
|
40013
|
+
...(s.flags ?? []).map((f) => ` ${"".padEnd(width)}${f.name} ${f.summary}`)
|
|
40014
|
+
]).join("\n");
|
|
39414
40015
|
}
|
|
39415
40016
|
|
|
39416
|
-
// ../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
39417
|
-
import { createHash as createHash6 } from "crypto";
|
|
39418
|
-
|
|
39419
40017
|
// ../packages/remote/src/http.ts
|
|
39420
40018
|
import { request as httpRequest } from "http";
|
|
39421
40019
|
import { request as httpsRequest } from "https";
|
|
@@ -39695,6 +40293,7 @@ function createRemoteClient(options) {
|
|
|
39695
40293
|
url: url2(ROUTES.shares),
|
|
39696
40294
|
body: JSON.stringify(validated.data)
|
|
39697
40295
|
});
|
|
40296
|
+
if (response.status === 404) throw new RemoteRouteAbsent(ROUTES.shares);
|
|
39698
40297
|
okBody(response);
|
|
39699
40298
|
},
|
|
39700
40299
|
async pollCommand() {
|
|
@@ -39746,16 +40345,67 @@ function createAttachClient(options) {
|
|
|
39746
40345
|
};
|
|
39747
40346
|
}
|
|
39748
40347
|
|
|
40348
|
+
// ../packages/remote/src/failure-kind.ts
|
|
40349
|
+
function statusOf(err) {
|
|
40350
|
+
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
40351
|
+
const { status } = err;
|
|
40352
|
+
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
40353
|
+
return status >= 100 && status <= 599 ? status : null;
|
|
40354
|
+
}
|
|
40355
|
+
function nameOf(err) {
|
|
40356
|
+
if (typeof err !== "object" || err === null || !("name" in err)) return null;
|
|
40357
|
+
return typeof err.name === "string" ? err.name : null;
|
|
40358
|
+
}
|
|
40359
|
+
function classifyRemoteFailure(err) {
|
|
40360
|
+
switch (nameOf(err)) {
|
|
40361
|
+
case "RemoteRouteAbsent":
|
|
40362
|
+
return "route-absent";
|
|
40363
|
+
case "RemoteRequestInvalid":
|
|
40364
|
+
return "invalid-request";
|
|
40365
|
+
case "RemoteResponseInvalid":
|
|
40366
|
+
return "rejected";
|
|
40367
|
+
default:
|
|
40368
|
+
break;
|
|
40369
|
+
}
|
|
40370
|
+
const status = statusOf(err);
|
|
40371
|
+
if (status === null) return "unreachable";
|
|
40372
|
+
switch (status) {
|
|
40373
|
+
case 401:
|
|
40374
|
+
return "unauthorized";
|
|
40375
|
+
case 403:
|
|
40376
|
+
return "forbidden";
|
|
40377
|
+
case 429:
|
|
40378
|
+
return "unreachable";
|
|
40379
|
+
case 404:
|
|
40380
|
+
return "unreachable";
|
|
40381
|
+
default:
|
|
40382
|
+
return status >= 400 && status <= 499 ? "rejected" : "unreachable";
|
|
40383
|
+
}
|
|
40384
|
+
}
|
|
40385
|
+
|
|
40386
|
+
// ../packages/remote/src/shares-sender.ts
|
|
40387
|
+
var SHARES_FORWARD_TIMEOUT_MS = 15e3;
|
|
40388
|
+
function createSharesSender(timeoutMs = SHARES_FORWARD_TIMEOUT_MS) {
|
|
40389
|
+
return async (connection, request) => {
|
|
40390
|
+
try {
|
|
40391
|
+
await createRemoteClient({ ...connection, timeoutMs }).recordProjectEgress(request);
|
|
40392
|
+
return { ok: true };
|
|
40393
|
+
} catch (err) {
|
|
40394
|
+
return { ok: false, kind: classifyRemoteFailure(err) };
|
|
40395
|
+
}
|
|
40396
|
+
};
|
|
40397
|
+
}
|
|
40398
|
+
|
|
39749
40399
|
// ../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
39750
|
-
import { readFileSync as
|
|
39751
|
-
import { join as
|
|
40400
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
40401
|
+
import { join as join27 } from "path";
|
|
39752
40402
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
39753
40403
|
function forwardDropsPath(dataDir2) {
|
|
39754
|
-
return
|
|
40404
|
+
return join27(dataDir2, FORWARD_DROPS_FILENAME);
|
|
39755
40405
|
}
|
|
39756
40406
|
function readForwardDrops(dataDir2) {
|
|
39757
40407
|
try {
|
|
39758
|
-
const parsed2 = JSON.parse(
|
|
40408
|
+
const parsed2 = JSON.parse(readFileSync19(forwardDropsPath(dataDir2), "utf8"));
|
|
39759
40409
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
39760
40410
|
const record2 = parsed2;
|
|
39761
40411
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -39773,9 +40423,9 @@ function readForwardDrops(dataDir2) {
|
|
|
39773
40423
|
|
|
39774
40424
|
// ../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
39775
40425
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
39776
|
-
import { readFileSync as
|
|
40426
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
39777
40427
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
39778
|
-
import { join as
|
|
40428
|
+
import { join as join28 } from "path";
|
|
39779
40429
|
var BREAKER_COOLDOWN_MS = 3e4;
|
|
39780
40430
|
var FAILURES = /* @__PURE__ */ new Set([
|
|
39781
40431
|
"unauthorized",
|
|
@@ -39799,15 +40449,15 @@ function parseBreakerState(raw, nowMs) {
|
|
|
39799
40449
|
}
|
|
39800
40450
|
function readForwardHealth(dir, nowMs = Date.now()) {
|
|
39801
40451
|
try {
|
|
39802
|
-
return parseBreakerState(
|
|
40452
|
+
return parseBreakerState(readFileSync20(join28(dir, STATE_FILENAME), "utf8"), nowMs);
|
|
39803
40453
|
} catch {
|
|
39804
40454
|
return null;
|
|
39805
40455
|
}
|
|
39806
40456
|
}
|
|
39807
40457
|
|
|
39808
40458
|
// ../packages/plugin-runtime/src/attached/history-state.ts
|
|
39809
|
-
import { readFileSync as
|
|
39810
|
-
import { join as
|
|
40459
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
40460
|
+
import { join as join29 } from "path";
|
|
39811
40461
|
var HISTORY_SYNC_STATE_FILENAME = ATTACHED_HISTORY_SYNC_STATE_FILENAME;
|
|
39812
40462
|
var PHASES = /* @__PURE__ */ new Set(["filling", "complete"]);
|
|
39813
40463
|
var OUTCOMES = /* @__PURE__ */ new Set([
|
|
@@ -39818,7 +40468,7 @@ var OUTCOMES = /* @__PURE__ */ new Set([
|
|
|
39818
40468
|
]);
|
|
39819
40469
|
var SPEC_VERSION = 1;
|
|
39820
40470
|
function historySyncStatePath(dataDir2) {
|
|
39821
|
-
return
|
|
40471
|
+
return join29(dataDir2, HISTORY_SYNC_STATE_FILENAME);
|
|
39822
40472
|
}
|
|
39823
40473
|
function writeHistorySyncState(dataDir2, state) {
|
|
39824
40474
|
try {
|
|
@@ -39831,7 +40481,7 @@ function writeHistorySyncState(dataDir2, state) {
|
|
|
39831
40481
|
}
|
|
39832
40482
|
function readHistorySyncState(dataDir2) {
|
|
39833
40483
|
try {
|
|
39834
|
-
const parsed2 = JSON.parse(
|
|
40484
|
+
const parsed2 = JSON.parse(readFileSync21(historySyncStatePath(dataDir2), "utf8"));
|
|
39835
40485
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
39836
40486
|
const r = parsed2;
|
|
39837
40487
|
if (r.specVersion !== SPEC_VERSION) return null;
|
|
@@ -39863,7 +40513,7 @@ function isNullableCount(v) {
|
|
|
39863
40513
|
}
|
|
39864
40514
|
|
|
39865
40515
|
// ../packages/plugin-runtime/src/attached/history-sync.ts
|
|
39866
|
-
import { createHash as
|
|
40516
|
+
import { createHash as createHash8 } from "crypto";
|
|
39867
40517
|
import { hostname as hostname5 } from "os";
|
|
39868
40518
|
|
|
39869
40519
|
// ../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
@@ -40027,7 +40677,7 @@ var CAPTURE_BATCH_SIZE = INGEST_BATCH_MAX;
|
|
|
40027
40677
|
var STRUCTURAL_BUDGET_SHARE = 0.7;
|
|
40028
40678
|
var BATCH_SIZE = AUDIT_EVENT_BATCH_MAX;
|
|
40029
40679
|
function endpointFingerprint(endpoint) {
|
|
40030
|
-
return
|
|
40680
|
+
return createHash8("sha256").update(endpoint).digest("hex");
|
|
40031
40681
|
}
|
|
40032
40682
|
var didNotRun = (reason) => ({
|
|
40033
40683
|
attempted: false,
|
|
@@ -40044,9 +40694,12 @@ async function runHistorySync(deps) {
|
|
|
40044
40694
|
if (!isAttached(settings)) return didNotRun("not-attached");
|
|
40045
40695
|
const connection = settings.controlPlane;
|
|
40046
40696
|
if (connection === void 0) return didNotRun("not-attached");
|
|
40047
|
-
|
|
40697
|
+
const consent = settings.historySyncConsent;
|
|
40698
|
+
if (!isHistorySyncConsentValid(consent, connection.endpoint) || consent === void 0) {
|
|
40048
40699
|
return didNotRun("no-consent");
|
|
40049
40700
|
}
|
|
40701
|
+
const consentAcknowledgedAtMs = Date.parse(consent.acknowledgedAt);
|
|
40702
|
+
if (!Number.isFinite(consentAcknowledgedAtMs)) return didNotRun("attachment-unreadable");
|
|
40050
40703
|
const state = readControlPlaneCredentialFile(deps.settingsDir, connection);
|
|
40051
40704
|
if (!state.usable) return didNotRun("credential-unusable");
|
|
40052
40705
|
const nowMs = now();
|
|
@@ -40062,7 +40715,7 @@ async function runHistorySync(deps) {
|
|
|
40062
40715
|
const recorded = ledger.deployment();
|
|
40063
40716
|
let backlogBefore;
|
|
40064
40717
|
if (recorded.fingerprint !== fingerprint) {
|
|
40065
|
-
ledger.rearmFor(fingerprint, attachedAtMs);
|
|
40718
|
+
ledger.rearmFor(fingerprint, attachedAtMs, consentAcknowledgedAtMs);
|
|
40066
40719
|
backlogBefore = attachedAtMs;
|
|
40067
40720
|
} else if (recorded.backlogBefore === void 0) {
|
|
40068
40721
|
ledger.freezeBoundary(attachedAtMs);
|
|
@@ -40438,12 +41091,12 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
40438
41091
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
40439
41092
|
|
|
40440
41093
|
// ../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
40441
|
-
import { readFileSync as
|
|
41094
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
40442
41095
|
|
|
40443
41096
|
// ../packages/plugin-runtime/src/attached/policy-store.ts
|
|
40444
41097
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
40445
41098
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
40446
|
-
import { join as
|
|
41099
|
+
import { join as join30 } from "path";
|
|
40447
41100
|
|
|
40448
41101
|
// ../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
40449
41102
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -40467,7 +41120,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
40467
41120
|
|
|
40468
41121
|
// ../packages/plugin-runtime/src/attached/policy-store.ts
|
|
40469
41122
|
function createPolicyStore(dir = dataDir()) {
|
|
40470
|
-
const file2 =
|
|
41123
|
+
const file2 = join30(dir, "policy-cache.json");
|
|
40471
41124
|
async function read() {
|
|
40472
41125
|
try {
|
|
40473
41126
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -40546,11 +41199,11 @@ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
|
40546
41199
|
// ../packages/plugin-runtime/src/attached/posture-store.ts
|
|
40547
41200
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
40548
41201
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
40549
|
-
import { join as
|
|
41202
|
+
import { join as join31 } from "path";
|
|
40550
41203
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
40551
41204
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
40552
|
-
const file2 =
|
|
40553
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
41205
|
+
const file2 = join31(dir, "posture-state.json");
|
|
41206
|
+
const legacyFile = legacyDir === void 0 ? null : join31(legacyDir, "posture-state.json");
|
|
40554
41207
|
async function persist(state) {
|
|
40555
41208
|
await ensureDataDir(dir);
|
|
40556
41209
|
const tmp = `${file2}.${randomUUID18()}.tmp`;
|
|
@@ -40622,8 +41275,8 @@ async function readDeviceIdentity(dir) {
|
|
|
40622
41275
|
}
|
|
40623
41276
|
|
|
40624
41277
|
// ../packages/plugin-runtime/src/attached/sync-state.ts
|
|
40625
|
-
import { readFileSync as
|
|
40626
|
-
import { join as
|
|
41278
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
41279
|
+
import { join as join32 } from "path";
|
|
40627
41280
|
var SYNC_STATE_FILENAME = ATTACHED_SYNC_STATE_FILENAME;
|
|
40628
41281
|
var OUTCOMES2 = /* @__PURE__ */ new Set([
|
|
40629
41282
|
"ok",
|
|
@@ -40634,11 +41287,11 @@ var OUTCOMES2 = /* @__PURE__ */ new Set([
|
|
|
40634
41287
|
"invalid-bundle"
|
|
40635
41288
|
]);
|
|
40636
41289
|
function syncStatePath(dataDir2) {
|
|
40637
|
-
return
|
|
41290
|
+
return join32(dataDir2, SYNC_STATE_FILENAME);
|
|
40638
41291
|
}
|
|
40639
41292
|
function readSyncState(dataDir2) {
|
|
40640
41293
|
try {
|
|
40641
|
-
const parsed2 = JSON.parse(
|
|
41294
|
+
const parsed2 = JSON.parse(readFileSync23(syncStatePath(dataDir2), "utf8"));
|
|
40642
41295
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
40643
41296
|
const record2 = parsed2;
|
|
40644
41297
|
if (typeof record2.outcome !== "string" || !OUTCOMES2.has(record2.outcome)) return null;
|
|
@@ -41001,7 +41654,7 @@ async function attachByDeviceCode(deps) {
|
|
|
41001
41654
|
...deps.label === void 0 ? {} : { label: deps.label }
|
|
41002
41655
|
});
|
|
41003
41656
|
} catch (err) {
|
|
41004
|
-
if (
|
|
41657
|
+
if (statusOf(err) === 404) return { kind: "not-offered" };
|
|
41005
41658
|
return { kind: "not-offered", reason: describe3(err) };
|
|
41006
41659
|
}
|
|
41007
41660
|
deps.io.out(
|
|
@@ -41037,7 +41690,7 @@ async function attachByDeviceCode(deps) {
|
|
|
41037
41690
|
try {
|
|
41038
41691
|
answer = await deps.client.poll(grant2.deviceCode);
|
|
41039
41692
|
} catch (err) {
|
|
41040
|
-
if (
|
|
41693
|
+
if (statusOf(err) === 429) {
|
|
41041
41694
|
waitSeconds = Math.min(waitSeconds * 2, 60);
|
|
41042
41695
|
continue;
|
|
41043
41696
|
}
|
|
@@ -41105,13 +41758,9 @@ async function confirmAndReturn(deps, apiKey) {
|
|
|
41105
41758
|
}
|
|
41106
41759
|
return { kind: "attached", apiKey, identity };
|
|
41107
41760
|
}
|
|
41108
|
-
function statusOf3(err) {
|
|
41109
|
-
const status = err?.status;
|
|
41110
|
-
return typeof status === "number" ? status : void 0;
|
|
41111
|
-
}
|
|
41112
41761
|
function describe3(err) {
|
|
41113
|
-
const status =
|
|
41114
|
-
if (status !==
|
|
41762
|
+
const status = statusOf(err);
|
|
41763
|
+
if (status !== null) return `the deployment answered ${String(status)}.`;
|
|
41115
41764
|
return "the deployment could not be reached.";
|
|
41116
41765
|
}
|
|
41117
41766
|
function stringField(body, key) {
|
|
@@ -41273,6 +41922,7 @@ ${USAGE}`);
|
|
|
41273
41922
|
}
|
|
41274
41923
|
const historyConsent = await askAboutHistory(io, args.syncHistory, base, endpoint, identity);
|
|
41275
41924
|
const previous = readControlPlaneCredentialFile(settingsDir(base));
|
|
41925
|
+
const attachedAt = /* @__PURE__ */ new Date();
|
|
41276
41926
|
try {
|
|
41277
41927
|
writeControlPlaneCredential(settingsDir(base), {
|
|
41278
41928
|
specVersion: 1,
|
|
@@ -41285,7 +41935,7 @@ ${USAGE}`);
|
|
|
41285
41935
|
runMode: "attached",
|
|
41286
41936
|
controlPlane: {
|
|
41287
41937
|
endpoint,
|
|
41288
|
-
attachedAt:
|
|
41938
|
+
attachedAt: attachedAt.toISOString(),
|
|
41289
41939
|
...args.label === void 0 ? {} : { label: args.label }
|
|
41290
41940
|
},
|
|
41291
41941
|
// SPELLED, never omitted. `undefined` on an optional key is how this
|
|
@@ -41310,6 +41960,10 @@ ${USAGE}`);
|
|
|
41310
41960
|
exit(1);
|
|
41311
41961
|
return;
|
|
41312
41962
|
}
|
|
41963
|
+
if (historyConsent !== void 0) {
|
|
41964
|
+
seedCaptureBacklogOwed(dataDir(base), attachedAt.getTime());
|
|
41965
|
+
}
|
|
41966
|
+
(deps.installBackgroundSync ?? installBackgroundSync)(base);
|
|
41313
41967
|
io.out(
|
|
41314
41968
|
[
|
|
41315
41969
|
`Attached to ${args.label ?? endpoint}.`,
|
|
@@ -41349,6 +42003,10 @@ async function askAboutHistory(io, flag, base, endpoint, identity) {
|
|
|
41349
42003
|
`Verified against ${identity.tenantName}.`,
|
|
41350
42004
|
"",
|
|
41351
42005
|
"Activity from here on is sent to that deployment automatically.",
|
|
42006
|
+
// The register a scan records crosses under this same attachment, and
|
|
42007
|
+
// `aka scan` is a verb people reasonably think of as local — so consent
|
|
42008
|
+
// is where it gets named, and named with what it does and does not carry.
|
|
42009
|
+
"So is the Data Shares register a scan records \u2014 destinations and call sites, never source text.",
|
|
41352
42010
|
...backlog === void 0 ? [] : [backlog],
|
|
41353
42011
|
"AKA can also keep anything a live send fails to deliver, instead of",
|
|
41354
42012
|
"dropping it.",
|
|
@@ -41356,9 +42014,10 @@ async function askAboutHistory(io, flag, base, endpoint, identity) {
|
|
|
41356
42014
|
...backlog === void 0 ? [] : [
|
|
41357
42015
|
"What that history sends: which sessions ran, when, in which project,",
|
|
41358
42016
|
" repo and git branch; token usage and model per call;",
|
|
41359
|
-
" which tools were called, with their inputs truncated",
|
|
41360
|
-
"
|
|
41361
|
-
"
|
|
42017
|
+
" which tools were called, with their inputs truncated;",
|
|
42018
|
+
" what AKA detected in those tool inputs; and the",
|
|
42019
|
+
" prompts, assistant replies and tool results",
|
|
42020
|
+
" themselves.",
|
|
41362
42021
|
""
|
|
41363
42022
|
],
|
|
41364
42023
|
// The masking half is CONDITIONAL and has to read that way. A drained
|
|
@@ -41366,16 +42025,25 @@ async function askAboutHistory(io, flag, base, endpoint, identity) {
|
|
|
41366
42025
|
// where the policy assigned its detection is redact or block — so copy
|
|
41367
42026
|
// promising that every detected secret is masked first would be false
|
|
41368
42027
|
// under monitor and warn, which is every detection until the user
|
|
41369
|
-
// promotes one. Saying so is the point of asking
|
|
41370
|
-
|
|
41371
|
-
|
|
41372
|
-
|
|
41373
|
-
|
|
41374
|
-
|
|
41375
|
-
|
|
41376
|
-
|
|
41377
|
-
|
|
41378
|
-
|
|
42028
|
+
// promotes one. Saying so is the point of asking, and it now covers BOTH
|
|
42029
|
+
// buckets alike: once granted, that history is sent through the same
|
|
42030
|
+
// outbox mark and the same drain as an undelivered live send, so the
|
|
42031
|
+
// same masking rule applies to both.
|
|
42032
|
+
...backlog === void 0 ? [
|
|
42033
|
+
"For anything a live send could not deliver \u2014 the deployment was",
|
|
42034
|
+
"unreachable, or refused the key \u2014 what was captured, which for a"
|
|
42035
|
+
] : [
|
|
42036
|
+
"For that history, and for anything a later live send could not",
|
|
42037
|
+
"deliver \u2014 the deployment was unreachable, or refused the key \u2014 what",
|
|
42038
|
+
"was captured, which for a"
|
|
42039
|
+
],
|
|
42040
|
+
"prompt, an assistant reply or a tool result INCLUDES ITS TEXT. What is",
|
|
42041
|
+
"masked in that text follows the policy assigned to the detection that",
|
|
42042
|
+
"flagged the value: it is masked before it is stored or sent",
|
|
42043
|
+
"only where that policy is redact or block. Under monitor or warn the",
|
|
42044
|
+
"value goes as it was seen, and every detection ships on monitor, so on",
|
|
42045
|
+
"a default install nothing in that text is masked. Everything outside a",
|
|
42046
|
+
"flagged span goes as written either way.",
|
|
41379
42047
|
"",
|
|
41380
42048
|
"Saying no does not stop live sending \u2014 that is part of being attached.",
|
|
41381
42049
|
"It means an undelivered item is dropped rather than kept and retried.",
|
|
@@ -41418,6 +42086,7 @@ function runDetach(argv, deps = {}) {
|
|
|
41418
42086
|
}
|
|
41419
42087
|
removeControlPlaneCredential(settingsDir(base));
|
|
41420
42088
|
clearDerived(dataDir(base));
|
|
42089
|
+
(deps.uninstallBackgroundSync ?? uninstallBackgroundSync)(base);
|
|
41421
42090
|
io.out(
|
|
41422
42091
|
had ? "Detached. This machine records locally only; that deployment is sent nothing.\n" : "This machine was not attached; nothing to do.\n"
|
|
41423
42092
|
);
|
|
@@ -41452,6 +42121,9 @@ async function runStatus(argv, deps = {}) {
|
|
|
41452
42121
|
io.out(attached ? `${block}
|
|
41453
42122
|
${await renderPolicyLine(dataDir2)}
|
|
41454
42123
|
` : `${block}
|
|
42124
|
+
`);
|
|
42125
|
+
const hostLines = hostCompatibilityLines(readHostVersionCache(dataDir2));
|
|
42126
|
+
if (hostLines.length > 0) io.out(`${hostLines.join("\n")}
|
|
41455
42127
|
`);
|
|
41456
42128
|
}
|
|
41457
42129
|
function managedRefusal(base, endpoint, label, managedOverride) {
|
|
@@ -41503,7 +42175,7 @@ async function runDeviceAttach(input2) {
|
|
|
41503
42175
|
}
|
|
41504
42176
|
|
|
41505
42177
|
// src/commands/check-updates.ts
|
|
41506
|
-
import { existsSync as
|
|
42178
|
+
import { existsSync as existsSync15 } from "fs";
|
|
41507
42179
|
import { parseArgs } from "util";
|
|
41508
42180
|
function runCheckUpdates(argv) {
|
|
41509
42181
|
const { values } = parseArgs({ args: argv, options: HOME_OPTION });
|
|
@@ -41526,7 +42198,7 @@ function runCheckUpdates(argv) {
|
|
|
41526
42198
|
out.write(`${String(ups.length)} update(s) available \u2014 run \`aka update\` to apply.
|
|
41527
42199
|
`);
|
|
41528
42200
|
}
|
|
41529
|
-
if (
|
|
42201
|
+
if (existsSync15(dataDir(home))) {
|
|
41530
42202
|
const notifiedPluginIds = readCache(home)?.notifiedPluginIds ?? [];
|
|
41531
42203
|
writeCache(home, { checkedAt: Date.now(), report, notifiedPluginIds });
|
|
41532
42204
|
}
|
|
@@ -41534,8 +42206,11 @@ function runCheckUpdates(argv) {
|
|
|
41534
42206
|
|
|
41535
42207
|
// src/commands/completion.ts
|
|
41536
42208
|
var topNames = COMMAND_SPECS.map((s) => s.name);
|
|
42209
|
+
var flagSpecs = COMMAND_SPECS.filter((s) => (s.flags ?? []).length > 0);
|
|
41537
42210
|
function zshScript() {
|
|
41538
|
-
const
|
|
42211
|
+
const zq = (text) => text.replace(/'/g, "'\\''");
|
|
42212
|
+
const describe5 = COMMAND_SPECS.map((s) => ` '${zq(s.name)}:${zq(s.summary)}'`).join("\n");
|
|
42213
|
+
const flagArms = flagSpecs.map((s) => ` ${s.name}) _aka_flags+=(${(s.flags ?? []).map((f) => f.name).join(" ")}) ;;`).join("\n");
|
|
41539
42214
|
const argArms = COMMAND_SPECS.flatMap(
|
|
41540
42215
|
(s) => s.args ? [` ${s.name}) _values '${s.name}' ${s.args.join(" ")} ;;`] : []
|
|
41541
42216
|
);
|
|
@@ -41562,9 +42237,15 @@ ${describe5}
|
|
|
41562
42237
|
|
|
41563
42238
|
# Flags may follow any command; complete them whenever the current word starts
|
|
41564
42239
|
# with '-'. Checked BEFORE the positional switch so a command with no arg case
|
|
41565
|
-
# (e.g. \`aka stats --\`) still offers them \u2014 matching the bash script.
|
|
42240
|
+
# (e.g. \`aka stats --\`) still offers them \u2014 matching the bash script. A
|
|
42241
|
+
# command that owns flags of its own adds them here and nowhere else.
|
|
41566
42242
|
if [[ $words[CURRENT] == -* ]]; then
|
|
41567
|
-
|
|
42243
|
+
local -a _aka_flags
|
|
42244
|
+
_aka_flags=(${GLOBAL_FLAGS.join(" ")})
|
|
42245
|
+
case $words[2] in
|
|
42246
|
+
${flagArms}
|
|
42247
|
+
esac
|
|
42248
|
+
compadd -- $_aka_flags
|
|
41568
42249
|
return
|
|
41569
42250
|
fi
|
|
41570
42251
|
|
|
@@ -41590,6 +42271,9 @@ compdef _aka aka
|
|
|
41590
42271
|
`;
|
|
41591
42272
|
}
|
|
41592
42273
|
function bashScript() {
|
|
42274
|
+
const flagArms = flagSpecs.map(
|
|
42275
|
+
(s) => ` ${s.name}) flags="$flags ${(s.flags ?? []).map((f) => f.name).join(" ")}" ;;`
|
|
42276
|
+
).join("\n");
|
|
41593
42277
|
const argArms = COMMAND_SPECS.flatMap(
|
|
41594
42278
|
(s) => s.args ? [` ${s.name}) COMPREPLY=( $(compgen -W "${s.args.join(" ")}" -- "$cur") ); return ;;`] : []
|
|
41595
42279
|
).join("\n");
|
|
@@ -41608,9 +42292,14 @@ _aka() {
|
|
|
41608
42292
|
fi
|
|
41609
42293
|
|
|
41610
42294
|
# Flags may follow any command \u2014 checked before the positional switch so every
|
|
41611
|
-
# command offers them (matches the zsh script).
|
|
42295
|
+
# command offers them (matches the zsh script). A command that owns flags of
|
|
42296
|
+
# its own adds them here and nowhere else.
|
|
41612
42297
|
if [[ "$cur" == -* ]]; then
|
|
41613
|
-
|
|
42298
|
+
local flags="${GLOBAL_FLAGS.join(" ")}"
|
|
42299
|
+
case "$cmd" in
|
|
42300
|
+
${flagArms}
|
|
42301
|
+
esac
|
|
42302
|
+
COMPREPLY=( $(compgen -W "$flags" -- "$cur") )
|
|
41614
42303
|
return
|
|
41615
42304
|
fi
|
|
41616
42305
|
|
|
@@ -41680,17 +42369,17 @@ function runCompletion(argv) {
|
|
|
41680
42369
|
|
|
41681
42370
|
// src/commands/dashboard.ts
|
|
41682
42371
|
import { spawn as spawn5 } from "child_process";
|
|
41683
|
-
import { existsSync as
|
|
42372
|
+
import { existsSync as existsSync16 } from "fs";
|
|
41684
42373
|
import { createRequire as createRequire2 } from "module";
|
|
41685
42374
|
import { createServer } from "net";
|
|
41686
|
-
import { dirname as dirname9, join as
|
|
42375
|
+
import { dirname as dirname9, join as join33 } from "path";
|
|
41687
42376
|
import { fileURLToPath as fileURLToPath5, pathToFileURL } from "url";
|
|
41688
42377
|
import { parseArgs as parseArgs2 } from "util";
|
|
41689
42378
|
var require3 = createRequire2(import.meta.url);
|
|
41690
42379
|
var here = dirname9(fileURLToPath5(import.meta.url));
|
|
41691
42380
|
var BIND_HOST = "127.0.0.1";
|
|
41692
42381
|
function pkgBase() {
|
|
41693
|
-
return isSea() ? dirname9(process.execPath) :
|
|
42382
|
+
return isSea() ? dirname9(process.execPath) : join33(here, "..");
|
|
41694
42383
|
}
|
|
41695
42384
|
function isPortFree(port) {
|
|
41696
42385
|
return new Promise((resolve7) => {
|
|
@@ -41744,9 +42433,9 @@ async function runDashboard(argv) {
|
|
|
41744
42433
|
refreshUpdateMirror(dataDir());
|
|
41745
42434
|
const base = pkgBase();
|
|
41746
42435
|
const bundledServer = [
|
|
41747
|
-
|
|
41748
|
-
|
|
41749
|
-
].find((p) =>
|
|
42436
|
+
join33(base, "web-ui", "server.js"),
|
|
42437
|
+
join33(base, "web-ui", "web-ui", "server.js")
|
|
42438
|
+
].find((p) => existsSync16(p));
|
|
41750
42439
|
if (bundledServer) {
|
|
41751
42440
|
launchStandalone(bundledServer, port, shouldOpen, url2);
|
|
41752
42441
|
return;
|
|
@@ -41765,7 +42454,7 @@ async function runDashboard(argv) {
|
|
|
41765
42454
|
onReadyOpen(child, shouldOpen, url2, `Starting the AKA dashboard at ${url2}
|
|
41766
42455
|
`);
|
|
41767
42456
|
};
|
|
41768
|
-
if (
|
|
42457
|
+
if (existsSync16(join33(webUiDir, ".next", "BUILD_ID"))) {
|
|
41769
42458
|
startNext();
|
|
41770
42459
|
return;
|
|
41771
42460
|
}
|
|
@@ -42810,15 +43499,15 @@ async function runException(argv, io = terminalPrompter()) {
|
|
|
42810
43499
|
|
|
42811
43500
|
// src/commands/extension.ts
|
|
42812
43501
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
42813
|
-
import { existsSync as
|
|
43502
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync6, readFileSync as readFileSync24, renameSync as renameSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
42814
43503
|
import { createRequire as createRequire3 } from "module";
|
|
42815
|
-
import { homedir as
|
|
42816
|
-
import { dirname as dirname10, join as
|
|
43504
|
+
import { homedir as homedir7 } from "os";
|
|
43505
|
+
import { dirname as dirname10, join as join34 } from "path";
|
|
42817
43506
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
42818
43507
|
var require4 = createRequire3(import.meta.url);
|
|
42819
43508
|
var here2 = dirname10(fileURLToPath6(import.meta.url));
|
|
42820
43509
|
function cliRoot() {
|
|
42821
|
-
return isSea() ? dirname10(process.execPath) :
|
|
43510
|
+
return isSea() ? dirname10(process.execPath) : join34(here2, "..");
|
|
42822
43511
|
}
|
|
42823
43512
|
var EXTENSION_IDS = [
|
|
42824
43513
|
// Derived from the "key" in plugins/browser-extension/manifest.json.
|
|
@@ -42835,31 +43524,31 @@ function nativeHostManifest(hostPath) {
|
|
|
42835
43524
|
};
|
|
42836
43525
|
}
|
|
42837
43526
|
function resolveHostScript(root = cliRoot()) {
|
|
42838
|
-
const bundled =
|
|
42839
|
-
if (
|
|
43527
|
+
const bundled = join34(root, "native-host", "host.js");
|
|
43528
|
+
if (existsSync17(bundled)) return bundled;
|
|
42840
43529
|
try {
|
|
42841
43530
|
const pkgJson = require4.resolve("@akasecurity/plugin-browser-extension/package.json");
|
|
42842
|
-
const devBuilt =
|
|
42843
|
-
if (
|
|
43531
|
+
const devBuilt = join34(dirname10(pkgJson), "native-host", "host.js");
|
|
43532
|
+
if (existsSync17(devBuilt)) return devBuilt;
|
|
42844
43533
|
} catch {
|
|
42845
43534
|
}
|
|
42846
43535
|
return null;
|
|
42847
43536
|
}
|
|
42848
43537
|
function resolveExtensionDist(root = cliRoot()) {
|
|
42849
|
-
const bundled =
|
|
42850
|
-
if (
|
|
43538
|
+
const bundled = join34(root, "extension");
|
|
43539
|
+
if (existsSync17(bundled)) return bundled;
|
|
42851
43540
|
try {
|
|
42852
43541
|
const pkgJson = require4.resolve("@akasecurity/plugin-browser-extension/package.json");
|
|
42853
|
-
const devDist =
|
|
42854
|
-
if (
|
|
43542
|
+
const devDist = join34(dirname10(pkgJson), "dist");
|
|
43543
|
+
if (existsSync17(devDist)) return devDist;
|
|
42855
43544
|
} catch {
|
|
42856
43545
|
}
|
|
42857
43546
|
return null;
|
|
42858
43547
|
}
|
|
42859
43548
|
function chromeManifestDir(platform3 = process.platform) {
|
|
42860
43549
|
if (platform3 === "darwin") {
|
|
42861
|
-
return
|
|
42862
|
-
|
|
43550
|
+
return join34(
|
|
43551
|
+
homedir7(),
|
|
42863
43552
|
"Library",
|
|
42864
43553
|
"Application Support",
|
|
42865
43554
|
"Google",
|
|
@@ -42868,9 +43557,9 @@ function chromeManifestDir(platform3 = process.platform) {
|
|
|
42868
43557
|
);
|
|
42869
43558
|
}
|
|
42870
43559
|
if (platform3 === "win32") {
|
|
42871
|
-
return
|
|
43560
|
+
return join34(homedir7(), ".aka", "native-messaging");
|
|
42872
43561
|
}
|
|
42873
|
-
return
|
|
43562
|
+
return join34(homedir7(), ".config", "google-chrome", "NativeMessagingHosts");
|
|
42874
43563
|
}
|
|
42875
43564
|
function resolveNodeForLauncher() {
|
|
42876
43565
|
if (!isSea()) return process.execPath;
|
|
@@ -42884,7 +43573,7 @@ function resolveNodeForLauncher() {
|
|
|
42884
43573
|
}
|
|
42885
43574
|
}
|
|
42886
43575
|
function launcherPath(manifestDir, platform3) {
|
|
42887
|
-
return
|
|
43576
|
+
return join34(
|
|
42888
43577
|
manifestDir,
|
|
42889
43578
|
platform3 === "win32" ? `${NATIVE_HOST_NAME}.cmd` : `${NATIVE_HOST_NAME}.sh`
|
|
42890
43579
|
);
|
|
@@ -42899,11 +43588,11 @@ function writeLauncher(manifestDir, hostScript, nodePath, platform3) {
|
|
|
42899
43588
|
const path = launcherPath(manifestDir, platform3);
|
|
42900
43589
|
const tmp = `${path}.tmp`;
|
|
42901
43590
|
if (platform3 === "win32") {
|
|
42902
|
-
|
|
43591
|
+
writeFileSync9(tmp, `@echo off\r
|
|
42903
43592
|
${cmdQuote(nodePath)} ${cmdQuote(hostScript)} %*\r
|
|
42904
43593
|
`);
|
|
42905
43594
|
} else {
|
|
42906
|
-
|
|
43595
|
+
writeFileSync9(tmp, `#!/bin/sh
|
|
42907
43596
|
exec ${posixQuote(nodePath)} ${posixQuote(hostScript)} "$@"
|
|
42908
43597
|
`, {
|
|
42909
43598
|
mode: 493
|
|
@@ -42959,11 +43648,11 @@ function runInstall(manifestDir, hostScript = resolveHostScript(), extensionDist
|
|
|
42959
43648
|
process.exitCode = 1;
|
|
42960
43649
|
return;
|
|
42961
43650
|
}
|
|
42962
|
-
|
|
43651
|
+
mkdirSync6(manifestDir, { recursive: true });
|
|
42963
43652
|
const launcher = writeLauncher(manifestDir, hostScript, nodePath, process.platform);
|
|
42964
|
-
const manifestPath =
|
|
43653
|
+
const manifestPath = join34(manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
42965
43654
|
const tmp = `${manifestPath}.tmp`;
|
|
42966
|
-
|
|
43655
|
+
writeFileSync9(tmp, `${JSON.stringify(nativeHostManifest(launcher), null, 2)}
|
|
42967
43656
|
`, {
|
|
42968
43657
|
mode: DATA_FILE_MODE
|
|
42969
43658
|
});
|
|
@@ -42998,7 +43687,7 @@ Next: load the extension in Chrome \u2014
|
|
|
42998
43687
|
function manifestFault(manifestPath) {
|
|
42999
43688
|
let parsed2;
|
|
43000
43689
|
try {
|
|
43001
|
-
parsed2 = JSON.parse(
|
|
43690
|
+
parsed2 = JSON.parse(readFileSync24(manifestPath, "utf8"));
|
|
43002
43691
|
} catch (err) {
|
|
43003
43692
|
return `could not be read (${err instanceof Error ? err.message : String(err)})`;
|
|
43004
43693
|
}
|
|
@@ -43009,8 +43698,8 @@ function manifestFault(manifestPath) {
|
|
|
43009
43698
|
return `does not grant ${missing.length === 1 ? "the extension id" : "the extension ids"} ${missing.join(", ")}`;
|
|
43010
43699
|
}
|
|
43011
43700
|
function runStatus2(manifestDir) {
|
|
43012
|
-
const manifestPath =
|
|
43013
|
-
if (!
|
|
43701
|
+
const manifestPath = join34(manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
43702
|
+
if (!existsSync17(manifestPath)) {
|
|
43014
43703
|
process.stdout.write(
|
|
43015
43704
|
`native-messaging host: not installed
|
|
43016
43705
|
manifest: ${manifestPath}
|
|
@@ -43032,21 +43721,18 @@ function runStatus2(manifestDir) {
|
|
|
43032
43721
|
}
|
|
43033
43722
|
|
|
43034
43723
|
// src/commands/init.ts
|
|
43035
|
-
import { existsSync as
|
|
43036
|
-
import { join as
|
|
43724
|
+
import { existsSync as existsSync19, lstatSync as lstatSync4, readdirSync as readdirSync7, statSync as statSync13 } from "fs";
|
|
43725
|
+
import { join as join35 } from "path";
|
|
43037
43726
|
import * as readline from "readline/promises";
|
|
43038
43727
|
import { parseArgs as parseArgs6 } from "util";
|
|
43039
43728
|
|
|
43040
43729
|
// src/commands/plugins.ts
|
|
43041
|
-
import { existsSync as
|
|
43730
|
+
import { existsSync as existsSync18 } from "fs";
|
|
43042
43731
|
import { parseArgs as parseArgs5 } from "util";
|
|
43043
|
-
function runPlugins(argv) {
|
|
43732
|
+
function runPlugins(argv, deps = {}) {
|
|
43044
43733
|
const [sub, ...rest] = argv;
|
|
43045
43734
|
if (!sub || sub === "list") return listPlugins(rest);
|
|
43046
|
-
if (sub === "install")
|
|
43047
|
-
installPlugin(rest);
|
|
43048
|
-
return;
|
|
43049
|
-
}
|
|
43735
|
+
if (sub === "install") return installPlugin(rest, deps);
|
|
43050
43736
|
process.stderr.write(`aka plugins: unknown subcommand '${sub}' (try: list, install <agent>)
|
|
43051
43737
|
`);
|
|
43052
43738
|
process.exitCode = 1;
|
|
@@ -43055,7 +43741,7 @@ async function listPlugins(argv) {
|
|
|
43055
43741
|
const { values } = parseArgs5({ args: argv, options: HOME_OPTION });
|
|
43056
43742
|
const home = homeBase(values.home);
|
|
43057
43743
|
const active = /* @__PURE__ */ new Set();
|
|
43058
|
-
if (
|
|
43744
|
+
if (existsSync18(dbPath(home))) {
|
|
43059
43745
|
const db = openLocalDatabase(dataDir(home));
|
|
43060
43746
|
try {
|
|
43061
43747
|
const recent = await db.findings.recentFindings({ limit: 1e3 });
|
|
@@ -43079,7 +43765,7 @@ async function listPlugins(argv) {
|
|
|
43079
43765
|
out.write("\nInstall: aka plugins install <agent>\n");
|
|
43080
43766
|
out.write("Update: aka update (or: aka check-updates)\n");
|
|
43081
43767
|
}
|
|
43082
|
-
function installPlugin(argv) {
|
|
43768
|
+
async function installPlugin(argv, deps) {
|
|
43083
43769
|
const { positionals } = parseArgs5({ args: argv, options: HOME_OPTION, allowPositionals: true });
|
|
43084
43770
|
const id = positionals[0];
|
|
43085
43771
|
if (!id) {
|
|
@@ -43119,6 +43805,27 @@ Then run \`aka init\` to set up the local store.
|
|
|
43119
43805
|
);
|
|
43120
43806
|
return;
|
|
43121
43807
|
}
|
|
43808
|
+
if (cliBin === "claude") {
|
|
43809
|
+
const version2 = (deps.hostVersion ?? hostCliVersion)(cliBin);
|
|
43810
|
+
const gaps = hostFloorGaps(version2);
|
|
43811
|
+
const required2 = requiredHostVersion(gaps);
|
|
43812
|
+
if (version2 !== void 0 && required2 !== void 0) {
|
|
43813
|
+
const io = deps.prompter ?? terminalPrompter();
|
|
43814
|
+
io.out(
|
|
43815
|
+
`This ${cliBin} is ${version2}, which is older than AKA needs for ${gaps.map((g) => g.label).join(", ")}.
|
|
43816
|
+
Those protections will be inactive until you update Claude Code to ${required2} or newer. Everything else AKA does works normally.
|
|
43817
|
+
`
|
|
43818
|
+
);
|
|
43819
|
+
if (io.isInteractive && deps.assumeYes !== true) {
|
|
43820
|
+
const answer = (await io.ask("Install anyway? [y/N] ")).trim().toLowerCase();
|
|
43821
|
+
if (answer !== "y" && answer !== "yes") {
|
|
43822
|
+
io.out("Not installed. Update Claude Code, then run this again.\n");
|
|
43823
|
+
if (deps.declineIsFailure !== false) process.exitCode = 1;
|
|
43824
|
+
return;
|
|
43825
|
+
}
|
|
43826
|
+
}
|
|
43827
|
+
}
|
|
43828
|
+
}
|
|
43122
43829
|
const plan = manager3.installSpawnPlan(ref, agent.marketplaceSource, agent.marketplace);
|
|
43123
43830
|
process.stdout.write(
|
|
43124
43831
|
`Installing ${agent.name} via ${cliBin}, running:
|
|
@@ -43152,7 +43859,7 @@ function storeModeTargets(home) {
|
|
|
43152
43859
|
try {
|
|
43153
43860
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
43154
43861
|
if (entry.name.endsWith(".partial") && !entry.isDirectory()) continue;
|
|
43155
|
-
targets.add(
|
|
43862
|
+
targets.add(join35(dir, entry.name));
|
|
43156
43863
|
}
|
|
43157
43864
|
} catch {
|
|
43158
43865
|
}
|
|
@@ -43197,7 +43904,7 @@ function assertStoreLinksResolve(home) {
|
|
|
43197
43904
|
}
|
|
43198
43905
|
function isBrokenLink(path) {
|
|
43199
43906
|
try {
|
|
43200
|
-
return lstatSync4(path).isSymbolicLink() && !
|
|
43907
|
+
return lstatSync4(path).isSymbolicLink() && !existsSync19(path);
|
|
43201
43908
|
} catch {
|
|
43202
43909
|
return false;
|
|
43203
43910
|
}
|
|
@@ -43234,12 +43941,12 @@ async function runInit(argv) {
|
|
|
43234
43941
|
ensureLayoutDirSync(home);
|
|
43235
43942
|
const settings = settingsDir(home);
|
|
43236
43943
|
ensureLayoutDirSync(settings);
|
|
43237
|
-
const settingsFile =
|
|
43944
|
+
const settingsFile = join35(settings, "settings.json");
|
|
43238
43945
|
let settingsCreated = false;
|
|
43239
|
-
if (!
|
|
43946
|
+
if (!existsSync19(settingsFile)) {
|
|
43240
43947
|
try {
|
|
43241
43948
|
settingsCreated = withFileLock(settingsFile, () => {
|
|
43242
|
-
if (
|
|
43949
|
+
if (existsSync19(settingsFile)) return false;
|
|
43243
43950
|
writeOwnerOnlyFileSync(
|
|
43244
43951
|
settingsFile,
|
|
43245
43952
|
`${JSON.stringify(defaultWorkspaceSettings(), null, 2)}
|
|
@@ -43268,7 +43975,7 @@ async function runInit(argv) {
|
|
|
43268
43975
|
const symlinked = symlinkedStorePaths(home);
|
|
43269
43976
|
process.stdout.write(
|
|
43270
43977
|
`\u2713 Initialized AKA at ${home}
|
|
43271
|
-
settings: ${settingsFile}${settingsCreated ? "" :
|
|
43978
|
+
settings: ${settingsFile}${settingsCreated ? "" : existsSync19(settingsFile) ? " (kept existing)" : " (not written \u2014 another process was writing it)"}
|
|
43272
43979
|
database: ${dbPath(home)}
|
|
43273
43980
|
seeded ${String(policyCount)} default policies, ${String(packCount)} detection pack(s)
|
|
43274
43981
|
` + (updatesAvailable > 0 ? ` \u2B06 ${String(updatesAvailable)} detection pack update(s) available \u2014 review with \`aka detections\`, apply with \`aka detections update --all\`
|
|
@@ -43305,7 +44012,10 @@ Install the ${PRODUCT_NAME} plugin for Claude Code now (via the marketplace)? [y
|
|
|
43305
44012
|
return;
|
|
43306
44013
|
}
|
|
43307
44014
|
}
|
|
43308
|
-
await runPlugins(["install", "claude-code"]
|
|
44015
|
+
await runPlugins(["install", "claude-code"], {
|
|
44016
|
+
assumeYes: autoYes,
|
|
44017
|
+
declineIsFailure: false
|
|
44018
|
+
});
|
|
43309
44019
|
}
|
|
43310
44020
|
async function confirm(question) {
|
|
43311
44021
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -43332,13 +44042,29 @@ function renderEgressLine(egress) {
|
|
|
43332
44042
|
const cap = egress.truncated ? ` (capped at ${String(MAX_EGRESS_CALL_SITES_PER_PROJECT)} call sites)` : "";
|
|
43333
44043
|
return `Data shares: ${String(egress.destinations)} destination(s) \xB7 ${String(egress.endpoints)} endpoint(s) \xB7 ${String(egress.callSites)} call site(s)${cap}`;
|
|
43334
44044
|
}
|
|
43335
|
-
|
|
44045
|
+
function reportedForward(outcome) {
|
|
44046
|
+
return outcome === null || outcome.status === "not-attached" ? null : outcome;
|
|
44047
|
+
}
|
|
44048
|
+
function renderForwardLine(outcome) {
|
|
44049
|
+
switch (outcome.status) {
|
|
44050
|
+
case "disabled":
|
|
44051
|
+
return outcome.reason === "opt-out" ? "Data shares: not forwarded (--no-forward)" : "Data shares: not forwarded (Data Shares is off in Settings)";
|
|
44052
|
+
case "no-credential":
|
|
44053
|
+
return `Data shares: not forwarded to ${outcome.endpoint} \u2014 no usable credential; re-attach with \`aka attach\``;
|
|
44054
|
+
case "forwarded":
|
|
44055
|
+
return `Data shares: forwarded to ${outcome.endpoint} \xB7 ${String(outcome.callSites)} call site(s)`;
|
|
44056
|
+
case "failed":
|
|
44057
|
+
return `Data shares: not forwarded to ${outcome.endpoint} \u2014 ${FORWARD_FAILURE_LINES[outcome.kind]}`;
|
|
44058
|
+
}
|
|
44059
|
+
}
|
|
44060
|
+
async function runScan(argv, deps = {}) {
|
|
43336
44061
|
const { values, positionals } = parseArgs7({
|
|
43337
44062
|
args: argv,
|
|
43338
44063
|
options: {
|
|
43339
44064
|
...HOME_OPTION,
|
|
43340
44065
|
format: { type: "string" },
|
|
43341
|
-
"fail-on": { type: "string" }
|
|
44066
|
+
"fail-on": { type: "string" },
|
|
44067
|
+
"no-forward": { type: "boolean" }
|
|
43342
44068
|
},
|
|
43343
44069
|
allowPositionals: true
|
|
43344
44070
|
});
|
|
@@ -43383,14 +44109,39 @@ async function runScan(argv) {
|
|
|
43383
44109
|
result = await scanPathIntoStore(db, target, {
|
|
43384
44110
|
ruleActions,
|
|
43385
44111
|
sourceTool: SOURCE_TOOL.Cli,
|
|
43386
|
-
dataDir: storeDir
|
|
44112
|
+
dataDir: storeDir,
|
|
44113
|
+
// The AKA home this invocation is actually using, so the scanner's
|
|
44114
|
+
// protected-path exclusion covers a `--home` store rather than the
|
|
44115
|
+
// literal `~/.aka` the default would name. `home` is the same base
|
|
44116
|
+
// `recordProjectEgress` takes below, for the same reason.
|
|
44117
|
+
akaHome: home
|
|
43387
44118
|
});
|
|
43388
44119
|
inventory = recordProjectInventory(db, target);
|
|
43389
44120
|
egress = recordProjectEgress(db, target, result.egress, home);
|
|
44121
|
+
} catch (err) {
|
|
44122
|
+
if (!isProtectedTarget(err)) throw err;
|
|
44123
|
+
process.stderr.write(`aka scan: ${err.message}
|
|
44124
|
+
`);
|
|
44125
|
+
process.exitCode = 1;
|
|
44126
|
+
return;
|
|
43390
44127
|
} finally {
|
|
43391
44128
|
db.close();
|
|
43392
44129
|
}
|
|
44130
|
+
const recorded = egress;
|
|
44131
|
+
const runForward = async () => {
|
|
44132
|
+
if (recorded === null) return null;
|
|
44133
|
+
try {
|
|
44134
|
+
return await forwardProjectEgress(home, recorded.input, {
|
|
44135
|
+
send: deps.send ?? createSharesSender(),
|
|
44136
|
+
enabled: values["no-forward"] !== true
|
|
44137
|
+
});
|
|
44138
|
+
} catch {
|
|
44139
|
+
process.stderr.write("aka scan: forward failed unexpectedly\n");
|
|
44140
|
+
return null;
|
|
44141
|
+
}
|
|
44142
|
+
};
|
|
43393
44143
|
if (format === "json") {
|
|
44144
|
+
const forward = await runForward();
|
|
43394
44145
|
const findings = result.files.flatMap(
|
|
43395
44146
|
(f) => f.findings.map((d) => ({
|
|
43396
44147
|
file: f.path,
|
|
@@ -43416,9 +44167,17 @@ async function runScan(argv) {
|
|
|
43416
44167
|
callSites: egress.callSites,
|
|
43417
44168
|
truncated: egress.truncated
|
|
43418
44169
|
} : null;
|
|
44170
|
+
const forwardJson = reportedForward(forward);
|
|
43419
44171
|
process.stdout.write(
|
|
43420
44172
|
`${JSON.stringify(
|
|
43421
|
-
{
|
|
44173
|
+
{
|
|
44174
|
+
target,
|
|
44175
|
+
scanned: result.scanned,
|
|
44176
|
+
findings,
|
|
44177
|
+
inventory: inventoryJson,
|
|
44178
|
+
egress: egressJson,
|
|
44179
|
+
forward: forwardJson
|
|
44180
|
+
},
|
|
43422
44181
|
null,
|
|
43423
44182
|
2
|
|
43424
44183
|
)}
|
|
@@ -43432,6 +44191,9 @@ async function runScan(argv) {
|
|
|
43432
44191
|
if (inventory) process.stdout.write(`${renderInventoryLine(inventory)}
|
|
43433
44192
|
`);
|
|
43434
44193
|
if (egress) process.stdout.write(`${renderEgressLine(egress)}
|
|
44194
|
+
`);
|
|
44195
|
+
const reported = reportedForward(await runForward());
|
|
44196
|
+
if (reported !== null) process.stdout.write(`${renderForwardLine(reported)}
|
|
43435
44197
|
`);
|
|
43436
44198
|
}
|
|
43437
44199
|
if (failOn !== void 0) {
|
|
@@ -43635,8 +44397,9 @@ function grant(io, exit, base, settings) {
|
|
|
43635
44397
|
exit(1);
|
|
43636
44398
|
return false;
|
|
43637
44399
|
}
|
|
44400
|
+
const grantedAt = Date.now();
|
|
43638
44401
|
const consent = {
|
|
43639
|
-
acknowledgedAt:
|
|
44402
|
+
acknowledgedAt: new Date(grantedAt).toISOString(),
|
|
43640
44403
|
payloadVersion: HISTORY_SYNC_PAYLOAD_VERSION,
|
|
43641
44404
|
endpoint: settings.controlPlane.endpoint
|
|
43642
44405
|
};
|
|
@@ -43647,14 +44410,15 @@ function grant(io, exit, base, settings) {
|
|
|
43647
44410
|
exit(1);
|
|
43648
44411
|
return false;
|
|
43649
44412
|
}
|
|
44413
|
+
seedCaptureBacklogOwed(dataDir(base), grantedAt);
|
|
43650
44414
|
io.out(
|
|
43651
44415
|
`Sending this machine's unsent activity to ${controlPlaneName(settings.controlPlane)}.
|
|
43652
|
-
That is the activity recorded before it attached
|
|
43653
|
-
deliver
|
|
43654
|
-
in that text is masked only where the policy assigned to the detection
|
|
43655
|
-
is redact or block; under monitor or warn it goes as it was seen, and
|
|
43656
|
-
ships on monitor. It goes in the background, a little at a time,
|
|
43657
|
-
next session. Anything already sent cannot be recalled.
|
|
44416
|
+
That is the activity recorded before it attached and anything a live send could not
|
|
44417
|
+
deliver, alike: for a captured prompt, reply or tool result, either one includes its
|
|
44418
|
+
text. A value in that text is masked only where the policy assigned to the detection
|
|
44419
|
+
that flagged it is redact or block; under monitor or warn it goes as it was seen, and
|
|
44420
|
+
every detection ships on monitor. It goes in the background, a little at a time,
|
|
44421
|
+
starting with your next session. Anything already sent cannot be recalled.
|
|
43658
44422
|
`
|
|
43659
44423
|
);
|
|
43660
44424
|
return true;
|
|
@@ -43685,9 +44449,10 @@ function describe4(settings) {
|
|
|
43685
44449
|
Run \`aka sync-history --on\` to send it.`;
|
|
43686
44450
|
}
|
|
43687
44451
|
return isHistorySyncConsentStale(settings.historySyncConsent, settings.controlPlane.endpoint) ? `Not sending this machine's unsent activity to ${where}: your grant predates a change.
|
|
43688
|
-
It now also covers the text of
|
|
43689
|
-
live send
|
|
43690
|
-
flagged it is set to redact or block
|
|
44452
|
+
It now also covers the text of the activity recorded before this machine attached \u2014
|
|
44453
|
+
not just what a live send failed to deliver afterward \u2014 in which a value is masked
|
|
44454
|
+
only where the detection that flagged it is set to redact or block, and every
|
|
44455
|
+
detection ships on monitor.
|
|
43691
44456
|
Run \`aka sync-history --on\` to grant it again.` : `Not sending this machine's unsent activity to ${where}: the earlier grant no longer
|
|
43692
44457
|
applies. Run \`aka sync-history --on\` to grant it again.`;
|
|
43693
44458
|
}
|
|
@@ -44150,71 +44915,6 @@ function Dashboard({
|
|
|
44150
44915
|
] });
|
|
44151
44916
|
}
|
|
44152
44917
|
|
|
44153
|
-
// ../packages/dashboard-ui/src/security/recommendations.ts
|
|
44154
|
-
var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
44155
|
-
var ADVICE = {
|
|
44156
|
-
secret: "Rotate the exposed credentials and move them out of prompts (secrets manager / env vars).",
|
|
44157
|
-
pii: "Remove or mask personal data before it reaches the model.",
|
|
44158
|
-
financial: "Strip card and account numbers; share only non-sensitive references.",
|
|
44159
|
-
phi: "Remove protected health information \u2014 it should never reach an external model.",
|
|
44160
|
-
code_context: "Confirm this proprietary code context is safe to share.",
|
|
44161
|
-
custom: "Review against your organization\u2019s custom policy."
|
|
44162
|
-
};
|
|
44163
|
-
var REC_TEMPLATE = {
|
|
44164
|
-
secret: { title: "Exposed secret detected", action: "Rotate" },
|
|
44165
|
-
pii: { title: "Personal data in a prompt", action: "Remove" },
|
|
44166
|
-
financial: { title: "Financial data detected", action: "Strip" },
|
|
44167
|
-
phi: { title: "Health information detected", action: "Remove" },
|
|
44168
|
-
code_context: { title: "Proprietary code shared", action: "Review" },
|
|
44169
|
-
custom: { title: "Custom policy match", action: "Review" }
|
|
44170
|
-
};
|
|
44171
|
-
var MAX_RECOMMENDATIONS = 10;
|
|
44172
|
-
function healthScore(summary) {
|
|
44173
|
-
const handled = summary.byAction.block + summary.byAction.redact + summary.byAction.warn;
|
|
44174
|
-
const handledRatio = summary.findings === 0 ? 1 : handled / summary.findings;
|
|
44175
|
-
return Math.round(100 * (0.6 * summary.coverage + 0.4 * handledRatio));
|
|
44176
|
-
}
|
|
44177
|
-
function findingStatus(summary) {
|
|
44178
|
-
return {
|
|
44179
|
-
score: healthScore(summary),
|
|
44180
|
-
unreviewed: { ...summary.bySeverity },
|
|
44181
|
-
openFindings: summary.findings
|
|
44182
|
-
};
|
|
44183
|
-
}
|
|
44184
|
-
function bucketize(findings) {
|
|
44185
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
44186
|
-
for (const f of findings) {
|
|
44187
|
-
const b = buckets.get(f.category) ?? {
|
|
44188
|
-
category: f.category,
|
|
44189
|
-
count: 0,
|
|
44190
|
-
severity: f.severity,
|
|
44191
|
-
weight: 0,
|
|
44192
|
-
ruleId: f.ruleId
|
|
44193
|
-
};
|
|
44194
|
-
b.count++;
|
|
44195
|
-
const w = SEVERITY_WEIGHT[f.severity] ?? 0;
|
|
44196
|
-
if (w > b.weight) {
|
|
44197
|
-
b.weight = w;
|
|
44198
|
-
b.severity = f.severity;
|
|
44199
|
-
b.ruleId = f.ruleId;
|
|
44200
|
-
}
|
|
44201
|
-
buckets.set(f.category, b);
|
|
44202
|
-
}
|
|
44203
|
-
return [...buckets.values()].sort((a, b) => b.weight - a.weight || b.count - a.count).slice(0, MAX_RECOMMENDATIONS);
|
|
44204
|
-
}
|
|
44205
|
-
function buildRecommendations(findings) {
|
|
44206
|
-
return bucketize(findings).map((b) => {
|
|
44207
|
-
const t = REC_TEMPLATE[b.category] ?? { title: `${b.category} finding`, action: "Review" };
|
|
44208
|
-
return {
|
|
44209
|
-
severity: b.severity,
|
|
44210
|
-
title: t.title,
|
|
44211
|
-
description: ADVICE[b.category] ?? "Review this finding against your policy.",
|
|
44212
|
-
context: `${b.ruleId} \xB7 ${String(b.count)} finding${b.count === 1 ? "" : "s"}`,
|
|
44213
|
-
action: t.action
|
|
44214
|
-
};
|
|
44215
|
-
});
|
|
44216
|
-
}
|
|
44217
|
-
|
|
44218
44918
|
// src/tui/report.ts
|
|
44219
44919
|
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
44220
44920
|
function weekday(isoDay2) {
|
|
@@ -44470,30 +45170,30 @@ import { parseArgs as parseArgs12 } from "util";
|
|
|
44470
45170
|
import {
|
|
44471
45171
|
lstatSync as lstatSync5,
|
|
44472
45172
|
readdirSync as readdirSync8,
|
|
44473
|
-
readFileSync as
|
|
44474
|
-
realpathSync as
|
|
45173
|
+
readFileSync as readFileSync25,
|
|
45174
|
+
realpathSync as realpathSync6,
|
|
44475
45175
|
renameSync as renameSync6,
|
|
44476
|
-
rmSync as
|
|
45176
|
+
rmSync as rmSync9,
|
|
44477
45177
|
statSync as statSync15,
|
|
44478
|
-
writeFileSync as
|
|
45178
|
+
writeFileSync as writeFileSync10
|
|
44479
45179
|
} from "fs";
|
|
44480
|
-
import { homedir as
|
|
44481
|
-
import { basename as basename7, dirname as dirname12, isAbsolute as isAbsolute2, join as
|
|
45180
|
+
import { homedir as homedir8 } from "os";
|
|
45181
|
+
import { basename as basename7, dirname as dirname12, isAbsolute as isAbsolute2, join as join36, relative as relative2, resolve as resolve6 } from "path";
|
|
44482
45182
|
var DEFAULT_MAX_REWRITE_BYTES = 32 * 1024 * 1024;
|
|
44483
45183
|
function transcriptRoots(userHome) {
|
|
44484
|
-
return [
|
|
45184
|
+
return [join36(userHome ?? homedir8(), ".claude", "projects")];
|
|
44485
45185
|
}
|
|
44486
45186
|
function readContainedFile(realPath, maxBytes = DEFAULT_MAX_REWRITE_BYTES) {
|
|
44487
45187
|
try {
|
|
44488
45188
|
if (statSync15(realPath).size > maxBytes) return null;
|
|
44489
|
-
return
|
|
45189
|
+
return readFileSync25(realPath, "utf8");
|
|
44490
45190
|
} catch {
|
|
44491
45191
|
return null;
|
|
44492
45192
|
}
|
|
44493
45193
|
}
|
|
44494
45194
|
function realPathOrNull(path) {
|
|
44495
45195
|
try {
|
|
44496
|
-
return
|
|
45196
|
+
return realpathSync6(path);
|
|
44497
45197
|
} catch {
|
|
44498
45198
|
return null;
|
|
44499
45199
|
}
|
|
@@ -44537,9 +45237,9 @@ function sweepStrandedTemps(realPath) {
|
|
|
44537
45237
|
const pid = Number(pidPart);
|
|
44538
45238
|
if (pid !== process.pid && isProcessAlive(pid)) continue;
|
|
44539
45239
|
try {
|
|
44540
|
-
const stranded =
|
|
45240
|
+
const stranded = join36(dir, name);
|
|
44541
45241
|
if (!lstatSync5(stranded).isFile()) continue;
|
|
44542
|
-
|
|
45242
|
+
rmSync9(stranded, { force: true });
|
|
44543
45243
|
} catch {
|
|
44544
45244
|
}
|
|
44545
45245
|
}
|
|
@@ -44551,7 +45251,7 @@ async function rewriteContainedFile(realPath, transform2, maxBytes = DEFAULT_MAX
|
|
|
44551
45251
|
before = statSync15(realPath);
|
|
44552
45252
|
if (before.size > maxBytes)
|
|
44553
45253
|
return { status: "aborted", reason: "file is larger than the read cap" };
|
|
44554
|
-
content =
|
|
45254
|
+
content = readFileSync25(realPath, "utf8");
|
|
44555
45255
|
} catch {
|
|
44556
45256
|
return { status: "aborted", reason: "unreadable" };
|
|
44557
45257
|
}
|
|
@@ -44566,16 +45266,16 @@ async function rewriteContainedFile(realPath, transform2, maxBytes = DEFAULT_MAX
|
|
|
44566
45266
|
if (result.text === content) return { status: "unchanged" };
|
|
44567
45267
|
const tmpPath = tempPathFor(realPath);
|
|
44568
45268
|
try {
|
|
44569
|
-
|
|
45269
|
+
writeFileSync10(tmpPath, result.text, { mode: before.mode & 511, flag: "wx" });
|
|
44570
45270
|
const now = statSync15(realPath);
|
|
44571
45271
|
if (now.size !== before.size || now.mtimeMs !== before.mtimeMs) {
|
|
44572
|
-
|
|
45272
|
+
rmSync9(tmpPath, { force: true, recursive: true });
|
|
44573
45273
|
return { status: "aborted", reason: "the file changed while it was being read" };
|
|
44574
45274
|
}
|
|
44575
45275
|
renameSync6(tmpPath, realPath);
|
|
44576
45276
|
} catch {
|
|
44577
45277
|
try {
|
|
44578
|
-
|
|
45278
|
+
rmSync9(tmpPath, { force: true, recursive: true });
|
|
44579
45279
|
} catch {
|
|
44580
45280
|
}
|
|
44581
45281
|
return { status: "aborted", reason: "the rewrite could not be written" };
|