@akasecurity/cli 0.9.4 → 0.9.5
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 +18 -0
- package/dist/cli.js +2220 -410
- package/extension/background.js +1 -0
- package/extension/content.js +1 -0
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +39 -0
- package/extension/popup.html +69 -0
- package/extension/popup.js +1 -0
- package/native-host/host.js +31084 -0
- package/native-host/scan-worker.js +18006 -0
- package/package.json +12 -8
- package/web-ui/package.json +11 -7
- package/web-ui/packages/dashboard-ui/package.json +2 -0
- package/web-ui/packages/plugin-sdk/package.json +3 -1
- package/web-ui/packages/ui-kit/package.json +1 -0
- package/web-ui/rules/secrets/gcp-service-account.json +1 -1
- package/web-ui/web-ui/.next/BUILD_ID +1 -1
- package/web-ui/web-ui/.next/app-build-manifest.json +194 -56
- package/web-ui/web-ui/.next/app-path-routes-manifest.json +8 -8
- package/web-ui/web-ui/.next/build-manifest.json +8 -8
- package/web-ui/web-ui/.next/prerender-manifest.json +14 -14
- package/web-ui/web-ui/.next/required-server-files.json +5 -0
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- 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.js +2 -2
- 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.js +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 +8 -8
- package/web-ui/web-ui/.next/server/chunks/114.js +792 -0
- package/web-ui/web-ui/.next/server/chunks/160.js +785 -0
- package/web-ui/web-ui/.next/server/chunks/243.js +1 -1
- package/web-ui/web-ui/.next/server/chunks/330.js +1 -1
- package/web-ui/web-ui/.next/server/chunks/333.js +1 -0
- package/web-ui/web-ui/.next/server/chunks/363.js +12 -0
- package/web-ui/web-ui/.next/server/chunks/528.js +73 -0
- package/web-ui/web-ui/.next/server/chunks/557.js +5 -0
- package/web-ui/web-ui/.next/server/chunks/561.js +123 -0
- package/web-ui/web-ui/.next/server/functions-config-manifest.json +6 -6
- package/web-ui/web-ui/.next/server/middleware-build-manifest.js +1 -1
- 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/133-14f0f297836736b9.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/539-b35890d398017f99.js +62 -0
- package/web-ui/web-ui/.next/static/chunks/992-ba273ebe2ac236a5.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-90ea4cb1f1956a03.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-d96aa1158bb1c630.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-269fa2d4f47e6125.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/error-092d6780c93209d5.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-4ad5b60f476d7c6e.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-2bf76696fbe6e76d.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-5d2511a7c06dd645.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-1891ec610b461455.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-bc090490b3416d48.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-d7a290e9b0dd8a15.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-1d1a93a1e561ca82.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-7a1a29a230626f96.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-a7fa0e028623b7cb.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-9729632fc8128534.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-bda9e826a1ac1a00.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/loading-0e999b82265bac84.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-28116d3ce827e152.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/page-315a9068831a3ad5.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/{framework-3444de8758288822.js → framework-a566532c222ba944.js} +1 -1
- package/web-ui/web-ui/.next/static/chunks/{main-b13545f01f111855.js → main-f710385bb68f9de5.js} +1 -1
- package/web-ui/web-ui/.next/static/chunks/pages/{_app-7f0d920cc5af1c9f.js → _app-63f369037199ea68.js} +1 -1
- package/web-ui/web-ui/.next/static/chunks/pages/{_error-3d8b61361c4b2c6b.js → _error-41a11264bde7b852.js} +1 -1
- package/web-ui/web-ui/.next/static/css/0af7782f7a750368.css +2 -0
- package/web-ui/web-ui/.next/static/{cVKs25XbQsC1083sU5GOD → x4ixG3_q4UeyC_WNmSFdI}/_buildManifest.js +1 -1
- package/web-ui/web-ui/dist/scan-worker.js +18006 -0
- package/web-ui/web-ui/package.json +4 -3
- package/web-ui/web-ui/server.js +1 -1
- package/web-ui/web-ui/.next/server/chunks/28.js +0 -1
- package/web-ui/web-ui/.next/server/chunks/334.js +0 -2
- package/web-ui/web-ui/.next/server/chunks/514.js +0 -755
- package/web-ui/web-ui/.next/server/chunks/628.js +0 -84
- package/web-ui/web-ui/.next/server/chunks/817.js +0 -1
- package/web-ui/web-ui/.next/server/chunks/830.js +0 -750
- package/web-ui/web-ui/.next/server/chunks/949.js +0 -123
- package/web-ui/web-ui/.next/static/chunks/501-939e9e83b25a6293.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/84-8205054e8901dbde.js +0 -62
- package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/page-e3ce4be93d2f8ac5.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/page-8bb13b53c7bde504.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-07307e6d8ddb5cbf.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-aecbc7b6fbd143c2.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/page-1d468a1a0cf12aea.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-863e759b7e26c759.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/page-7636a56ffa40d5c6.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-c80a692b9d05f321.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-01e6dceb36cb882f.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/policies/page-d98e303b17524dad.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-178c53b1f56b5e97.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/security/page-7f69b2579337c729.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-7019a1567cd9aadd.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-90ce0ad1b9403f48.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/vault/page-7ef77f76ff07ea1f.js +0 -1
- package/web-ui/web-ui/.next/static/chunks/app/page-3fbfddda391d9939.js +0 -1
- package/web-ui/web-ui/.next/static/css/10425160b1a85ff4.css +0 -2
- /package/web-ui/web-ui/.next/static/{cVKs25XbQsC1083sU5GOD → x4ixG3_q4UeyC_WNmSFdI}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -539,12 +539,16 @@ function runInherit(command, args) {
|
|
|
539
539
|
}
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
-
// ../packages/local-ops/src/
|
|
543
|
-
function
|
|
544
|
-
return
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
542
|
+
// ../packages/local-ops/src/cli-plugin-manager.ts
|
|
543
|
+
function createCliPluginManager(bin) {
|
|
544
|
+
return {
|
|
545
|
+
available: () => binExists(bin),
|
|
546
|
+
ensureMarketplace: (source) => {
|
|
547
|
+
runCapture(bin, ["plugin", "marketplace", "add", source], 6e4);
|
|
548
|
+
},
|
|
549
|
+
install: (ref) => runInherit(bin, ["plugin", "install", ref]),
|
|
550
|
+
update: (ref) => runInherit(bin, ["plugin", "update", ref])
|
|
551
|
+
};
|
|
548
552
|
}
|
|
549
553
|
|
|
550
554
|
// ../packages/local-ops/src/registry.ts
|
|
@@ -557,7 +561,43 @@ var AGENT_PLUGINS = [
|
|
|
557
561
|
npmPackage: "@akasecurity/ai-tc-claude-code",
|
|
558
562
|
pluginName: "ai-tc",
|
|
559
563
|
marketplace: "akasecurity",
|
|
560
|
-
marketplaceSource: "akasecurity/marketplace"
|
|
564
|
+
marketplaceSource: "akasecurity/marketplace",
|
|
565
|
+
cliBin: "claude"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
id: "codex",
|
|
569
|
+
name: "Codex CLI",
|
|
570
|
+
sourceTool: "codex",
|
|
571
|
+
description: "Hooks Codex CLI sessions to detect + redact sensitive data in prompts, responses, and Bash tool calls.",
|
|
572
|
+
npmPackage: "@akasecurity/ai-tc-codex",
|
|
573
|
+
// Distinct from Claude Code's `pluginName: 'ai-tc'` — ids from the two
|
|
574
|
+
// registry entries flow into ONE ref-keyed `installed` lookup map
|
|
575
|
+
// (see updates.ts/installedPluginVersions and apply.ts/resolveRef); an
|
|
576
|
+
// identical ref for both would make either plugin's install ledger
|
|
577
|
+
// entry satisfy the other's "is it installed" check.
|
|
578
|
+
pluginName: "aka-codex",
|
|
579
|
+
// Codex CLI installs from this repo's own marketplace file; the canonical
|
|
580
|
+
// akasecurity/marketplace repo currently carries Claude Code only.
|
|
581
|
+
marketplace: "ai-tc",
|
|
582
|
+
marketplaceSource: "akasecurity/ai-tc",
|
|
583
|
+
cliBin: "codex"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
id: "antigravity",
|
|
587
|
+
name: "Antigravity",
|
|
588
|
+
sourceTool: "antigravity",
|
|
589
|
+
description: "Hooks Antigravity CLI sessions to detect sensitive data in shell commands and file writes.",
|
|
590
|
+
npmPackage: "@akasecurity/ai-tc-antigravity",
|
|
591
|
+
// NO pluginName/marketplace/cliBin, deliberately. Antigravity has no plugin
|
|
592
|
+
// marketplace: `agy plugin install` takes a LOCAL DIRECTORY PATH, not a
|
|
593
|
+
// `<plugin>@<marketplace>` ref, so the generic cli-plugin-manager binding
|
|
594
|
+
// the other two entries use would emit a command `agy` does not accept.
|
|
595
|
+
// Leaving the coordinates absent is the registry's documented "installed by
|
|
596
|
+
// other means" case — buildUpdateReport skips an entry with no ref, so this
|
|
597
|
+
// agent is listed and joined to its rows by `sourceTool` without claiming an
|
|
598
|
+
// update path that does not exist. Install is documented in
|
|
599
|
+
// plugins/antigravity/skills/setup/SKILL.md.
|
|
600
|
+
installHint: "Antigravity installs plugins from a local directory rather than a marketplace:\n npm pack @akasecurity/ai-tc-antigravity && tar -xzf akasecurity-ai-tc-antigravity-*.tgz\n agy plugin install ./package\nThen run `aka init` to set up the local store."
|
|
561
601
|
}
|
|
562
602
|
];
|
|
563
603
|
function findAgent(id) {
|
|
@@ -569,7 +609,7 @@ function pluginRef(agent) {
|
|
|
569
609
|
}
|
|
570
610
|
|
|
571
611
|
// ../packages/local-ops/src/updates.ts
|
|
572
|
-
import { existsSync, readFileSync } from "fs";
|
|
612
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
573
613
|
import { homedir as homedir2 } from "os";
|
|
574
614
|
import { dirname, join } from "path";
|
|
575
615
|
import { fileURLToPath } from "url";
|
|
@@ -583,6 +623,9 @@ function parse(version2) {
|
|
|
583
623
|
pre: match[4] ? match[4].split(".") : []
|
|
584
624
|
};
|
|
585
625
|
}
|
|
626
|
+
function isSemver(version2) {
|
|
627
|
+
return parse(version2) !== null;
|
|
628
|
+
}
|
|
586
629
|
function comparePre(a, b) {
|
|
587
630
|
if (a.length === 0 && b.length === 0) return 0;
|
|
588
631
|
if (a.length === 0) return 1;
|
|
@@ -665,6 +708,43 @@ function installedPluginVersions(claudeHome = join(homedir2(), ".claude")) {
|
|
|
665
708
|
}
|
|
666
709
|
return out;
|
|
667
710
|
}
|
|
711
|
+
function codexPluginCacheRoot(codexHome) {
|
|
712
|
+
return join(codexHome, "plugins", "cache");
|
|
713
|
+
}
|
|
714
|
+
function activeCodexPluginVersion(dir) {
|
|
715
|
+
let entries;
|
|
716
|
+
try {
|
|
717
|
+
entries = readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
718
|
+
} catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
if (entries.length === 0) return null;
|
|
722
|
+
if (entries.includes("local")) return "local";
|
|
723
|
+
const versions = entries.filter(isSemver);
|
|
724
|
+
if (versions.length === 0) return null;
|
|
725
|
+
return versions.reduce(
|
|
726
|
+
(best, candidate) => compareSemver(candidate, best) > 0 ? candidate : best
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
function installedCodexPluginVersions(codexHome = join(homedir2(), ".codex")) {
|
|
730
|
+
const out = /* @__PURE__ */ new Map();
|
|
731
|
+
const cacheRoot = codexPluginCacheRoot(codexHome);
|
|
732
|
+
for (const agent of AGENT_PLUGINS) {
|
|
733
|
+
if (agent.cliBin !== "codex") continue;
|
|
734
|
+
const ref = pluginRef(agent);
|
|
735
|
+
if (!ref || !agent.pluginName || !agent.marketplace) continue;
|
|
736
|
+
const pluginDir = join(cacheRoot, agent.marketplace, agent.pluginName);
|
|
737
|
+
const version2 = activeCodexPluginVersion(pluginDir);
|
|
738
|
+
if (version2 !== null) out.set(ref, version2);
|
|
739
|
+
}
|
|
740
|
+
return out;
|
|
741
|
+
}
|
|
742
|
+
function installedAgentPluginVersions(claudeHome, codexHome) {
|
|
743
|
+
return new Map([
|
|
744
|
+
...installedPluginVersions(claudeHome),
|
|
745
|
+
...installedCodexPluginVersions(codexHome)
|
|
746
|
+
]);
|
|
747
|
+
}
|
|
668
748
|
function npmViewVersion(pkg) {
|
|
669
749
|
const res = runCapture("npm", ["view", pkg, "version"], 15e3);
|
|
670
750
|
if (!res.ok || !res.stdout) return null;
|
|
@@ -707,7 +787,7 @@ function gatherReport(deps) {
|
|
|
707
787
|
function gatherReportLive() {
|
|
708
788
|
return gatherReport({
|
|
709
789
|
viewVersion: npmViewVersion,
|
|
710
|
-
installed:
|
|
790
|
+
installed: installedAgentPluginVersions(),
|
|
711
791
|
cliInstalled: cliVersion()
|
|
712
792
|
});
|
|
713
793
|
}
|
|
@@ -728,31 +808,53 @@ function resolveRef(agentId) {
|
|
|
728
808
|
const agent = findAgent(agentId);
|
|
729
809
|
if (!agent) return { ok: false, output: `unknown agent '${agentId}'` };
|
|
730
810
|
const ref = pluginRef(agent);
|
|
731
|
-
|
|
732
|
-
if (!
|
|
811
|
+
const cliBin = agent.cliBin;
|
|
812
|
+
if (!ref || !cliBin) {
|
|
813
|
+
return { ok: false, output: `${agent.name} has no automated install path yet.` };
|
|
814
|
+
}
|
|
815
|
+
const manager3 = createCliPluginManager(cliBin);
|
|
816
|
+
if (!manager3.available()) {
|
|
733
817
|
return {
|
|
734
818
|
ok: false,
|
|
735
|
-
output: `the \`
|
|
819
|
+
output: `the \`${cliBin}\` CLI isn't on your PATH \u2014 install ${agent.name}, then run \`${cliBin} plugin install ${ref}\` (or update with \`${cliBin} plugin update ${ref}\`).`
|
|
736
820
|
};
|
|
737
821
|
}
|
|
738
|
-
return { ref, marketplaceSource: agent.marketplaceSource };
|
|
822
|
+
return { ref, cliBin, marketplaceSource: agent.marketplaceSource };
|
|
739
823
|
}
|
|
740
824
|
function applyPluginUpdate(agentId, mode = "capture") {
|
|
741
825
|
const resolved = resolveRef(agentId);
|
|
742
826
|
if ("ok" in resolved) return resolved;
|
|
743
|
-
if (resolved.marketplaceSource)
|
|
744
|
-
|
|
827
|
+
if (resolved.marketplaceSource) {
|
|
828
|
+
createCliPluginManager(resolved.cliBin).ensureMarketplace(resolved.marketplaceSource);
|
|
829
|
+
}
|
|
830
|
+
return run(resolved.cliBin, ["plugin", "update", resolved.ref], mode);
|
|
745
831
|
}
|
|
746
832
|
function installAgentPlugin(agentId, mode = "capture") {
|
|
747
833
|
const resolved = resolveRef(agentId);
|
|
748
834
|
if ("ok" in resolved) return resolved;
|
|
749
|
-
if (resolved.marketplaceSource)
|
|
750
|
-
|
|
835
|
+
if (resolved.marketplaceSource) {
|
|
836
|
+
createCliPluginManager(resolved.cliBin).ensureMarketplace(resolved.marketplaceSource);
|
|
837
|
+
}
|
|
838
|
+
return run(resolved.cliBin, ["plugin", "install", resolved.ref], mode);
|
|
751
839
|
}
|
|
752
840
|
|
|
841
|
+
// ../packages/local-ops/src/claude-plugin.ts
|
|
842
|
+
var manager = createCliPluginManager("claude");
|
|
843
|
+
var claudeAvailable = manager.available;
|
|
844
|
+
var ensureMarketplace = manager.ensureMarketplace;
|
|
845
|
+
var installClaudePlugin = manager.install;
|
|
846
|
+
var updateClaudePlugin = manager.update;
|
|
847
|
+
|
|
848
|
+
// ../packages/local-ops/src/codex-plugin.ts
|
|
849
|
+
var manager2 = createCliPluginManager("codex");
|
|
850
|
+
var codexAvailable = manager2.available;
|
|
851
|
+
var ensureCodexMarketplace = manager2.ensureMarketplace;
|
|
852
|
+
var installCodexPlugin = manager2.install;
|
|
853
|
+
var updateCodexPlugin = manager2.update;
|
|
854
|
+
|
|
753
855
|
// ../packages/local-ops/src/egress-record.ts
|
|
754
|
-
import { statSync as
|
|
755
|
-
import { dirname as
|
|
856
|
+
import { statSync as statSync7 } from "fs";
|
|
857
|
+
import { dirname as dirname5, relative as relative2, resolve } from "path";
|
|
756
858
|
|
|
757
859
|
// ../packages/detections/src/egress/registry.ts
|
|
758
860
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -1742,11 +1844,11 @@ var EXACT_KIND_BY_BASENAME = {
|
|
|
1742
1844
|
"composer.json": "composer.json",
|
|
1743
1845
|
"packages.config": "packages.config"
|
|
1744
1846
|
};
|
|
1745
|
-
function manifestKindOf(
|
|
1746
|
-
if (LOCKFILE_BASENAMES.has(
|
|
1747
|
-
const exact = Object.hasOwn(EXACT_KIND_BY_BASENAME,
|
|
1847
|
+
function manifestKindOf(basename7) {
|
|
1848
|
+
if (LOCKFILE_BASENAMES.has(basename7)) return null;
|
|
1849
|
+
const exact = Object.hasOwn(EXACT_KIND_BY_BASENAME, basename7) ? EXACT_KIND_BY_BASENAME[basename7] : void 0;
|
|
1748
1850
|
if (exact !== void 0) return exact;
|
|
1749
|
-
if (
|
|
1851
|
+
if (basename7.endsWith(".csproj")) return "csproj";
|
|
1750
1852
|
return null;
|
|
1751
1853
|
}
|
|
1752
1854
|
function extractManifestSdks(text, kind) {
|
|
@@ -2486,6 +2588,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
2486
2588
|
{
|
|
2487
2589
|
tag: "0018_serious_tana_nile",
|
|
2488
2590
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
2591
|
+
},
|
|
2592
|
+
{
|
|
2593
|
+
tag: "0019_audit_started_at_index",
|
|
2594
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
2595
|
+
},
|
|
2596
|
+
{
|
|
2597
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
2598
|
+
sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
|
|
2489
2599
|
}
|
|
2490
2600
|
];
|
|
2491
2601
|
|
|
@@ -17346,7 +17456,17 @@ var Finding = external_exports.object({
|
|
|
17346
17456
|
}).meta({ id: "Finding" });
|
|
17347
17457
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
17348
17458
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
17349
|
-
var FindingProvider = external_exports.enum([
|
|
17459
|
+
var FindingProvider = external_exports.enum([
|
|
17460
|
+
"claudecode",
|
|
17461
|
+
"claudedesktop",
|
|
17462
|
+
"cursor",
|
|
17463
|
+
"copilot",
|
|
17464
|
+
"chatgpt",
|
|
17465
|
+
"claudeai",
|
|
17466
|
+
"codex",
|
|
17467
|
+
"antigravity",
|
|
17468
|
+
"api"
|
|
17469
|
+
]).meta({ id: "FindingProvider" });
|
|
17350
17470
|
var FindingCategory = external_exports.enum([
|
|
17351
17471
|
"secret",
|
|
17352
17472
|
"pii",
|
|
@@ -17400,7 +17520,16 @@ var FindingInstance = external_exports.object({
|
|
|
17400
17520
|
confidence: external_exports.number().min(0).max(1),
|
|
17401
17521
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
17402
17522
|
// that predate the resolution feature stay valid.
|
|
17403
|
-
status: FindingStatus.optional()
|
|
17523
|
+
status: FindingStatus.optional(),
|
|
17524
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
17525
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
17526
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
17527
|
+
// MOST RECENT detection event, not the first.
|
|
17528
|
+
eventId: external_exports.string().optional(),
|
|
17529
|
+
// The session that event belongs to, when it has one — the seam a
|
|
17530
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
17531
|
+
// outside a session.
|
|
17532
|
+
sessionId: external_exports.string().optional()
|
|
17404
17533
|
}).meta({ id: "FindingInstance" });
|
|
17405
17534
|
var FindingGroup = external_exports.object({
|
|
17406
17535
|
id: external_exports.string(),
|
|
@@ -17444,7 +17573,11 @@ var FindingFacets = external_exports.object({
|
|
|
17444
17573
|
// for every instance, so every group lands in a bucket; a status-less
|
|
17445
17574
|
// group (possible only for callers whose rows carry no statuses) is
|
|
17446
17575
|
// counted under no value.
|
|
17447
|
-
status: external_exports.array(FindingFacetItem)
|
|
17576
|
+
status: external_exports.array(FindingFacetItem),
|
|
17577
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
17578
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
17579
|
+
// because a group spans tools.
|
|
17580
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
17448
17581
|
}).meta({ id: "FindingFacets" });
|
|
17449
17582
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
17450
17583
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -17462,6 +17595,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
17462
17595
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
17463
17596
|
// session → findings drilldown). Findings without a session never match.
|
|
17464
17597
|
sessionId: external_exports.string().optional(),
|
|
17598
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
17599
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
17600
|
+
// means all time — this list has no default window.
|
|
17601
|
+
from: external_exports.iso.datetime().optional(),
|
|
17602
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
17603
|
+
// has already advanced past its sort position. This is what keeps the
|
|
17604
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
17605
|
+
// paginates: the target group is appended out of sort order rather than
|
|
17606
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
17607
|
+
includeId: external_exports.string().optional(),
|
|
17465
17608
|
groupBy: external_exports.literal("type").optional(),
|
|
17466
17609
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
17467
17610
|
cursor: external_exports.string().optional()
|
|
@@ -17506,15 +17649,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
17506
17649
|
detection: FindingDetectionRef,
|
|
17507
17650
|
policy: FindingPolicyRef
|
|
17508
17651
|
}).meta({ id: "FindingInstanceDetail" });
|
|
17652
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
17653
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
17654
|
+
severity: external_exports.array(Severity).optional(),
|
|
17655
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
17656
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
17657
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
17658
|
+
action: external_exports.array(FindingAction).optional(),
|
|
17659
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
17660
|
+
// the grouped query's group-level fold.
|
|
17661
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
17662
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
17663
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
17664
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
17665
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
17666
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
17667
|
+
repo: external_exports.string().optional(),
|
|
17668
|
+
file: external_exports.string().optional(),
|
|
17669
|
+
q: external_exports.string().optional(),
|
|
17670
|
+
sessionId: external_exports.string().optional(),
|
|
17671
|
+
from: external_exports.iso.datetime().optional(),
|
|
17672
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
17673
|
+
cursor: external_exports.string().optional()
|
|
17674
|
+
});
|
|
17675
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
17676
|
+
// Instances matching the filters across the whole scope, not just this
|
|
17677
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
17678
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
17679
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
17680
|
+
// dimension still excludes its own filter.
|
|
17681
|
+
facets: FindingFacets,
|
|
17682
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
17683
|
+
nextCursor: external_exports.string().nullable()
|
|
17684
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
17685
|
+
var FindingLocationFile = external_exports.object({
|
|
17686
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
17687
|
+
// with no file attribution).
|
|
17688
|
+
file: external_exports.string(),
|
|
17689
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
17690
|
+
maxSeverity: Severity,
|
|
17691
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
17692
|
+
// Folded from the instances' derived statuses with the same
|
|
17693
|
+
// open-dominates precedence a group uses.
|
|
17694
|
+
status: FindingStatus.optional(),
|
|
17695
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
17696
|
+
// chips, and the count is what conveys scale.
|
|
17697
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
17698
|
+
}).meta({ id: "FindingLocationFile" });
|
|
17699
|
+
var FindingLocationRepo = external_exports.object({
|
|
17700
|
+
/** Empty when the instances carried no repo attribute. */
|
|
17701
|
+
repo: external_exports.string(),
|
|
17702
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
17703
|
+
maxSeverity: Severity,
|
|
17704
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
17705
|
+
status: FindingStatus.optional(),
|
|
17706
|
+
files: external_exports.array(FindingLocationFile)
|
|
17707
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
17708
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
17709
|
+
severity: external_exports.array(Severity).optional(),
|
|
17710
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
17711
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
17712
|
+
action: external_exports.array(FindingAction).optional(),
|
|
17713
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
17714
|
+
// instances that match, and folds its status from those.
|
|
17715
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
17716
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
17717
|
+
q: external_exports.string().optional(),
|
|
17718
|
+
sessionId: external_exports.string().optional(),
|
|
17719
|
+
from: external_exports.iso.datetime().optional(),
|
|
17720
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
17721
|
+
});
|
|
17722
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
17723
|
+
totals: external_exports.object({
|
|
17724
|
+
findings: external_exports.number().int().nonnegative(),
|
|
17725
|
+
repos: external_exports.number().int().nonnegative(),
|
|
17726
|
+
files: external_exports.number().int().nonnegative()
|
|
17727
|
+
}),
|
|
17728
|
+
/** Sorted by max severity, then most recent. */
|
|
17729
|
+
items: external_exports.array(FindingLocationRepo),
|
|
17730
|
+
/** Whether `limit` truncated the repo list. */
|
|
17731
|
+
hasMore: external_exports.boolean()
|
|
17732
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
17509
17733
|
|
|
17510
17734
|
// ../packages/schema/src/zod/harness-map.ts
|
|
17511
|
-
var Harness = external_exports.enum([
|
|
17735
|
+
var Harness = external_exports.enum([
|
|
17736
|
+
"claudecode",
|
|
17737
|
+
"cursor",
|
|
17738
|
+
"copilot",
|
|
17739
|
+
"codex",
|
|
17740
|
+
"antigravity",
|
|
17741
|
+
"windsurf",
|
|
17742
|
+
"claudedesktop",
|
|
17743
|
+
"chatgpt",
|
|
17744
|
+
"claudeai",
|
|
17745
|
+
"api"
|
|
17746
|
+
]).meta({ id: "Harness" });
|
|
17512
17747
|
var TOOL_TO_HARNESS = {
|
|
17513
17748
|
"claude-code": "claudecode",
|
|
17514
17749
|
"claude-desktop": "claudedesktop",
|
|
17515
17750
|
"github-copilot": "copilot",
|
|
17516
17751
|
cursor: "cursor",
|
|
17517
|
-
chatgpt: "chatgpt"
|
|
17752
|
+
chatgpt: "chatgpt",
|
|
17753
|
+
codex: "codex",
|
|
17754
|
+
antigravity: "antigravity",
|
|
17755
|
+
"claude-ai": "claudeai"
|
|
17518
17756
|
};
|
|
17519
17757
|
|
|
17520
17758
|
// ../packages/schema/src/zod/meta.ts
|
|
@@ -17972,7 +18210,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
17972
18210
|
// ../packages/schema/src/zod/event.ts
|
|
17973
18211
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
17974
18212
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
17975
|
-
var SourceTool = external_exports.enum([
|
|
18213
|
+
var SourceTool = external_exports.enum([
|
|
18214
|
+
"claude-code",
|
|
18215
|
+
"claude-desktop",
|
|
18216
|
+
"cursor",
|
|
18217
|
+
"chatgpt",
|
|
18218
|
+
"claude-ai",
|
|
18219
|
+
"github-copilot",
|
|
18220
|
+
"codex",
|
|
18221
|
+
"antigravity",
|
|
18222
|
+
"cli",
|
|
18223
|
+
"unknown"
|
|
18224
|
+
]).meta({ id: "SourceTool" });
|
|
17976
18225
|
var EventMetadata = external_exports.object({
|
|
17977
18226
|
sessionId: external_exports.string().optional(),
|
|
17978
18227
|
repo: external_exports.string().optional(),
|
|
@@ -18043,7 +18292,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
18043
18292
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
18044
18293
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
18045
18294
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
18046
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
18295
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
18047
18296
|
var AccessCounts = external_exports.object({
|
|
18048
18297
|
open: external_exports.number().int().nonnegative(),
|
|
18049
18298
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -18312,9 +18561,19 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
18312
18561
|
useCount: true,
|
|
18313
18562
|
conditions: true
|
|
18314
18563
|
});
|
|
18564
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
18315
18565
|
function isMatchableUnder(keyVersion, key) {
|
|
18316
18566
|
return key.status === "present" && key.version === keyVersion;
|
|
18317
18567
|
}
|
|
18568
|
+
var LEDGER_WINDOW_HOURS = 24;
|
|
18569
|
+
function rotationBlockedLedgerNote(stillApprovable) {
|
|
18570
|
+
if (stillApprovable <= 0) {
|
|
18571
|
+
return "Recently blocked detections are invalidated too: the ledger outlives a rotation, so its rows stay listed as a record of what was blocked but stop being approvable. Trigger the detection again to approve it under the new key.";
|
|
18572
|
+
}
|
|
18573
|
+
const window = `from the last ${String(LEDGER_WINDOW_HOURS)} hours`;
|
|
18574
|
+
const subject = stillApprovable === 1 ? `1 recently blocked detection ${window} is still approvable; after rotating, none are` : `${String(stillApprovable)} recently blocked detections ${window} are still approvable; after rotating, none are`;
|
|
18575
|
+
return `${subject}. They stay listed as a record of what was blocked \u2014 trigger the detection again to approve under the new key.`;
|
|
18576
|
+
}
|
|
18318
18577
|
|
|
18319
18578
|
// ../packages/schema/src/zod/rule.ts
|
|
18320
18579
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -19125,6 +19384,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
19125
19384
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
19126
19385
|
}).meta({ id: "EgressWriteSummary" });
|
|
19127
19386
|
|
|
19387
|
+
// ../packages/schema/src/zod/exception-action.ts
|
|
19388
|
+
var confirmation = external_exports.string().optional();
|
|
19389
|
+
var ApproveBlockedInput = external_exports.object({
|
|
19390
|
+
reference: external_exports.string(),
|
|
19391
|
+
scope: external_exports.string(),
|
|
19392
|
+
reason: external_exports.string(),
|
|
19393
|
+
confirmation
|
|
19394
|
+
});
|
|
19395
|
+
var AddExceptionInput = external_exports.object({
|
|
19396
|
+
ruleId: external_exports.string(),
|
|
19397
|
+
value: external_exports.string(),
|
|
19398
|
+
scope: external_exports.string(),
|
|
19399
|
+
reason: external_exports.string(),
|
|
19400
|
+
confirmation
|
|
19401
|
+
});
|
|
19402
|
+
var GrantRevealInput = external_exports.object({
|
|
19403
|
+
pointer: external_exports.string(),
|
|
19404
|
+
scope: external_exports.string(),
|
|
19405
|
+
justification: external_exports.string(),
|
|
19406
|
+
confirmation
|
|
19407
|
+
});
|
|
19408
|
+
var RevokeExceptionInput = external_exports.object({
|
|
19409
|
+
id: external_exports.string(),
|
|
19410
|
+
reason: external_exports.string()
|
|
19411
|
+
});
|
|
19412
|
+
var RotateKeyInput = external_exports.object({
|
|
19413
|
+
confirmation: external_exports.string()
|
|
19414
|
+
});
|
|
19415
|
+
|
|
19128
19416
|
// ../packages/schema/src/zod/findings-group-build.ts
|
|
19129
19417
|
function toApiAction(dbVal) {
|
|
19130
19418
|
const map2 = {
|
|
@@ -19180,6 +19468,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
19180
19468
|
repo: r.repo,
|
|
19181
19469
|
file: r.file,
|
|
19182
19470
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
19471
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
19472
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
19183
19473
|
action: toApiAction(effectiveDbAction),
|
|
19184
19474
|
detectedAt: r.occurredAt,
|
|
19185
19475
|
confidence: r.confidence,
|
|
@@ -19311,14 +19601,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
19311
19601
|
}
|
|
19312
19602
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
19313
19603
|
var SEVERITY_RANK2 = SEVERITY_ORDER;
|
|
19604
|
+
function compareFindingGroupOrder(a, b) {
|
|
19605
|
+
const rankA = SEVERITY_RANK2[a.severity] ?? -1;
|
|
19606
|
+
const rankB = SEVERITY_RANK2[b.severity] ?? -1;
|
|
19607
|
+
const severityDiff = rankA - rankB;
|
|
19608
|
+
if (severityDiff !== 0) return severityDiff;
|
|
19609
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
19610
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
19611
|
+
return a.id.localeCompare(b.id);
|
|
19612
|
+
}
|
|
19314
19613
|
function sortFindingGroups(groups) {
|
|
19315
|
-
return [...groups].sort(
|
|
19316
|
-
const rankA = SEVERITY_RANK2[a.severity] ?? -1;
|
|
19317
|
-
const rankB = SEVERITY_RANK2[b.severity] ?? -1;
|
|
19318
|
-
const severityDiff = rankA - rankB;
|
|
19319
|
-
if (severityDiff !== 0) return severityDiff;
|
|
19320
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
19321
|
-
});
|
|
19614
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
19322
19615
|
}
|
|
19323
19616
|
function computeFindingFacets(allGroups, opts) {
|
|
19324
19617
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -19374,15 +19667,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
19374
19667
|
for (const g of forStatus) {
|
|
19375
19668
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
19376
19669
|
}
|
|
19377
|
-
const
|
|
19670
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
19671
|
+
return {
|
|
19672
|
+
severity: toItems2(severityMap),
|
|
19673
|
+
provider: toItems2(providerMap),
|
|
19674
|
+
action: toItems2(actionMap),
|
|
19675
|
+
subtype: toItems2(subtypeMap),
|
|
19676
|
+
status: toItems2(statusMap)
|
|
19677
|
+
};
|
|
19678
|
+
}
|
|
19679
|
+
|
|
19680
|
+
// ../packages/schema/src/zod/findings-flat-build.ts
|
|
19681
|
+
function rowHaystack(row) {
|
|
19682
|
+
return [
|
|
19683
|
+
row.ruleId,
|
|
19684
|
+
row.category,
|
|
19685
|
+
row.maskedMatch,
|
|
19686
|
+
row.repo,
|
|
19687
|
+
row.file,
|
|
19688
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
19689
|
+
row.id
|
|
19690
|
+
].join(" ").toLowerCase();
|
|
19691
|
+
}
|
|
19692
|
+
function matchesDimension(row, opts, dimension) {
|
|
19693
|
+
switch (dimension) {
|
|
19694
|
+
case "severity":
|
|
19695
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
19696
|
+
case "subtype":
|
|
19697
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
19698
|
+
case "providers":
|
|
19699
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
19700
|
+
case "actions":
|
|
19701
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
19702
|
+
case "statuses":
|
|
19703
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
19704
|
+
case "tools":
|
|
19705
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
19706
|
+
case "repo":
|
|
19707
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
19708
|
+
case "file":
|
|
19709
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
19710
|
+
case "q":
|
|
19711
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
19712
|
+
}
|
|
19713
|
+
}
|
|
19714
|
+
var DIMENSIONS = [
|
|
19715
|
+
"severity",
|
|
19716
|
+
"subtype",
|
|
19717
|
+
"providers",
|
|
19718
|
+
"actions",
|
|
19719
|
+
"statuses",
|
|
19720
|
+
"tools",
|
|
19721
|
+
"repo",
|
|
19722
|
+
"file",
|
|
19723
|
+
"q"
|
|
19724
|
+
];
|
|
19725
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
19726
|
+
for (const dimension of DIMENSIONS) {
|
|
19727
|
+
if (dimension === except) continue;
|
|
19728
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
19729
|
+
}
|
|
19730
|
+
return true;
|
|
19731
|
+
}
|
|
19732
|
+
function toItems(counts) {
|
|
19733
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
19734
|
+
}
|
|
19735
|
+
function bump(counts, value) {
|
|
19736
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
19737
|
+
}
|
|
19738
|
+
function createInstanceFacetAccumulator(opts) {
|
|
19739
|
+
const severity = /* @__PURE__ */ new Map();
|
|
19740
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
19741
|
+
const provider = /* @__PURE__ */ new Map();
|
|
19742
|
+
const action = /* @__PURE__ */ new Map();
|
|
19743
|
+
const status = /* @__PURE__ */ new Map();
|
|
19744
|
+
const tool = /* @__PURE__ */ new Map();
|
|
19745
|
+
return {
|
|
19746
|
+
add(row) {
|
|
19747
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
19748
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
19749
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
19750
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
19751
|
+
}
|
|
19752
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
19753
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
19754
|
+
bump(status, row.status);
|
|
19755
|
+
}
|
|
19756
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
19757
|
+
bump(tool, row.toolName);
|
|
19758
|
+
}
|
|
19759
|
+
},
|
|
19760
|
+
facets: () => ({
|
|
19761
|
+
severity: toItems(severity),
|
|
19762
|
+
subtype: toItems(subtype),
|
|
19763
|
+
provider: toItems(provider),
|
|
19764
|
+
action: toItems(action),
|
|
19765
|
+
status: toItems(status),
|
|
19766
|
+
tool: toItems(tool)
|
|
19767
|
+
})
|
|
19768
|
+
};
|
|
19769
|
+
}
|
|
19770
|
+
function toInstanceDetail(row) {
|
|
19771
|
+
const category = toApiCategory(row.category);
|
|
19772
|
+
return {
|
|
19773
|
+
id: row.id,
|
|
19774
|
+
provider: toApiProvider(row.sourceTool),
|
|
19775
|
+
repo: row.repo,
|
|
19776
|
+
file: row.file,
|
|
19777
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
19778
|
+
eventId: row.eventId,
|
|
19779
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
19780
|
+
action: toApiAction(row.actionTaken),
|
|
19781
|
+
detectedAt: row.occurredAt,
|
|
19782
|
+
confidence: row.confidence,
|
|
19783
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
19784
|
+
groupId: row.ruleId,
|
|
19785
|
+
category,
|
|
19786
|
+
subtype: row.ruleId,
|
|
19787
|
+
severity: row.severity,
|
|
19788
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
19789
|
+
detection: { id: row.ruleId, name: null },
|
|
19790
|
+
policy: { id: `category:${category}`, name: category }
|
|
19791
|
+
};
|
|
19792
|
+
}
|
|
19793
|
+
var SEVERITY_ORDER2 = {
|
|
19794
|
+
critical: 0,
|
|
19795
|
+
high: 1,
|
|
19796
|
+
medium: 2,
|
|
19797
|
+
low: 3
|
|
19798
|
+
};
|
|
19799
|
+
function newLocationAccumulator() {
|
|
19378
19800
|
return {
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
19801
|
+
instanceCount: 0,
|
|
19802
|
+
// Sorts after every known severity, so the first row always wins the
|
|
19803
|
+
// comparison below rather than an unknown value pinning the location.
|
|
19804
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
19805
|
+
maxSeverity: "low",
|
|
19806
|
+
latestDetectedAt: "",
|
|
19807
|
+
statuses: [],
|
|
19808
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
19384
19809
|
};
|
|
19385
19810
|
}
|
|
19811
|
+
function addToLocation(acc, row) {
|
|
19812
|
+
acc.instanceCount += 1;
|
|
19813
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
19814
|
+
if (rank < acc.maxSeverityRank) {
|
|
19815
|
+
acc.maxSeverityRank = rank;
|
|
19816
|
+
acc.maxSeverity = row.severity;
|
|
19817
|
+
}
|
|
19818
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
19819
|
+
acc.statuses.push(row.status);
|
|
19820
|
+
acc.ruleIds.add(row.ruleId);
|
|
19821
|
+
}
|
|
19386
19822
|
|
|
19387
19823
|
// ../packages/schema/src/zod/installed-pack.ts
|
|
19388
19824
|
var InstalledPack = external_exports.object({
|
|
@@ -19523,6 +19959,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
19523
19959
|
revealGrantId: external_exports.string().nullable(),
|
|
19524
19960
|
sightings: external_exports.array(VaultSighting)
|
|
19525
19961
|
});
|
|
19962
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
19963
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
19964
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
19965
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
19966
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
19967
|
+
// Opaque; names the last row of the page just served.
|
|
19968
|
+
cursor: external_exports.string().optional()
|
|
19969
|
+
});
|
|
19970
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
19971
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
19972
|
+
// independent, so paging never changes what the count claims.
|
|
19973
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
19974
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
19975
|
+
// `null` once the last page is reached.
|
|
19976
|
+
nextCursor: external_exports.string().nullable()
|
|
19977
|
+
});
|
|
19978
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
19979
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
19980
|
+
cursor: external_exports.string().optional()
|
|
19981
|
+
});
|
|
19982
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
19983
|
+
// Reused values across the whole store — the number the section's claim
|
|
19984
|
+
// ("values detected in more than one place") is about.
|
|
19985
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
19986
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
19987
|
+
nextCursor: external_exports.string().nullable()
|
|
19988
|
+
});
|
|
19989
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
19990
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
19991
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
19992
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
19993
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
19994
|
+
includeBatched: external_exports.boolean().optional(),
|
|
19995
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
19996
|
+
cursor: external_exports.string().optional()
|
|
19997
|
+
});
|
|
19998
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
19999
|
+
items: external_exports.array(VaultDeref),
|
|
20000
|
+
nextCursor: external_exports.string().nullable(),
|
|
20001
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
20002
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
20003
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
20004
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
20005
|
+
});
|
|
19526
20006
|
var VaultKeyCustody = external_exports.string();
|
|
19527
20007
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
19528
20008
|
var VAULT_CONSENT_VERSION = 1;
|
|
@@ -19902,7 +20382,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
19902
20382
|
// Omit for both kinds.
|
|
19903
20383
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
19904
20384
|
});
|
|
19905
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
20385
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
19906
20386
|
var ScanCoverageProvider = external_exports.object({
|
|
19907
20387
|
provider: Provider,
|
|
19908
20388
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -20214,9 +20694,8 @@ var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].m
|
|
|
20214
20694
|
);
|
|
20215
20695
|
|
|
20216
20696
|
// ../packages/persistence/src/database.ts
|
|
20217
|
-
import { randomUUID as
|
|
20218
|
-
import {
|
|
20219
|
-
import { join as join2, sep } from "path";
|
|
20697
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
20698
|
+
import { join as join3, sep } from "path";
|
|
20220
20699
|
import { DatabaseSync } from "node:sqlite";
|
|
20221
20700
|
|
|
20222
20701
|
// ../packages/persistence/src/ids.ts
|
|
@@ -20250,6 +20729,212 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
20250
20729
|
);
|
|
20251
20730
|
}
|
|
20252
20731
|
|
|
20732
|
+
// ../packages/persistence/src/internal/snapshot.ts
|
|
20733
|
+
import { randomUUID } from "crypto";
|
|
20734
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
20735
|
+
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
20736
|
+
|
|
20737
|
+
// ../packages/persistence/src/paths.ts
|
|
20738
|
+
import {
|
|
20739
|
+
chmodSync,
|
|
20740
|
+
linkSync,
|
|
20741
|
+
lstatSync,
|
|
20742
|
+
mkdirSync,
|
|
20743
|
+
renameSync,
|
|
20744
|
+
rmSync,
|
|
20745
|
+
writeFileSync
|
|
20746
|
+
} from "fs";
|
|
20747
|
+
import { threadId } from "worker_threads";
|
|
20748
|
+
var DATA_DIR_MODE = 448;
|
|
20749
|
+
var DATA_FILE_MODE = 384;
|
|
20750
|
+
var DB_FILENAME = "aka.db";
|
|
20751
|
+
function isSymlink(path) {
|
|
20752
|
+
try {
|
|
20753
|
+
return lstatSync(path).isSymbolicLink();
|
|
20754
|
+
} catch {
|
|
20755
|
+
return false;
|
|
20756
|
+
}
|
|
20757
|
+
}
|
|
20758
|
+
function chmodBestEffort(path, mode) {
|
|
20759
|
+
if (isSymlink(path)) return;
|
|
20760
|
+
try {
|
|
20761
|
+
chmodSync(path, mode);
|
|
20762
|
+
} catch {
|
|
20763
|
+
}
|
|
20764
|
+
}
|
|
20765
|
+
function tightenDir(dir) {
|
|
20766
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
20767
|
+
}
|
|
20768
|
+
function ensureDataDirSync(dir) {
|
|
20769
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
20770
|
+
tightenDir(dir);
|
|
20771
|
+
}
|
|
20772
|
+
function dbSidecars(file2) {
|
|
20773
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
20774
|
+
}
|
|
20775
|
+
function tightenFile(file2) {
|
|
20776
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
20777
|
+
}
|
|
20778
|
+
function tightenPerms(file2) {
|
|
20779
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
20780
|
+
}
|
|
20781
|
+
function writeOwnerOnlyFileSync(file2, data) {
|
|
20782
|
+
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
20783
|
+
try {
|
|
20784
|
+
rmSync(tmp, { force: true });
|
|
20785
|
+
} catch {
|
|
20786
|
+
}
|
|
20787
|
+
try {
|
|
20788
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
20789
|
+
renameSync(tmp, file2);
|
|
20790
|
+
} finally {
|
|
20791
|
+
try {
|
|
20792
|
+
rmSync(tmp, { force: true });
|
|
20793
|
+
} catch {
|
|
20794
|
+
}
|
|
20795
|
+
}
|
|
20796
|
+
tightenFile(file2);
|
|
20797
|
+
}
|
|
20798
|
+
function classifyOccupant(file2) {
|
|
20799
|
+
try {
|
|
20800
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
20801
|
+
return { kind: "gone" };
|
|
20802
|
+
} catch (err) {
|
|
20803
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
20804
|
+
return { kind: "unknown", cause: err };
|
|
20805
|
+
}
|
|
20806
|
+
}
|
|
20807
|
+
var KeyUnclaimableError = class extends Error {
|
|
20808
|
+
code = "key-unclaimable";
|
|
20809
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
20810
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
20811
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
20812
|
+
// captured and then lost, which is worse than its plain absence.
|
|
20813
|
+
constructor(message, cause) {
|
|
20814
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
20815
|
+
this.name = "KeyUnclaimableError";
|
|
20816
|
+
}
|
|
20817
|
+
};
|
|
20818
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
20819
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
20820
|
+
try {
|
|
20821
|
+
rmSync(tmp, { force: true });
|
|
20822
|
+
} catch {
|
|
20823
|
+
}
|
|
20824
|
+
let created;
|
|
20825
|
+
try {
|
|
20826
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
20827
|
+
created = publishByLink(tmp, file2, data);
|
|
20828
|
+
} finally {
|
|
20829
|
+
try {
|
|
20830
|
+
rmSync(tmp, { force: true });
|
|
20831
|
+
} catch {
|
|
20832
|
+
}
|
|
20833
|
+
}
|
|
20834
|
+
if (created) tightenFile(file2);
|
|
20835
|
+
return created;
|
|
20836
|
+
}
|
|
20837
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
20838
|
+
function publishByLink(tmp, file2, data) {
|
|
20839
|
+
try {
|
|
20840
|
+
linkSync(tmp, file2);
|
|
20841
|
+
return true;
|
|
20842
|
+
} catch (err) {
|
|
20843
|
+
const code = err.code;
|
|
20844
|
+
if (code === "EEXIST") return false;
|
|
20845
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
20846
|
+
}
|
|
20847
|
+
try {
|
|
20848
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
20849
|
+
return true;
|
|
20850
|
+
} catch (err) {
|
|
20851
|
+
if (err.code === "EEXIST") return false;
|
|
20852
|
+
throw err;
|
|
20853
|
+
}
|
|
20854
|
+
}
|
|
20855
|
+
|
|
20856
|
+
// ../packages/persistence/src/internal/snapshot.ts
|
|
20857
|
+
function backupPath(file2, tag) {
|
|
20858
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
20859
|
+
}
|
|
20860
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
20861
|
+
function reapStalePartials(file2) {
|
|
20862
|
+
const dir = dirname2(file2);
|
|
20863
|
+
const prefix = `${basename(file2)}.`;
|
|
20864
|
+
let entries;
|
|
20865
|
+
try {
|
|
20866
|
+
entries = readdirSync2(dir);
|
|
20867
|
+
} catch {
|
|
20868
|
+
return;
|
|
20869
|
+
}
|
|
20870
|
+
for (const name of entries) {
|
|
20871
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
20872
|
+
const partial2 = join2(dir, name);
|
|
20873
|
+
try {
|
|
20874
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
20875
|
+
rmSync2(partial2, { force: true });
|
|
20876
|
+
}
|
|
20877
|
+
} catch {
|
|
20878
|
+
}
|
|
20879
|
+
}
|
|
20880
|
+
}
|
|
20881
|
+
function snapshotStore(db, backup) {
|
|
20882
|
+
const partial2 = `${backup}.partial`;
|
|
20883
|
+
try {
|
|
20884
|
+
rmSync2(partial2, { force: true });
|
|
20885
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
20886
|
+
tightenFile(partial2);
|
|
20887
|
+
renameSync2(partial2, backup);
|
|
20888
|
+
} catch (error51) {
|
|
20889
|
+
try {
|
|
20890
|
+
rmSync2(partial2, { force: true });
|
|
20891
|
+
} catch {
|
|
20892
|
+
}
|
|
20893
|
+
throw error51;
|
|
20894
|
+
}
|
|
20895
|
+
}
|
|
20896
|
+
function moveStoreAside(file2, backup) {
|
|
20897
|
+
const undo = [];
|
|
20898
|
+
renameSync2(file2, backup);
|
|
20899
|
+
undo.push([backup, file2]);
|
|
20900
|
+
try {
|
|
20901
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
20902
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
20903
|
+
try {
|
|
20904
|
+
renameSync2(sidecar, moved);
|
|
20905
|
+
undo.push([moved, sidecar]);
|
|
20906
|
+
} catch {
|
|
20907
|
+
rmSync2(sidecar, { force: true });
|
|
20908
|
+
}
|
|
20909
|
+
}
|
|
20910
|
+
} catch (error51) {
|
|
20911
|
+
for (const [from, to] of undo.reverse()) {
|
|
20912
|
+
try {
|
|
20913
|
+
renameSync2(from, to);
|
|
20914
|
+
} catch {
|
|
20915
|
+
}
|
|
20916
|
+
}
|
|
20917
|
+
throw error51;
|
|
20918
|
+
}
|
|
20919
|
+
tightenPerms(backup);
|
|
20920
|
+
}
|
|
20921
|
+
function discardStore(file2, backup) {
|
|
20922
|
+
try {
|
|
20923
|
+
rmSync2(file2, { force: true });
|
|
20924
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
20925
|
+
rmSync2(sidecar, { force: true });
|
|
20926
|
+
}
|
|
20927
|
+
} catch (error51) {
|
|
20928
|
+
if (existsSync2(file2)) {
|
|
20929
|
+
try {
|
|
20930
|
+
rmSync2(backup, { force: true });
|
|
20931
|
+
} catch {
|
|
20932
|
+
}
|
|
20933
|
+
}
|
|
20934
|
+
throw error51;
|
|
20935
|
+
}
|
|
20936
|
+
}
|
|
20937
|
+
|
|
20253
20938
|
// ../packages/persistence/src/internal/sql-text.ts
|
|
20254
20939
|
function escapeLikePattern(s) {
|
|
20255
20940
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -20391,55 +21076,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
20391
21076
|
return out;
|
|
20392
21077
|
}
|
|
20393
21078
|
|
|
20394
|
-
// ../packages/persistence/src/paths.ts
|
|
20395
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
20396
|
-
var DATA_DIR_MODE = 448;
|
|
20397
|
-
var DATA_FILE_MODE = 384;
|
|
20398
|
-
var DB_FILENAME = "aka.db";
|
|
20399
|
-
function chmodBestEffort(path, mode) {
|
|
20400
|
-
try {
|
|
20401
|
-
chmodSync(path, mode);
|
|
20402
|
-
} catch {
|
|
20403
|
-
}
|
|
20404
|
-
}
|
|
20405
|
-
function tightenDir(dir) {
|
|
20406
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
20407
|
-
}
|
|
20408
|
-
function ensureDataDirSync(dir) {
|
|
20409
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
20410
|
-
tightenDir(dir);
|
|
20411
|
-
}
|
|
20412
|
-
function dbSidecars(file2) {
|
|
20413
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
20414
|
-
}
|
|
20415
|
-
function tightenFile(file2) {
|
|
20416
|
-
try {
|
|
20417
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
20418
|
-
} catch {
|
|
20419
|
-
}
|
|
20420
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
20421
|
-
}
|
|
20422
|
-
function tightenPerms(file2) {
|
|
20423
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
20424
|
-
}
|
|
20425
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
20426
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
20427
|
-
try {
|
|
20428
|
-
rmSync(tmp, { force: true });
|
|
20429
|
-
} catch {
|
|
20430
|
-
}
|
|
20431
|
-
try {
|
|
20432
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
20433
|
-
renameSync(tmp, file2);
|
|
20434
|
-
} finally {
|
|
20435
|
-
try {
|
|
20436
|
-
rmSync(tmp, { force: true });
|
|
20437
|
-
} catch {
|
|
20438
|
-
}
|
|
20439
|
-
}
|
|
20440
|
-
tightenFile(file2);
|
|
20441
|
-
}
|
|
20442
|
-
|
|
20443
21079
|
// ../packages/persistence/src/migrations.ts
|
|
20444
21080
|
function describeObject(object2) {
|
|
20445
21081
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -20555,9 +21191,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
20555
21191
|
}
|
|
20556
21192
|
}
|
|
20557
21193
|
function backupBeforeLegacyDrop(db, file2) {
|
|
20558
|
-
|
|
20559
|
-
|
|
20560
|
-
|
|
21194
|
+
reapStalePartials(file2);
|
|
21195
|
+
const backup = backupPath(file2, "pre-drop");
|
|
21196
|
+
snapshotStore(db, backup);
|
|
20561
21197
|
return backup;
|
|
20562
21198
|
}
|
|
20563
21199
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -20901,6 +21537,25 @@ function parseJsonObject(s) {
|
|
|
20901
21537
|
return void 0;
|
|
20902
21538
|
}
|
|
20903
21539
|
|
|
21540
|
+
// ../packages/persistence/src/internal/keyset-cursor.ts
|
|
21541
|
+
function encodeKeysetCursor(payload) {
|
|
21542
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
21543
|
+
}
|
|
21544
|
+
function decodeKeysetCursor(cursor) {
|
|
21545
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
21546
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
21547
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
21548
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
21549
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
21550
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
21551
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
21552
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
21553
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
21554
|
+
return parsed;
|
|
21555
|
+
}
|
|
21556
|
+
return null;
|
|
21557
|
+
}
|
|
21558
|
+
|
|
20904
21559
|
// ../packages/persistence/src/repositories/activity.ts
|
|
20905
21560
|
var DAY_MS = 864e5;
|
|
20906
21561
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -20946,16 +21601,6 @@ function utcWindow(nowMs) {
|
|
|
20946
21601
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
20947
21602
|
return { startMs, endMs: startMs + DAY_MS };
|
|
20948
21603
|
}
|
|
20949
|
-
function encodeCursor(payload) {
|
|
20950
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20951
|
-
}
|
|
20952
|
-
function decodeCursor(cursor) {
|
|
20953
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20954
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
20955
|
-
return parsed;
|
|
20956
|
-
}
|
|
20957
|
-
return null;
|
|
20958
|
-
}
|
|
20959
21604
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
20960
21605
|
session: "session",
|
|
20961
21606
|
prompt: "prompt",
|
|
@@ -21100,7 +21745,7 @@ var SqliteActivityRepository = class {
|
|
|
21100
21745
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
21101
21746
|
}
|
|
21102
21747
|
listSessions(query) {
|
|
21103
|
-
const cursor = query.cursor ?
|
|
21748
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
21104
21749
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
21105
21750
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
21106
21751
|
const conditions = [SESSION_ROOT];
|
|
@@ -21174,7 +21819,7 @@ var SqliteActivityRepository = class {
|
|
|
21174
21819
|
)
|
|
21175
21820
|
);
|
|
21176
21821
|
const last = page[page.length - 1];
|
|
21177
|
-
const nextCursor = hasMore && last ?
|
|
21822
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
21178
21823
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
21179
21824
|
}
|
|
21180
21825
|
getSession(sessionId) {
|
|
@@ -22047,7 +22692,7 @@ var SqliteEventsRepository = class {
|
|
|
22047
22692
|
};
|
|
22048
22693
|
|
|
22049
22694
|
// ../packages/persistence/src/repositories/exceptions.ts
|
|
22050
|
-
import { randomUUID } from "crypto";
|
|
22695
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
22051
22696
|
|
|
22052
22697
|
// ../packages/persistence/src/internal/sqlite-errors.ts
|
|
22053
22698
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -22083,8 +22728,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
22083
22728
|
AND conditions IS NULL
|
|
22084
22729
|
AND ${ACTIVE_PREDICATE}`;
|
|
22085
22730
|
var SqliteExceptionsRepository = class {
|
|
22086
|
-
constructor(db) {
|
|
22731
|
+
constructor(db, now = () => Date.now()) {
|
|
22087
22732
|
this.db = db;
|
|
22733
|
+
this.now = now;
|
|
22088
22734
|
this.consumeStmt = db.prepare(
|
|
22089
22735
|
`UPDATE exceptions
|
|
22090
22736
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -22102,6 +22748,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22102
22748
|
);
|
|
22103
22749
|
}
|
|
22104
22750
|
db;
|
|
22751
|
+
now;
|
|
22105
22752
|
consumeStmt;
|
|
22106
22753
|
insertBlockedStmt;
|
|
22107
22754
|
sweepBlockedStmt;
|
|
@@ -22128,8 +22775,8 @@ var SqliteExceptionsRepository = class {
|
|
|
22128
22775
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
22129
22776
|
);
|
|
22130
22777
|
}
|
|
22131
|
-
const id =
|
|
22132
|
-
const now =
|
|
22778
|
+
const id = randomUUID2();
|
|
22779
|
+
const now = this.now();
|
|
22133
22780
|
try {
|
|
22134
22781
|
this.insertExceptionRow(id, input, now);
|
|
22135
22782
|
} catch (err) {
|
|
@@ -22207,7 +22854,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22207
22854
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
22208
22855
|
const rows = allRows(
|
|
22209
22856
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
22210
|
-
opts?.includeTerminal ? {} : { now:
|
|
22857
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
22211
22858
|
);
|
|
22212
22859
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
22213
22860
|
return Promise.resolve(exceptions);
|
|
@@ -22242,7 +22889,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22242
22889
|
* already revoked.
|
|
22243
22890
|
*/
|
|
22244
22891
|
revoke(id, revokedBy, reason) {
|
|
22245
|
-
const now =
|
|
22892
|
+
const now = this.now();
|
|
22246
22893
|
const result = this.db.prepare(
|
|
22247
22894
|
`UPDATE exceptions
|
|
22248
22895
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -22256,7 +22903,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22256
22903
|
* callers must treat identically — means it does not and the detection is
|
|
22257
22904
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
22258
22905
|
*/
|
|
22259
|
-
consume(id, now =
|
|
22906
|
+
consume(id, now = this.now()) {
|
|
22260
22907
|
const result = this.consumeStmt.run({ id, now });
|
|
22261
22908
|
return Promise.resolve(Number(result.changes) === 1);
|
|
22262
22909
|
}
|
|
@@ -22265,7 +22912,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22265
22912
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
22266
22913
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
22267
22914
|
*/
|
|
22268
|
-
activeBundleEntries(keyVersion, now =
|
|
22915
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
22269
22916
|
const rows = allRows(
|
|
22270
22917
|
this.db.prepare(
|
|
22271
22918
|
`SELECT * FROM exceptions
|
|
@@ -22297,7 +22944,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22297
22944
|
* than the retention window on every write, so the ledger self-limits.
|
|
22298
22945
|
*/
|
|
22299
22946
|
recordBlocked(entry) {
|
|
22300
|
-
const now =
|
|
22947
|
+
const now = this.now();
|
|
22301
22948
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
22302
22949
|
this.insertBlockedStmt.run({
|
|
22303
22950
|
reference: entry.reference,
|
|
@@ -22320,7 +22967,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22320
22967
|
WHERE blocked_at > :cutoff
|
|
22321
22968
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
22322
22969
|
),
|
|
22323
|
-
{ cutoff:
|
|
22970
|
+
{ cutoff: this.now() - windowMs }
|
|
22324
22971
|
);
|
|
22325
22972
|
return Promise.resolve(
|
|
22326
22973
|
rows.map((row) => ({
|
|
@@ -22348,8 +22995,9 @@ var SqliteExceptionsRepository = class {
|
|
|
22348
22995
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
22349
22996
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
22350
22997
|
*/
|
|
22351
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
22998
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
22352
22999
|
try {
|
|
23000
|
+
const at = now ?? this.now();
|
|
22353
23001
|
const row = getRow(
|
|
22354
23002
|
this.db.prepare(
|
|
22355
23003
|
`SELECT id FROM exceptions
|
|
@@ -22358,7 +23006,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22358
23006
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22359
23007
|
LIMIT 1`
|
|
22360
23008
|
),
|
|
22361
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
23009
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
22362
23010
|
);
|
|
22363
23011
|
return Promise.resolve(row ?? null);
|
|
22364
23012
|
} catch (err) {
|
|
@@ -22372,7 +23020,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22372
23020
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
22373
23021
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
22374
23022
|
*/
|
|
22375
|
-
sweepTerminal(retentionMs, now =
|
|
23023
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
22376
23024
|
const result = this.db.prepare(
|
|
22377
23025
|
`DELETE FROM exceptions
|
|
22378
23026
|
WHERE updated_at < :cutoff
|
|
@@ -22435,6 +23083,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
22435
23083
|
|
|
22436
23084
|
// ../packages/persistence/src/repositories/findings.ts
|
|
22437
23085
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
23086
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
23087
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
23088
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
23089
|
+
function compareLocationOrder(a, b) {
|
|
23090
|
+
return compareFindingGroupOrder(
|
|
23091
|
+
{
|
|
23092
|
+
severity: a.maxSeverity,
|
|
23093
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
23094
|
+
id: ""
|
|
23095
|
+
},
|
|
23096
|
+
{
|
|
23097
|
+
severity: b.maxSeverity,
|
|
23098
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
23099
|
+
id: ""
|
|
23100
|
+
}
|
|
23101
|
+
);
|
|
23102
|
+
}
|
|
22438
23103
|
var CONCAT_SEP = ",";
|
|
22439
23104
|
var TUPLE_SEP = "|";
|
|
22440
23105
|
function splitConcat(value) {
|
|
@@ -22447,6 +23112,33 @@ function deriveInstanceStatus(row) {
|
|
|
22447
23112
|
latestResolutionStatus: row.latest_status
|
|
22448
23113
|
});
|
|
22449
23114
|
}
|
|
23115
|
+
function encodeGroupCursor(group) {
|
|
23116
|
+
const payload = {
|
|
23117
|
+
sev: group.severity,
|
|
23118
|
+
t: group.latestDetectedAt,
|
|
23119
|
+
id: group.id
|
|
23120
|
+
};
|
|
23121
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23122
|
+
}
|
|
23123
|
+
function decodeGroupCursor(cursor) {
|
|
23124
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23125
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
23126
|
+
return {
|
|
23127
|
+
severity: parsed.sev,
|
|
23128
|
+
latestDetectedAt: parsed.t,
|
|
23129
|
+
id: parsed.id
|
|
23130
|
+
};
|
|
23131
|
+
}
|
|
23132
|
+
return null;
|
|
23133
|
+
}
|
|
23134
|
+
function firstAfter(sorted, cursor) {
|
|
23135
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
23136
|
+
return index === -1 ? sorted.length : index;
|
|
23137
|
+
}
|
|
23138
|
+
function findDeepLinked(sorted, page, id) {
|
|
23139
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
23140
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
23141
|
+
}
|
|
22450
23142
|
var DAY_MS3 = 864e5;
|
|
22451
23143
|
var SqliteFindingsRepository = class {
|
|
22452
23144
|
constructor(db) {
|
|
@@ -22556,8 +23248,13 @@ var SqliteFindingsRepository = class {
|
|
|
22556
23248
|
*/
|
|
22557
23249
|
listGroupedFindings(query) {
|
|
22558
23250
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
22559
|
-
const
|
|
22560
|
-
const
|
|
23251
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
23252
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
23253
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
23254
|
+
const sessionParams = {
|
|
23255
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
23256
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
23257
|
+
};
|
|
22561
23258
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
22562
23259
|
predicate,
|
|
22563
23260
|
params: sessionParams
|
|
@@ -22565,7 +23262,8 @@ var SqliteFindingsRepository = class {
|
|
|
22565
23262
|
const rows = allRows(
|
|
22566
23263
|
this.db.prepare(
|
|
22567
23264
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
22568
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
23265
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
23266
|
+
kind, finding_key, latest_status
|
|
22569
23267
|
FROM (
|
|
22570
23268
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
22571
23269
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -22575,6 +23273,7 @@ var SqliteFindingsRepository = class {
|
|
|
22575
23273
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
22576
23274
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
22577
23275
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
23276
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
22578
23277
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
22579
23278
|
latest.status AS latest_status,
|
|
22580
23279
|
ROW_NUMBER() OVER (
|
|
@@ -22606,6 +23305,8 @@ var SqliteFindingsRepository = class {
|
|
|
22606
23305
|
repo: r.repo ?? "",
|
|
22607
23306
|
file: r.file ?? "",
|
|
22608
23307
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
23308
|
+
eventId: r.event_id,
|
|
23309
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
22609
23310
|
status: deriveInstanceStatus(r)
|
|
22610
23311
|
}));
|
|
22611
23312
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -22629,18 +23330,23 @@ var SqliteFindingsRepository = class {
|
|
|
22629
23330
|
groups: sorted.length
|
|
22630
23331
|
};
|
|
22631
23332
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
23333
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
23334
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
23335
|
+
const page = sorted.slice(start, start + limit);
|
|
23336
|
+
const lastOnPage = page.at(-1);
|
|
23337
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
23338
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
22632
23339
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
22633
|
-
const
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
);
|
|
23340
|
+
const narrow = (g) => statusSet ? {
|
|
23341
|
+
...g,
|
|
23342
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
23343
|
+
} : g;
|
|
23344
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
22639
23345
|
return Promise.resolve({
|
|
22640
23346
|
totals,
|
|
22641
23347
|
facets,
|
|
22642
23348
|
items,
|
|
22643
|
-
nextCursor
|
|
23349
|
+
nextCursor,
|
|
22644
23350
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
22645
23351
|
});
|
|
22646
23352
|
}
|
|
@@ -22672,6 +23378,266 @@ var SqliteFindingsRepository = class {
|
|
|
22672
23378
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
22673
23379
|
* path repeating across tuples.)
|
|
22674
23380
|
*/
|
|
23381
|
+
/**
|
|
23382
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
23383
|
+
* paged by keyset.
|
|
23384
|
+
*
|
|
23385
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
23386
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
23387
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
23388
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
23389
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
23390
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
23391
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
23392
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
23393
|
+
* none of the mappers names", which no IN-list can say.
|
|
23394
|
+
*
|
|
23395
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
23396
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
23397
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
23398
|
+
* while the counting runs, and only the page itself is retained.
|
|
23399
|
+
*/
|
|
23400
|
+
listFindingInstances(query) {
|
|
23401
|
+
const opts = {
|
|
23402
|
+
severity: query.severity,
|
|
23403
|
+
subtype: query.subtype,
|
|
23404
|
+
providers: query.provider,
|
|
23405
|
+
actions: query.action,
|
|
23406
|
+
statuses: query.status,
|
|
23407
|
+
tools: query.tool,
|
|
23408
|
+
repo: query.repo,
|
|
23409
|
+
file: query.file,
|
|
23410
|
+
q: query.q
|
|
23411
|
+
};
|
|
23412
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
23413
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23414
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
23415
|
+
const items = [];
|
|
23416
|
+
let total = 0;
|
|
23417
|
+
let last;
|
|
23418
|
+
let hasMore = false;
|
|
23419
|
+
for (const row of this.scanFindingRows({
|
|
23420
|
+
sessionId: query.sessionId,
|
|
23421
|
+
from: query.from
|
|
23422
|
+
})) {
|
|
23423
|
+
accumulator.add(row);
|
|
23424
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
23425
|
+
total += 1;
|
|
23426
|
+
if (items.length < limit) {
|
|
23427
|
+
items.push(toInstanceDetail(row));
|
|
23428
|
+
last = row;
|
|
23429
|
+
} else {
|
|
23430
|
+
hasMore = true;
|
|
23431
|
+
}
|
|
23432
|
+
}
|
|
23433
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
23434
|
+
if (cursor !== null) {
|
|
23435
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
23436
|
+
return Promise.resolve({
|
|
23437
|
+
totals: { findings: total },
|
|
23438
|
+
facets: accumulator.facets(),
|
|
23439
|
+
items: resumed.items,
|
|
23440
|
+
nextCursor: resumed.nextCursor
|
|
23441
|
+
});
|
|
23442
|
+
}
|
|
23443
|
+
return Promise.resolve({
|
|
23444
|
+
totals: { findings: total },
|
|
23445
|
+
facets: accumulator.facets(),
|
|
23446
|
+
items,
|
|
23447
|
+
nextCursor
|
|
23448
|
+
});
|
|
23449
|
+
}
|
|
23450
|
+
/**
|
|
23451
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
23452
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
23453
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
23454
|
+
* uses, so a later page costs less than the first rather than more.
|
|
23455
|
+
*/
|
|
23456
|
+
pageAfter(cursor, opts, limit, query) {
|
|
23457
|
+
const items = [];
|
|
23458
|
+
let last;
|
|
23459
|
+
let hasMore = false;
|
|
23460
|
+
for (const row of this.scanFindingRows({
|
|
23461
|
+
sessionId: query.sessionId,
|
|
23462
|
+
from: query.from,
|
|
23463
|
+
after: cursor
|
|
23464
|
+
})) {
|
|
23465
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
23466
|
+
if (items.length < limit) {
|
|
23467
|
+
items.push(toInstanceDetail(row));
|
|
23468
|
+
last = row;
|
|
23469
|
+
} else {
|
|
23470
|
+
hasMore = true;
|
|
23471
|
+
break;
|
|
23472
|
+
}
|
|
23473
|
+
}
|
|
23474
|
+
return {
|
|
23475
|
+
items,
|
|
23476
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
23477
|
+
};
|
|
23478
|
+
}
|
|
23479
|
+
/**
|
|
23480
|
+
* The same findings folded by location: repository, then file within it.
|
|
23481
|
+
*
|
|
23482
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
23483
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
23484
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
23485
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
23486
|
+
* filter can name it.
|
|
23487
|
+
*/
|
|
23488
|
+
listFindingLocations(query) {
|
|
23489
|
+
const opts = {
|
|
23490
|
+
severity: query.severity,
|
|
23491
|
+
subtype: query.subtype,
|
|
23492
|
+
providers: query.provider,
|
|
23493
|
+
actions: query.action,
|
|
23494
|
+
statuses: query.status,
|
|
23495
|
+
tools: query.tool,
|
|
23496
|
+
q: query.q
|
|
23497
|
+
};
|
|
23498
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
23499
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
23500
|
+
let total = 0;
|
|
23501
|
+
for (const row of this.scanFindingRows({
|
|
23502
|
+
sessionId: query.sessionId,
|
|
23503
|
+
from: query.from
|
|
23504
|
+
})) {
|
|
23505
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
23506
|
+
total += 1;
|
|
23507
|
+
let files = byRepo.get(row.repo);
|
|
23508
|
+
if (files === void 0) {
|
|
23509
|
+
files = /* @__PURE__ */ new Map();
|
|
23510
|
+
byRepo.set(row.repo, files);
|
|
23511
|
+
}
|
|
23512
|
+
let acc = files.get(row.file);
|
|
23513
|
+
if (acc === void 0) {
|
|
23514
|
+
acc = newLocationAccumulator();
|
|
23515
|
+
files.set(row.file, acc);
|
|
23516
|
+
}
|
|
23517
|
+
addToLocation(acc, row);
|
|
23518
|
+
}
|
|
23519
|
+
let fileCount = 0;
|
|
23520
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
23521
|
+
fileCount += files.size;
|
|
23522
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
23523
|
+
file: file2,
|
|
23524
|
+
instanceCount: acc.instanceCount,
|
|
23525
|
+
maxSeverity: acc.maxSeverity,
|
|
23526
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
23527
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
23528
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
23529
|
+
})).sort(compareLocationOrder);
|
|
23530
|
+
const rollup = fileRows.reduce(
|
|
23531
|
+
(a, f) => ({
|
|
23532
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
23533
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
23534
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
23535
|
+
}),
|
|
23536
|
+
{
|
|
23537
|
+
instanceCount: 0,
|
|
23538
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
23539
|
+
latestDetectedAt: ""
|
|
23540
|
+
}
|
|
23541
|
+
);
|
|
23542
|
+
const statuses = fileRows.map((f) => f.status);
|
|
23543
|
+
const folded = foldGroupStatus(statuses);
|
|
23544
|
+
return {
|
|
23545
|
+
repo,
|
|
23546
|
+
instanceCount: rollup.instanceCount,
|
|
23547
|
+
maxSeverity: rollup.maxSeverity,
|
|
23548
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
23549
|
+
...folded === void 0 ? {} : { status: folded },
|
|
23550
|
+
files: fileRows
|
|
23551
|
+
};
|
|
23552
|
+
});
|
|
23553
|
+
repos.sort(compareLocationOrder);
|
|
23554
|
+
return Promise.resolve({
|
|
23555
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
23556
|
+
items: repos.slice(0, limit),
|
|
23557
|
+
hasMore: repos.length > limit
|
|
23558
|
+
});
|
|
23559
|
+
}
|
|
23560
|
+
/**
|
|
23561
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
23562
|
+
*
|
|
23563
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
23564
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
23565
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
23566
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
23567
|
+
* rather than one unbounded result set.
|
|
23568
|
+
*
|
|
23569
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
23570
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
23571
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
23572
|
+
* a window over the whole resolution table once per batch.
|
|
23573
|
+
*
|
|
23574
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
23575
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
23576
|
+
* dimension — see listFindingInstances.
|
|
23577
|
+
*/
|
|
23578
|
+
*scanFindingRows(scope) {
|
|
23579
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
23580
|
+
const params = [];
|
|
23581
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
23582
|
+
conditions.push("e.root_session_id = ?");
|
|
23583
|
+
params.push(scope.sessionId);
|
|
23584
|
+
}
|
|
23585
|
+
if (scope.from !== void 0) {
|
|
23586
|
+
conditions.push("e.started_at >= ?");
|
|
23587
|
+
params.push(isoToEpochMillis(scope.from));
|
|
23588
|
+
}
|
|
23589
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
23590
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
23591
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
23592
|
+
e.started_at AS occurred_at,
|
|
23593
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
23594
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
23595
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
23596
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
23597
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
23598
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
23599
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
23600
|
+
FROM inspection_findings f
|
|
23601
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
23602
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
23603
|
+
WHERE ${conditions.join(" AND ")}
|
|
23604
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
23605
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
23606
|
+
LIMIT ?`;
|
|
23607
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
23608
|
+
for (; ; ) {
|
|
23609
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
23610
|
+
...params,
|
|
23611
|
+
after.startedAtMs,
|
|
23612
|
+
after.startedAtMs,
|
|
23613
|
+
after.id,
|
|
23614
|
+
SCAN_BATCH_ROWS
|
|
23615
|
+
]);
|
|
23616
|
+
for (const r of rows) {
|
|
23617
|
+
yield {
|
|
23618
|
+
id: r.id,
|
|
23619
|
+
ruleId: r.rule_id,
|
|
23620
|
+
category: r.category,
|
|
23621
|
+
severity: r.severity,
|
|
23622
|
+
maskedMatch: r.masked_match,
|
|
23623
|
+
actionTaken: r.action_taken,
|
|
23624
|
+
confidence: r.confidence,
|
|
23625
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
23626
|
+
sourceTool: r.source_tool,
|
|
23627
|
+
repo: r.repo ?? "",
|
|
23628
|
+
file: r.file ?? "",
|
|
23629
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
23630
|
+
eventId: r.event_id,
|
|
23631
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
23632
|
+
status: deriveInstanceStatus(r)
|
|
23633
|
+
};
|
|
23634
|
+
}
|
|
23635
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
23636
|
+
const lastRow = rows[rows.length - 1];
|
|
23637
|
+
if (lastRow === void 0) return;
|
|
23638
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
23639
|
+
}
|
|
23640
|
+
}
|
|
22675
23641
|
groupAggregates(withSearchText, scope) {
|
|
22676
23642
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
22677
23643
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -22932,7 +23898,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
22932
23898
|
};
|
|
22933
23899
|
|
|
22934
23900
|
// ../packages/persistence/src/repositories/installed-packs.ts
|
|
22935
|
-
import { createHash as createHash2, randomUUID as
|
|
23901
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
22936
23902
|
|
|
22937
23903
|
// ../packages/persistence/src/semver.ts
|
|
22938
23904
|
function parse4(version2) {
|
|
@@ -23083,7 +24049,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
23083
24049
|
let behind = false;
|
|
23084
24050
|
for (const row of rows) {
|
|
23085
24051
|
const params = {
|
|
23086
|
-
id:
|
|
24052
|
+
id: randomUUID3(),
|
|
23087
24053
|
namespace: row.namespace,
|
|
23088
24054
|
packId: row.packId,
|
|
23089
24055
|
version: row.version,
|
|
@@ -23095,7 +24061,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
23095
24061
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
23096
24062
|
this.upsertAvailableStmt.run({
|
|
23097
24063
|
...params,
|
|
23098
|
-
id:
|
|
24064
|
+
id: randomUUID3(),
|
|
23099
24065
|
recordedBy: meta3?.recordedBy ?? null
|
|
23100
24066
|
});
|
|
23101
24067
|
} else {
|
|
@@ -23418,14 +24384,15 @@ var SqliteInventoryRepository = class {
|
|
|
23418
24384
|
};
|
|
23419
24385
|
|
|
23420
24386
|
// ../packages/persistence/src/repositories/inventory-assets.ts
|
|
23421
|
-
import { randomUUID as
|
|
24387
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
23422
24388
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
23423
24389
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
23424
24390
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
23425
24391
|
var HARNESS_LABELS = {
|
|
23426
24392
|
claudecode: "Claude Code",
|
|
23427
24393
|
cursor: "Cursor",
|
|
23428
|
-
codex: "Codex"
|
|
24394
|
+
codex: "Codex",
|
|
24395
|
+
antigravity: "Antigravity"
|
|
23429
24396
|
};
|
|
23430
24397
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
23431
24398
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -23440,6 +24407,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
23440
24407
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
23441
24408
|
if (t.includes("cursor")) return "cursor";
|
|
23442
24409
|
if (t.includes("codex")) return "codex";
|
|
24410
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
23443
24411
|
return null;
|
|
23444
24412
|
}
|
|
23445
24413
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -23898,7 +24866,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
23898
24866
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
23899
24867
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
23900
24868
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
23901
|
-
).run({ id:
|
|
24869
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
23902
24870
|
}
|
|
23903
24871
|
return true;
|
|
23904
24872
|
}
|
|
@@ -23919,7 +24887,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
23919
24887
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
23920
24888
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
23921
24889
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
23922
|
-
).run({ id:
|
|
24890
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
23923
24891
|
}
|
|
23924
24892
|
this.configRowsCache = void 0;
|
|
23925
24893
|
return "ok";
|
|
@@ -24216,7 +25184,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
24216
25184
|
};
|
|
24217
25185
|
|
|
24218
25186
|
// ../packages/persistence/src/repositories/policies.ts
|
|
24219
|
-
import { randomUUID as
|
|
25187
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
24220
25188
|
var SqlitePoliciesRepository = class {
|
|
24221
25189
|
constructor(db) {
|
|
24222
25190
|
this.db = db;
|
|
@@ -24251,7 +25219,7 @@ var SqlitePoliciesRepository = class {
|
|
|
24251
25219
|
failOpenTransaction(this.db, () => {
|
|
24252
25220
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
24253
25221
|
stmt.run({
|
|
24254
|
-
id:
|
|
25222
|
+
id: randomUUID5(),
|
|
24255
25223
|
target: JSON.stringify({ category }),
|
|
24256
25224
|
action,
|
|
24257
25225
|
now: Date.now()
|
|
@@ -24271,7 +25239,7 @@ var SqlitePoliciesRepository = class {
|
|
|
24271
25239
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
24272
25240
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
24273
25241
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
24274
|
-
).run({ id:
|
|
25242
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
24275
25243
|
}
|
|
24276
25244
|
// Caps every global per-category policy currently set to block/redact down
|
|
24277
25245
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -24339,7 +25307,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
24339
25307
|
};
|
|
24340
25308
|
|
|
24341
25309
|
// ../packages/persistence/src/repositories/project-files.ts
|
|
24342
|
-
import { randomUUID as
|
|
25310
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
24343
25311
|
var SqliteProjectFilesRepository = class {
|
|
24344
25312
|
constructor(db) {
|
|
24345
25313
|
this.db = db;
|
|
@@ -24371,7 +25339,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
24371
25339
|
const stamp = Math.max(now, maxStamp + 1);
|
|
24372
25340
|
for (const file2 of scan2.files) {
|
|
24373
25341
|
this.upsertStmt.run({
|
|
24374
|
-
id:
|
|
25342
|
+
id: randomUUID6(),
|
|
24375
25343
|
projectId,
|
|
24376
25344
|
path: file2.path,
|
|
24377
25345
|
name: file2.name,
|
|
@@ -24385,7 +25353,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
24385
25353
|
};
|
|
24386
25354
|
|
|
24387
25355
|
// ../packages/persistence/src/repositories/resolutions.ts
|
|
24388
|
-
import { randomUUID as
|
|
25356
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
24389
25357
|
var SqliteResolutionsRepository = class {
|
|
24390
25358
|
constructor(db, now = () => Date.now()) {
|
|
24391
25359
|
this.db = db;
|
|
@@ -24439,7 +25407,7 @@ var SqliteResolutionsRepository = class {
|
|
|
24439
25407
|
*/
|
|
24440
25408
|
insertResolution(r) {
|
|
24441
25409
|
this.insertStmt.run({
|
|
24442
|
-
id:
|
|
25410
|
+
id: randomUUID7(),
|
|
24443
25411
|
findingKey: r.findingKey,
|
|
24444
25412
|
status: FindingStatus.parse(r.status),
|
|
24445
25413
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -24498,13 +25466,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
24498
25466
|
this.readStmt = db.prepare(
|
|
24499
25467
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
24500
25468
|
);
|
|
25469
|
+
this.countQuarantinedStmt = db.prepare(
|
|
25470
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
25471
|
+
);
|
|
25472
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
25473
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
25474
|
+
);
|
|
24501
25475
|
}
|
|
24502
25476
|
db;
|
|
24503
25477
|
upsertStmt;
|
|
24504
25478
|
readStmt;
|
|
25479
|
+
countQuarantinedStmt;
|
|
25480
|
+
clearQuarantinedStmt;
|
|
24505
25481
|
getVerdict(ruleKey) {
|
|
24506
25482
|
return getRow(this.readStmt, { ruleKey });
|
|
24507
25483
|
}
|
|
25484
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
25485
|
+
countQuarantined() {
|
|
25486
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
25487
|
+
}
|
|
25488
|
+
/**
|
|
25489
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
25490
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
25491
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
25492
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
25493
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
25494
|
+
*
|
|
25495
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
25496
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
25497
|
+
*
|
|
25498
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
25499
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
25500
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
25501
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
25502
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
25503
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
25504
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
25505
|
+
* to restore are silently still disabled.
|
|
25506
|
+
*/
|
|
25507
|
+
clearQuarantined() {
|
|
25508
|
+
const before = this.countQuarantined();
|
|
25509
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
25510
|
+
this.clearQuarantinedStmt.run();
|
|
25511
|
+
});
|
|
25512
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
25513
|
+
}
|
|
24508
25514
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
24509
25515
|
failOpenTransaction(this.db, () => {
|
|
24510
25516
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -24559,7 +25565,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
24559
25565
|
};
|
|
24560
25566
|
|
|
24561
25567
|
// ../packages/persistence/src/repositories/secret-vault.ts
|
|
24562
|
-
import { randomUUID as
|
|
25568
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
25569
|
+
function pageLimit(requested, fallback) {
|
|
25570
|
+
if (requested === void 0) return fallback;
|
|
25571
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
25572
|
+
}
|
|
25573
|
+
function encodeReuseCursor(payload) {
|
|
25574
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
25575
|
+
}
|
|
25576
|
+
function decodeReuseCursor(cursor) {
|
|
25577
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
25578
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
25579
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
25580
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
25581
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
25582
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
25583
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
25584
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
25585
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
25586
|
+
}
|
|
25587
|
+
return null;
|
|
25588
|
+
}
|
|
25589
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
25590
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
25591
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
25592
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
25593
|
+
function toSighting(row) {
|
|
25594
|
+
return {
|
|
25595
|
+
location: row.location,
|
|
25596
|
+
kind: row.kind,
|
|
25597
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
25598
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
25599
|
+
};
|
|
25600
|
+
}
|
|
24563
25601
|
var SELECT_COLUMNS = `
|
|
24564
25602
|
pointer_id AS pointerId,
|
|
24565
25603
|
value_fingerprint AS valueFingerprint,
|
|
@@ -24743,39 +25781,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
24743
25781
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
24744
25782
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
24745
25783
|
).run({
|
|
24746
|
-
id:
|
|
25784
|
+
id: randomUUID8(),
|
|
24747
25785
|
pointerId: entry.pointerId,
|
|
24748
25786
|
location: entry.location,
|
|
24749
25787
|
kind: entry.kind,
|
|
24750
25788
|
now
|
|
24751
25789
|
});
|
|
24752
25790
|
}
|
|
24753
|
-
|
|
25791
|
+
/**
|
|
25792
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
25793
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
25794
|
+
* the caller never has to distinguish "none" from "missing".
|
|
25795
|
+
*
|
|
25796
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
25797
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
25798
|
+
*/
|
|
25799
|
+
sightingsFor(pointerIds) {
|
|
25800
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
25801
|
+
if (pointerIds.length === 0) return byPointer;
|
|
24754
25802
|
const rows = allRows(
|
|
24755
25803
|
this.db.prepare(
|
|
24756
|
-
`SELECT location, kind, first_seen, last_seen
|
|
24757
|
-
|
|
25804
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
25805
|
+
FROM secret_vault_sighting
|
|
25806
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
25807
|
+
ORDER BY last_seen DESC`
|
|
24758
25808
|
),
|
|
24759
|
-
|
|
25809
|
+
pointerIds
|
|
24760
25810
|
);
|
|
25811
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
25812
|
+
return byPointer;
|
|
25813
|
+
}
|
|
25814
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
25815
|
+
toInventoryEntries(rows) {
|
|
25816
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
24761
25817
|
return rows.map((r) => ({
|
|
24762
|
-
|
|
24763
|
-
|
|
25818
|
+
pointerId: r.pointer_id,
|
|
25819
|
+
category: r.category,
|
|
25820
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
25821
|
+
maskedMatch: r.masked_match,
|
|
25822
|
+
occurrences: r.occurrence_count,
|
|
24764
25823
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
24765
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
25824
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
25825
|
+
revealGrantId: r.grant_id,
|
|
25826
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
24766
25827
|
}));
|
|
24767
25828
|
}
|
|
24768
25829
|
/**
|
|
24769
|
-
* The dashboard inventory
|
|
24770
|
-
* its sightings and the active
|
|
24771
|
-
* Raw-free by construction — neither
|
|
24772
|
-
* columns are selected.
|
|
25830
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
25831
|
+
* value's descriptor data joined with its sightings and the active
|
|
25832
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
25833
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
25834
|
+
*
|
|
25835
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
25836
|
+
* sees never depends on how far they have paged.
|
|
24773
25837
|
*/
|
|
24774
|
-
listInventory(now = Date.now()) {
|
|
25838
|
+
listInventory(query = {}, now = Date.now()) {
|
|
25839
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
25840
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
25841
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
24775
25842
|
const rows = allRows(
|
|
24776
25843
|
this.db.prepare(
|
|
24777
|
-
`SELECT
|
|
24778
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
25844
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
24779
25845
|
(SELECT e.id FROM exceptions e
|
|
24780
25846
|
WHERE e.rule_id = v.rule_id
|
|
24781
25847
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -24783,45 +25849,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
24783
25849
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
24784
25850
|
LIMIT 1) AS grant_id
|
|
24785
25851
|
FROM secret_vault v
|
|
24786
|
-
|
|
25852
|
+
${where}
|
|
25853
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
25854
|
+
LIMIT :limit`
|
|
24787
25855
|
),
|
|
24788
|
-
{
|
|
25856
|
+
bindParams({
|
|
25857
|
+
now,
|
|
25858
|
+
limit: limit + 1,
|
|
25859
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
25860
|
+
})
|
|
24789
25861
|
);
|
|
24790
|
-
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
|
|
24800
|
-
|
|
25862
|
+
const hasMore = rows.length > limit;
|
|
25863
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
25864
|
+
const last = page[page.length - 1];
|
|
25865
|
+
return {
|
|
25866
|
+
totals: { values: this.countEntries() },
|
|
25867
|
+
items: this.toInventoryEntries(page),
|
|
25868
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
25869
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
25870
|
+
};
|
|
25871
|
+
}
|
|
25872
|
+
/**
|
|
25873
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
25874
|
+
* than one location — most-reused first, one page at a time.
|
|
25875
|
+
*
|
|
25876
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
25877
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
25878
|
+
* under-report exactly the values a reader most needs to see.
|
|
25879
|
+
*/
|
|
25880
|
+
listReuse(query = {}, now = Date.now()) {
|
|
25881
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
25882
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
25883
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
25884
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
25885
|
+
const rows = allRows(
|
|
25886
|
+
this.db.prepare(
|
|
25887
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
25888
|
+
(SELECT e.id FROM exceptions e
|
|
25889
|
+
WHERE e.rule_id = v.rule_id
|
|
25890
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
25891
|
+
AND e.key_version = v.fingerprint_key_version
|
|
25892
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
25893
|
+
LIMIT 1) AS grant_id
|
|
25894
|
+
FROM secret_vault v
|
|
25895
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
25896
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
25897
|
+
LIMIT :limit`
|
|
25898
|
+
),
|
|
25899
|
+
bindParams({
|
|
25900
|
+
now,
|
|
25901
|
+
limit: limit + 1,
|
|
25902
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
25903
|
+
})
|
|
25904
|
+
);
|
|
25905
|
+
const hasMore = rows.length > limit;
|
|
25906
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
25907
|
+
const last = page[page.length - 1];
|
|
25908
|
+
return {
|
|
25909
|
+
totals: { reused: this.countReused() },
|
|
25910
|
+
items: this.toInventoryEntries(page),
|
|
25911
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
25912
|
+
};
|
|
24801
25913
|
}
|
|
24802
25914
|
/**
|
|
24803
|
-
* The de-reference trail, newest first. By default the
|
|
24804
|
-
* reasons (display, view-render) are hidden and counted
|
|
24805
|
-
* that matter as a signal are the model crossings, and
|
|
24806
|
-
* render noise would defeat the audit's purpose.
|
|
25915
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
25916
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
25917
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
25918
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
25919
|
+
*
|
|
25920
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
25921
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
25922
|
+
* the reader pages.
|
|
24807
25923
|
*/
|
|
24808
|
-
listDerefs(
|
|
24809
|
-
const limit =
|
|
24810
|
-
const
|
|
25924
|
+
listDerefs(query = {}) {
|
|
25925
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
25926
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
25927
|
+
const conditions = [];
|
|
25928
|
+
if (query.includeBatched !== true) {
|
|
25929
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
25930
|
+
}
|
|
25931
|
+
if (cursor !== null) {
|
|
25932
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
25933
|
+
}
|
|
25934
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
24811
25935
|
const rows = allRows(
|
|
24812
25936
|
this.db.prepare(
|
|
24813
25937
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
24814
25938
|
FROM secret_vault_deref ${where}
|
|
24815
|
-
ORDER BY at DESC,
|
|
25939
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
24816
25940
|
),
|
|
24817
|
-
{
|
|
25941
|
+
bindParams({
|
|
25942
|
+
limit: limit + 1,
|
|
25943
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
25944
|
+
})
|
|
24818
25945
|
);
|
|
24819
|
-
const
|
|
25946
|
+
const hasMore = rows.length > limit;
|
|
25947
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
25948
|
+
const last = page[page.length - 1];
|
|
25949
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
24820
25950
|
this.db,
|
|
24821
25951
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
24822
25952
|
);
|
|
24823
25953
|
return {
|
|
24824
|
-
|
|
25954
|
+
items: page.map((r) => ({
|
|
24825
25955
|
id: r.id,
|
|
24826
25956
|
pointerId: r.pointer_id,
|
|
24827
25957
|
at: new Date(r.at).toISOString(),
|
|
@@ -24831,12 +25961,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
24831
25961
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
24832
25962
|
pointerCount: r.pointer_count
|
|
24833
25963
|
})),
|
|
25964
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
24834
25965
|
hiddenBatched
|
|
24835
25966
|
};
|
|
24836
25967
|
}
|
|
24837
25968
|
countEntries() {
|
|
24838
25969
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
24839
25970
|
}
|
|
25971
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
25972
|
+
countReused() {
|
|
25973
|
+
return countScalar(
|
|
25974
|
+
this.db,
|
|
25975
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
25976
|
+
);
|
|
25977
|
+
}
|
|
24840
25978
|
};
|
|
24841
25979
|
|
|
24842
25980
|
// ../packages/persistence/src/repositories/security.ts
|
|
@@ -24851,7 +25989,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
24851
25989
|
var SCAN_COVERAGE = [
|
|
24852
25990
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
24853
25991
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
24854
|
-
{ provider: "codex", coverage:
|
|
25992
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
25993
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
25994
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
24855
25995
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
24856
25996
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
24857
25997
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -25184,7 +26324,7 @@ var SqliteSecurityRepository = class {
|
|
|
25184
26324
|
};
|
|
25185
26325
|
|
|
25186
26326
|
// ../packages/persistence/src/repositories/shares.ts
|
|
25187
|
-
import { randomUUID as
|
|
26327
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
25188
26328
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
25189
26329
|
var IN_CHUNK = 500;
|
|
25190
26330
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -25440,7 +26580,7 @@ var SqliteSharesRepository = class {
|
|
|
25440
26580
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
25441
26581
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
25442
26582
|
).run({
|
|
25443
|
-
id:
|
|
26583
|
+
id: randomUUID9(),
|
|
25444
26584
|
destinationId,
|
|
25445
26585
|
host: dest.host,
|
|
25446
26586
|
decision,
|
|
@@ -25589,7 +26729,7 @@ var SqliteSharesRepository = class {
|
|
|
25589
26729
|
let destinationId = destIds.get(hit.host);
|
|
25590
26730
|
if (destinationId === void 0) {
|
|
25591
26731
|
destStmt.run({
|
|
25592
|
-
id:
|
|
26732
|
+
id: randomUUID9(),
|
|
25593
26733
|
kind: hit.kind,
|
|
25594
26734
|
name: hit.name,
|
|
25595
26735
|
host: hit.host,
|
|
@@ -25605,7 +26745,7 @@ var SqliteSharesRepository = class {
|
|
|
25605
26745
|
let endpointId = endpointIds.get(endpointKey);
|
|
25606
26746
|
if (endpointId === void 0) {
|
|
25607
26747
|
endpointStmt.run({
|
|
25608
|
-
id:
|
|
26748
|
+
id: randomUUID9(),
|
|
25609
26749
|
destinationId,
|
|
25610
26750
|
method: hit.method,
|
|
25611
26751
|
transport: hit.transport,
|
|
@@ -25618,7 +26758,7 @@ var SqliteSharesRepository = class {
|
|
|
25618
26758
|
endpointIds.set(endpointKey, endpointId);
|
|
25619
26759
|
}
|
|
25620
26760
|
siteStmt.run({
|
|
25621
|
-
id:
|
|
26761
|
+
id: randomUUID9(),
|
|
25622
26762
|
endpointId,
|
|
25623
26763
|
project: input.project,
|
|
25624
26764
|
projectKey: input.projectKey,
|
|
@@ -25983,6 +27123,9 @@ function purgeSampleData(db) {
|
|
|
25983
27123
|
}
|
|
25984
27124
|
|
|
25985
27125
|
// ../packages/persistence/src/database.ts
|
|
27126
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
27127
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
27128
|
+
);
|
|
25986
27129
|
function linkHost(input, hostId) {
|
|
25987
27130
|
return hostId ? { ...input, hostId } : input;
|
|
25988
27131
|
}
|
|
@@ -26004,21 +27147,34 @@ function openWithPragmas(file2) {
|
|
|
26004
27147
|
}
|
|
26005
27148
|
return db;
|
|
26006
27149
|
}
|
|
26007
|
-
function backupLegacyStore(file2) {
|
|
26008
|
-
|
|
26009
|
-
|
|
26010
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
27150
|
+
function backupLegacyStore(db, file2) {
|
|
27151
|
+
reapStalePartials(file2);
|
|
27152
|
+
const backup = backupPath(file2, "legacy");
|
|
27153
|
+
let snapshotted = false;
|
|
27154
|
+
let snapshotError;
|
|
27155
|
+
try {
|
|
27156
|
+
snapshotStore(db, backup);
|
|
27157
|
+
snapshotted = true;
|
|
27158
|
+
} catch (error51) {
|
|
27159
|
+
snapshotError = error51;
|
|
27160
|
+
} finally {
|
|
27161
|
+
db.close();
|
|
27162
|
+
}
|
|
27163
|
+
if (!snapshotted) {
|
|
27164
|
+
akaWarn(
|
|
27165
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
27166
|
+
);
|
|
27167
|
+
moveStoreAside(file2, backup);
|
|
27168
|
+
return backup;
|
|
26013
27169
|
}
|
|
27170
|
+
discardStore(file2, backup);
|
|
26014
27171
|
return backup;
|
|
26015
27172
|
}
|
|
26016
27173
|
function openAndInitialize(file2) {
|
|
26017
27174
|
let db = openWithPragmas(file2);
|
|
26018
27175
|
try {
|
|
26019
27176
|
if (isForeignSqliteLineage(db)) {
|
|
26020
|
-
db
|
|
26021
|
-
const backup = backupLegacyStore(file2);
|
|
27177
|
+
const backup = backupLegacyStore(db, file2);
|
|
26022
27178
|
db = openWithPragmas(file2);
|
|
26023
27179
|
akaWarn(
|
|
26024
27180
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -26062,7 +27218,7 @@ function openAndInitialize(file2) {
|
|
|
26062
27218
|
}
|
|
26063
27219
|
function openLocalDatabase(dir) {
|
|
26064
27220
|
ensureDataDirSync(dir);
|
|
26065
|
-
const file2 =
|
|
27221
|
+
const file2 = join3(dir, DB_FILENAME);
|
|
26066
27222
|
const {
|
|
26067
27223
|
db,
|
|
26068
27224
|
events,
|
|
@@ -26179,7 +27335,7 @@ function openLocalDatabase(dir) {
|
|
|
26179
27335
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
26180
27336
|
if (!definitionId) continue;
|
|
26181
27337
|
inspectionFindings.insertFinding({
|
|
26182
|
-
id:
|
|
27338
|
+
id: randomUUID10(),
|
|
26183
27339
|
auditEventId: record2.scanEvent.id,
|
|
26184
27340
|
inspectionDefinitionId: definitionId,
|
|
26185
27341
|
span: finding.span,
|
|
@@ -26285,10 +27441,226 @@ function openLocalDatabase(dir) {
|
|
|
26285
27441
|
transaction,
|
|
26286
27442
|
close: () => {
|
|
26287
27443
|
db.close();
|
|
26288
|
-
}
|
|
27444
|
+
},
|
|
27445
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
27446
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
26289
27447
|
};
|
|
26290
27448
|
}
|
|
26291
27449
|
|
|
27450
|
+
// ../packages/persistence/src/file-lock.ts
|
|
27451
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
27452
|
+
import {
|
|
27453
|
+
closeSync,
|
|
27454
|
+
existsSync as existsSync3,
|
|
27455
|
+
openSync,
|
|
27456
|
+
readFileSync as readFileSync2,
|
|
27457
|
+
rmSync as rmSync3,
|
|
27458
|
+
statSync as statSync2,
|
|
27459
|
+
writeFileSync as writeFileSync2
|
|
27460
|
+
} from "fs";
|
|
27461
|
+
import { hostname as hostname3 } from "os";
|
|
27462
|
+
var LOCK_SUFFIX = ".lock";
|
|
27463
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
27464
|
+
var DEFAULT_STALE_MS = 2e3;
|
|
27465
|
+
var RETRY_INTERVAL_MS = 5;
|
|
27466
|
+
var RETRYABLE_CREATE_ERRNOS = /* @__PURE__ */ new Set(["EEXIST", "EACCES", "EPERM", "EBUSY"]);
|
|
27467
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
27468
|
+
function sleepSync(ms) {
|
|
27469
|
+
Atomics.wait(PARK, 0, 0, ms);
|
|
27470
|
+
}
|
|
27471
|
+
var FileLockError = class extends Error {
|
|
27472
|
+
reason;
|
|
27473
|
+
file;
|
|
27474
|
+
holderPid;
|
|
27475
|
+
constructor(reason, file2, detail, holderPid, options) {
|
|
27476
|
+
super(
|
|
27477
|
+
`cannot lock ${file2} for writing: ${detail}` + (holderPid === void 0 ? "" : ` (held by pid ${String(holderPid)})`),
|
|
27478
|
+
options
|
|
27479
|
+
);
|
|
27480
|
+
this.name = "FileLockError";
|
|
27481
|
+
this.reason = reason;
|
|
27482
|
+
this.file = file2;
|
|
27483
|
+
this.holderPid = holderPid;
|
|
27484
|
+
}
|
|
27485
|
+
};
|
|
27486
|
+
function lockPathFor(file2) {
|
|
27487
|
+
return `${file2}${LOCK_SUFFIX}`;
|
|
27488
|
+
}
|
|
27489
|
+
function readLockBody(lock) {
|
|
27490
|
+
let raw;
|
|
27491
|
+
try {
|
|
27492
|
+
raw = readFileSync2(lock, "utf8");
|
|
27493
|
+
} catch {
|
|
27494
|
+
return null;
|
|
27495
|
+
}
|
|
27496
|
+
try {
|
|
27497
|
+
const parsed = JSON.parse(raw);
|
|
27498
|
+
const { pid, token, at, host } = parsed;
|
|
27499
|
+
if (typeof pid !== "number" || typeof token !== "string" || typeof at !== "number") return null;
|
|
27500
|
+
return { pid, token, at, host: typeof host === "string" ? host : "" };
|
|
27501
|
+
} catch {
|
|
27502
|
+
return null;
|
|
27503
|
+
}
|
|
27504
|
+
}
|
|
27505
|
+
function holderIsAlive(pid) {
|
|
27506
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
27507
|
+
try {
|
|
27508
|
+
process.kill(pid, 0);
|
|
27509
|
+
return true;
|
|
27510
|
+
} catch (err) {
|
|
27511
|
+
return err.code !== "ESRCH";
|
|
27512
|
+
}
|
|
27513
|
+
}
|
|
27514
|
+
function directoryAcceptsCreates(lock) {
|
|
27515
|
+
const probe = `${lock}.probe-${randomUUID11()}`;
|
|
27516
|
+
try {
|
|
27517
|
+
closeSync(openSync(probe, "wx", DATA_FILE_MODE));
|
|
27518
|
+
return true;
|
|
27519
|
+
} catch {
|
|
27520
|
+
return false;
|
|
27521
|
+
} finally {
|
|
27522
|
+
try {
|
|
27523
|
+
rmSync3(probe, { force: true });
|
|
27524
|
+
} catch {
|
|
27525
|
+
}
|
|
27526
|
+
}
|
|
27527
|
+
}
|
|
27528
|
+
function tryAcquire(lock, file2) {
|
|
27529
|
+
const token = randomUUID11();
|
|
27530
|
+
let fd;
|
|
27531
|
+
try {
|
|
27532
|
+
fd = openSync(lock, "wx", DATA_FILE_MODE);
|
|
27533
|
+
} catch (err) {
|
|
27534
|
+
const code = err.code ?? "";
|
|
27535
|
+
if (code === "EEXIST") return null;
|
|
27536
|
+
if (RETRYABLE_CREATE_ERRNOS.has(code) && (existsSync3(lock) || directoryAcceptsCreates(lock))) {
|
|
27537
|
+
return null;
|
|
27538
|
+
}
|
|
27539
|
+
throw new FileLockError(
|
|
27540
|
+
"unavailable",
|
|
27541
|
+
file2,
|
|
27542
|
+
err instanceof Error ? err.message : String(err),
|
|
27543
|
+
void 0,
|
|
27544
|
+
{ cause: err }
|
|
27545
|
+
);
|
|
27546
|
+
}
|
|
27547
|
+
const body = { pid: process.pid, token, at: Date.now(), host: hostname3() };
|
|
27548
|
+
try {
|
|
27549
|
+
writeFileSync2(fd, `${JSON.stringify(body)}
|
|
27550
|
+
`);
|
|
27551
|
+
} catch {
|
|
27552
|
+
try {
|
|
27553
|
+
closeSync(fd);
|
|
27554
|
+
} catch {
|
|
27555
|
+
}
|
|
27556
|
+
rmSync3(lock, { force: true });
|
|
27557
|
+
return null;
|
|
27558
|
+
}
|
|
27559
|
+
try {
|
|
27560
|
+
closeSync(fd);
|
|
27561
|
+
} catch {
|
|
27562
|
+
}
|
|
27563
|
+
return token;
|
|
27564
|
+
}
|
|
27565
|
+
function isAbandoned(body, lock, staleMs) {
|
|
27566
|
+
if (!body) {
|
|
27567
|
+
try {
|
|
27568
|
+
return Date.now() - statSync2(lock).mtimeMs >= staleMs;
|
|
27569
|
+
} catch {
|
|
27570
|
+
return false;
|
|
27571
|
+
}
|
|
27572
|
+
}
|
|
27573
|
+
if (Date.now() - body.at < staleMs) return false;
|
|
27574
|
+
if (body.host !== hostname3() || !holderIsAlive(body.pid)) return true;
|
|
27575
|
+
return Date.now() - body.at >= abandonWindow(staleMs);
|
|
27576
|
+
}
|
|
27577
|
+
function breakIfStale(lock, staleMs) {
|
|
27578
|
+
const breaker = `${lock}.break`;
|
|
27579
|
+
let fd;
|
|
27580
|
+
try {
|
|
27581
|
+
fd = openSync(breaker, "wx", DATA_FILE_MODE);
|
|
27582
|
+
} catch {
|
|
27583
|
+
reapAbandonedBreaker(breaker);
|
|
27584
|
+
return false;
|
|
27585
|
+
}
|
|
27586
|
+
try {
|
|
27587
|
+
closeSync(fd);
|
|
27588
|
+
} catch {
|
|
27589
|
+
}
|
|
27590
|
+
try {
|
|
27591
|
+
if (!existsSync3(lock) || !isAbandoned(readLockBody(lock), lock, staleMs)) return false;
|
|
27592
|
+
rmSync3(lock, { force: true });
|
|
27593
|
+
return true;
|
|
27594
|
+
} catch {
|
|
27595
|
+
return false;
|
|
27596
|
+
} finally {
|
|
27597
|
+
try {
|
|
27598
|
+
rmSync3(breaker, { force: true });
|
|
27599
|
+
} catch {
|
|
27600
|
+
}
|
|
27601
|
+
}
|
|
27602
|
+
}
|
|
27603
|
+
var BREAKER_ABANDONED_MS = 1e4;
|
|
27604
|
+
function reapAbandonedBreaker(breaker) {
|
|
27605
|
+
try {
|
|
27606
|
+
if (Date.now() - statSync2(breaker).mtimeMs >= BREAKER_ABANDONED_MS) {
|
|
27607
|
+
rmSync3(breaker, { force: true });
|
|
27608
|
+
}
|
|
27609
|
+
} catch {
|
|
27610
|
+
}
|
|
27611
|
+
}
|
|
27612
|
+
function abandonWindow(staleMs) {
|
|
27613
|
+
return Math.max(staleMs * 30, 6e4);
|
|
27614
|
+
}
|
|
27615
|
+
function release(lock, token) {
|
|
27616
|
+
try {
|
|
27617
|
+
if (readLockBody(lock)?.token !== token) return;
|
|
27618
|
+
rmSync3(lock, { force: true });
|
|
27619
|
+
} catch {
|
|
27620
|
+
}
|
|
27621
|
+
}
|
|
27622
|
+
function withFileLock(file2, fn, options = {}) {
|
|
27623
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
27624
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
27625
|
+
const lock = lockPathFor(file2);
|
|
27626
|
+
const deadline = Date.now() + timeoutMs;
|
|
27627
|
+
let token = tryAcquire(lock, file2);
|
|
27628
|
+
while (token === null) {
|
|
27629
|
+
if (breakIfStale(lock, staleMs)) {
|
|
27630
|
+
token = tryAcquire(lock, file2);
|
|
27631
|
+
continue;
|
|
27632
|
+
}
|
|
27633
|
+
if (Date.now() >= deadline) {
|
|
27634
|
+
throw new FileLockError(
|
|
27635
|
+
"timeout",
|
|
27636
|
+
file2,
|
|
27637
|
+
`still held after ${String(timeoutMs)}ms`,
|
|
27638
|
+
readLockBody(lock)?.pid
|
|
27639
|
+
);
|
|
27640
|
+
}
|
|
27641
|
+
sleepSync(RETRY_INTERVAL_MS);
|
|
27642
|
+
token = tryAcquire(lock, file2);
|
|
27643
|
+
}
|
|
27644
|
+
try {
|
|
27645
|
+
const result = fn();
|
|
27646
|
+
if (isThenable(result)) {
|
|
27647
|
+
void result.then(
|
|
27648
|
+
() => void 0,
|
|
27649
|
+
() => void 0
|
|
27650
|
+
);
|
|
27651
|
+
throw new TypeError(
|
|
27652
|
+
`withFileLock(${file2}) was given an async body; the lock is released as soon as it returns, so the awaited work would run unguarded. Pass a synchronous function.`
|
|
27653
|
+
);
|
|
27654
|
+
}
|
|
27655
|
+
return result;
|
|
27656
|
+
} finally {
|
|
27657
|
+
release(lock, token);
|
|
27658
|
+
}
|
|
27659
|
+
}
|
|
27660
|
+
function isThenable(value) {
|
|
27661
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
27662
|
+
}
|
|
27663
|
+
|
|
26292
27664
|
// ../packages/persistence/src/finding-key.ts
|
|
26293
27665
|
import { createHash as createHash3 } from "crypto";
|
|
26294
27666
|
function normalizeFilePath(filePath) {
|
|
@@ -26301,13 +27673,13 @@ function computeFindingKey(input) {
|
|
|
26301
27673
|
|
|
26302
27674
|
// ../packages/persistence/src/fingerprint.ts
|
|
26303
27675
|
import { createHmac, randomBytes } from "crypto";
|
|
26304
|
-
import { existsSync as
|
|
26305
|
-
import { join as
|
|
27676
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
27677
|
+
import { join as join4 } from "path";
|
|
26306
27678
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
26307
|
-
var
|
|
27679
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
26308
27680
|
var KEY_MATERIAL_BYTES = 32;
|
|
26309
27681
|
function keyFilePath(dataDir2) {
|
|
26310
|
-
return
|
|
27682
|
+
return join4(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
26311
27683
|
}
|
|
26312
27684
|
function parseKeyFile(raw) {
|
|
26313
27685
|
const parsed = JSON.parse(raw);
|
|
@@ -26345,8 +27717,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
26345
27717
|
}
|
|
26346
27718
|
};
|
|
26347
27719
|
function storedKeyVersionFloor(dataDir2) {
|
|
26348
|
-
const file2 =
|
|
26349
|
-
if (!
|
|
27720
|
+
const file2 = join4(dataDir2, DB_FILENAME);
|
|
27721
|
+
if (!existsSync4(file2)) return 0;
|
|
26350
27722
|
let db;
|
|
26351
27723
|
try {
|
|
26352
27724
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -26371,18 +27743,42 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
26371
27743
|
db?.close();
|
|
26372
27744
|
}
|
|
26373
27745
|
}
|
|
27746
|
+
function serializeKey(key) {
|
|
27747
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
27748
|
+
}
|
|
26374
27749
|
function writeKeyFile(dataDir2, key) {
|
|
26375
27750
|
ensureDataDirSync(dataDir2);
|
|
26376
|
-
|
|
26377
|
-
const body = JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
26378
|
-
writeOwnerOnlyFileSync(file2, `${body}
|
|
27751
|
+
writeOwnerOnlyFileSync(keyFilePath(dataDir2), `${serializeKey(key)}
|
|
26379
27752
|
`);
|
|
26380
27753
|
return key;
|
|
26381
27754
|
}
|
|
27755
|
+
function createKeyFile(dataDir2, key) {
|
|
27756
|
+
ensureDataDirSync(dataDir2);
|
|
27757
|
+
const file2 = keyFilePath(dataDir2);
|
|
27758
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
27759
|
+
`)) return key;
|
|
27760
|
+
const winner = readFingerprintKey(dataDir2);
|
|
27761
|
+
if (winner) {
|
|
27762
|
+
tightenFile(file2);
|
|
27763
|
+
return winner;
|
|
27764
|
+
}
|
|
27765
|
+
const occupant = classifyOccupant(file2);
|
|
27766
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
27767
|
+
}
|
|
27768
|
+
function occupantMessage(file2, kind) {
|
|
27769
|
+
switch (kind) {
|
|
27770
|
+
case "symlink":
|
|
27771
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
27772
|
+
case "gone":
|
|
27773
|
+
return "exception key file was removed while it was being created";
|
|
27774
|
+
case "unknown":
|
|
27775
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
27776
|
+
}
|
|
27777
|
+
}
|
|
26382
27778
|
function readFingerprintKey(dataDir2) {
|
|
26383
27779
|
let raw;
|
|
26384
27780
|
try {
|
|
26385
|
-
raw =
|
|
27781
|
+
raw = readFileSync3(keyFilePath(dataDir2), "utf8");
|
|
26386
27782
|
} catch (err) {
|
|
26387
27783
|
if (err.code === "ENOENT") return null;
|
|
26388
27784
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26395,7 +27791,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
26395
27791
|
tightenFile(keyFilePath(dataDir2));
|
|
26396
27792
|
return existing;
|
|
26397
27793
|
}
|
|
26398
|
-
return
|
|
27794
|
+
return createKeyFile(dataDir2, {
|
|
26399
27795
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
26400
27796
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
26401
27797
|
});
|
|
@@ -26421,31 +27817,32 @@ function keyStateOf(key) {
|
|
|
26421
27817
|
import { renameSync as renameSync3 } from "fs";
|
|
26422
27818
|
import { mkdir } from "fs/promises";
|
|
26423
27819
|
import { homedir as homedir3 } from "os";
|
|
26424
|
-
import { join as
|
|
27820
|
+
import { join as join5 } from "path";
|
|
26425
27821
|
function defaultDataDir() {
|
|
26426
|
-
return
|
|
27822
|
+
return join5(homedir3(), ".aka");
|
|
26427
27823
|
}
|
|
26428
27824
|
function settingsDir(base = defaultDataDir()) {
|
|
26429
|
-
return
|
|
27825
|
+
return join5(base, "settings");
|
|
26430
27826
|
}
|
|
26431
27827
|
function dataDir(base = defaultDataDir()) {
|
|
26432
|
-
return
|
|
27828
|
+
return join5(base, "data");
|
|
26433
27829
|
}
|
|
26434
27830
|
function dbPath(base = defaultDataDir()) {
|
|
26435
|
-
return
|
|
27831
|
+
return join5(dataDir(base), "aka.db");
|
|
26436
27832
|
}
|
|
26437
27833
|
function keysDir(base = defaultDataDir()) {
|
|
26438
|
-
return
|
|
27834
|
+
return join5(base, "keys");
|
|
26439
27835
|
}
|
|
26440
27836
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
26441
27837
|
ensureDataDirSync(dir);
|
|
26442
27838
|
}
|
|
26443
27839
|
|
|
26444
27840
|
// ../packages/persistence/src/settings.ts
|
|
26445
|
-
import { readFileSync as
|
|
26446
|
-
import { join as
|
|
27841
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
27842
|
+
import { join as join6 } from "path";
|
|
27843
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
26447
27844
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
26448
|
-
const record2 = readJson(
|
|
27845
|
+
const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
|
|
26449
27846
|
if (!record2) return defaultWorkspaceSettings();
|
|
26450
27847
|
try {
|
|
26451
27848
|
return WorkspaceSettings.parse(record2);
|
|
@@ -26456,7 +27853,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
26456
27853
|
function readJson(file2) {
|
|
26457
27854
|
let text;
|
|
26458
27855
|
try {
|
|
26459
|
-
text =
|
|
27856
|
+
text = readFileSync4(file2, "utf8");
|
|
26460
27857
|
} catch {
|
|
26461
27858
|
return null;
|
|
26462
27859
|
}
|
|
@@ -26577,13 +27974,18 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
26577
27974
|
import {
|
|
26578
27975
|
chmodSync as chmodSync2,
|
|
26579
27976
|
mkdirSync as mkdirSync2,
|
|
26580
|
-
readFileSync as
|
|
27977
|
+
readFileSync as readFileSync5,
|
|
26581
27978
|
renameSync as renameSync4,
|
|
26582
|
-
rmSync as
|
|
26583
|
-
statSync,
|
|
26584
|
-
writeFileSync as
|
|
27979
|
+
rmSync as rmSync4,
|
|
27980
|
+
statSync as statSync3,
|
|
27981
|
+
writeFileSync as writeFileSync3
|
|
26585
27982
|
} from "fs";
|
|
26586
|
-
import { join as
|
|
27983
|
+
import { join as join7 } from "path";
|
|
27984
|
+
var VAULT_OCCUPANT_REASON = {
|
|
27985
|
+
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
27986
|
+
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
27987
|
+
unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
|
|
27988
|
+
};
|
|
26587
27989
|
var VaultKeyEpochMissingError = class extends Error {
|
|
26588
27990
|
version;
|
|
26589
27991
|
constructor(version2) {
|
|
@@ -26676,28 +28078,28 @@ function claimRotationLock(lock, owner) {
|
|
|
26676
28078
|
throw asError(err);
|
|
26677
28079
|
}
|
|
26678
28080
|
try {
|
|
26679
|
-
|
|
28081
|
+
writeFileSync3(join7(lock, LOCK_OWNER_FILE), `${owner}
|
|
26680
28082
|
`, { mode: DATA_FILE_MODE });
|
|
26681
28083
|
return true;
|
|
26682
28084
|
} catch (err) {
|
|
26683
|
-
|
|
28085
|
+
rmSync4(lock, { recursive: true, force: true });
|
|
26684
28086
|
throw asError(err);
|
|
26685
28087
|
}
|
|
26686
28088
|
}
|
|
26687
28089
|
function acquireRotationLock(keysDir2) {
|
|
26688
|
-
const lock =
|
|
28090
|
+
const lock = join7(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
26689
28091
|
const owner = randomBytes2(16).toString("hex");
|
|
26690
28092
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
26691
28093
|
let held;
|
|
26692
28094
|
try {
|
|
26693
|
-
held =
|
|
28095
|
+
held = statSync3(lock);
|
|
26694
28096
|
} catch {
|
|
26695
28097
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26696
28098
|
}
|
|
26697
28099
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
26698
28100
|
const aside = `${lock}.stale.${owner}`;
|
|
26699
28101
|
try {
|
|
26700
|
-
const now =
|
|
28102
|
+
const now = statSync3(lock);
|
|
26701
28103
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
26702
28104
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26703
28105
|
}
|
|
@@ -26706,17 +28108,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
26706
28108
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
26707
28109
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
26708
28110
|
}
|
|
26709
|
-
|
|
28111
|
+
rmSync4(aside, { recursive: true, force: true });
|
|
26710
28112
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
26711
28113
|
return { lock, owner };
|
|
26712
28114
|
}
|
|
26713
28115
|
function releaseRotationLock(lease) {
|
|
26714
28116
|
try {
|
|
26715
|
-
if (
|
|
28117
|
+
if (readFileSync5(join7(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
26716
28118
|
} catch {
|
|
26717
28119
|
return;
|
|
26718
28120
|
}
|
|
26719
|
-
|
|
28121
|
+
rmSync4(lease.lock, { recursive: true, force: true });
|
|
26720
28122
|
}
|
|
26721
28123
|
function withRotationLock(keysDir2, work) {
|
|
26722
28124
|
ensureDataDirSync(keysDir2);
|
|
@@ -26733,7 +28135,7 @@ var FileKeyProvider = class {
|
|
|
26733
28135
|
this.#keysDir = keysDir2;
|
|
26734
28136
|
}
|
|
26735
28137
|
get filePath() {
|
|
26736
|
-
return
|
|
28138
|
+
return join7(this.#keysDir, VAULT_KEY_FILENAME);
|
|
26737
28139
|
}
|
|
26738
28140
|
loadOrCreate() {
|
|
26739
28141
|
return asAsync(() => {
|
|
@@ -26763,7 +28165,7 @@ var FileKeyProvider = class {
|
|
|
26763
28165
|
#read() {
|
|
26764
28166
|
let raw;
|
|
26765
28167
|
try {
|
|
26766
|
-
raw =
|
|
28168
|
+
raw = readFileSync5(this.filePath, "utf8");
|
|
26767
28169
|
} catch (err) {
|
|
26768
28170
|
if (err.code === "ENOENT") return null;
|
|
26769
28171
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26771,34 +28173,32 @@ var FileKeyProvider = class {
|
|
|
26771
28173
|
return parseKeyring(raw);
|
|
26772
28174
|
}
|
|
26773
28175
|
/**
|
|
26774
|
-
* First mint: the keyring is
|
|
26775
|
-
*
|
|
26776
|
-
*
|
|
26777
|
-
*
|
|
26778
|
-
*
|
|
26779
|
-
*
|
|
26780
|
-
*
|
|
26781
|
-
*
|
|
28176
|
+
* First mint: the keyring is CREATED, never replaced, so two processes racing
|
|
28177
|
+
* a fresh machine cannot each mint a different epoch 1 — with tmp + rename
|
|
28178
|
+
* the loser's replace would orphan everything the winner had already sealed.
|
|
28179
|
+
* The loser re-reads and adopts the winner's keyring; it minted nothing.
|
|
28180
|
+
*
|
|
28181
|
+
* `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
|
|
28182
|
+
* at the final path, so the keyring never exists at zero length: a reader —
|
|
28183
|
+
* including the loser, re-reading in order to adopt — sees the file absent or
|
|
28184
|
+
* whole, and never mistakes a live keyring for a corrupt one. A corrupt file
|
|
28185
|
+
* still throws from the parse and is never re-minted over.
|
|
26782
28186
|
*/
|
|
26783
28187
|
#createExclusive() {
|
|
26784
28188
|
ensureDataDirSync(this.#keysDir);
|
|
26785
28189
|
const keyring = mintKeyring();
|
|
26786
|
-
|
|
26787
|
-
|
|
26788
|
-
|
|
26789
|
-
|
|
26790
|
-
|
|
26791
|
-
|
|
26792
|
-
|
|
26793
|
-
|
|
26794
|
-
|
|
26795
|
-
if (!winner) {
|
|
26796
|
-
throw new Error("vault: key file vanished during first mint", { cause: err });
|
|
26797
|
-
}
|
|
26798
|
-
return winner;
|
|
28190
|
+
if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
28191
|
+
`)) return keyring;
|
|
28192
|
+
const winner = this.#read();
|
|
28193
|
+
if (!winner) {
|
|
28194
|
+
const occupant = classifyOccupant(this.filePath);
|
|
28195
|
+
throw new KeyUnclaimableError(
|
|
28196
|
+
`vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
|
|
28197
|
+
occupant.cause
|
|
28198
|
+
);
|
|
26799
28199
|
}
|
|
26800
28200
|
tightenFileMode(this.filePath);
|
|
26801
|
-
return
|
|
28201
|
+
return winner;
|
|
26802
28202
|
}
|
|
26803
28203
|
/**
|
|
26804
28204
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
@@ -26809,7 +28209,7 @@ var FileKeyProvider = class {
|
|
|
26809
28209
|
ensureDataDirSync(this.#keysDir);
|
|
26810
28210
|
const file2 = this.filePath;
|
|
26811
28211
|
const tmp = `${file2}.tmp`;
|
|
26812
|
-
|
|
28212
|
+
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
26813
28213
|
`, { mode: DATA_FILE_MODE });
|
|
26814
28214
|
renameSync4(tmp, file2);
|
|
26815
28215
|
tightenFileMode(file2);
|
|
@@ -26935,7 +28335,7 @@ function createKeyProvider(custody, keysDir2) {
|
|
|
26935
28335
|
}
|
|
26936
28336
|
|
|
26937
28337
|
// ../packages/persistence/src/vault/vault.ts
|
|
26938
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
28338
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
26939
28339
|
var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
|
|
26940
28340
|
var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
|
|
26941
28341
|
var VAULT_PURGE_POINTER_ID = "*";
|
|
@@ -26962,14 +28362,12 @@ function parsePointer(token) {
|
|
|
26962
28362
|
var SecretVault = class {
|
|
26963
28363
|
#repo;
|
|
26964
28364
|
#keys;
|
|
26965
|
-
#fingerprintKey;
|
|
26966
28365
|
#isConsented;
|
|
26967
28366
|
#verifyGrant;
|
|
26968
28367
|
#now;
|
|
26969
28368
|
constructor(deps) {
|
|
26970
28369
|
this.#repo = deps.repo;
|
|
26971
28370
|
this.#keys = deps.keys;
|
|
26972
|
-
this.#fingerprintKey = deps.fingerprintKey;
|
|
26973
28371
|
this.#isConsented = deps.isConsented;
|
|
26974
28372
|
this.#verifyGrant = deps.verifyGrant;
|
|
26975
28373
|
this.#now = deps.now ?? (() => Date.now());
|
|
@@ -26978,10 +28376,23 @@ var SecretVault = class {
|
|
|
26978
28376
|
* Store a value and return the pointer that stands for it. The same value
|
|
26979
28377
|
* always yields the same pointer on this machine — one row, one pointer id,
|
|
26980
28378
|
* one category — which is what makes dedup and reuse counting work.
|
|
28379
|
+
*
|
|
28380
|
+
* `fingerprintKey` is the exception-key epoch this value's fingerprint is
|
|
28381
|
+
* derived under — a different key from the vault's, with different rotation
|
|
28382
|
+
* semantics. It is a parameter of the WRITE rather than a constructor dep,
|
|
28383
|
+
* and a thunk rather than a value, so that the only way to reach a key is to
|
|
28384
|
+
* store something: a read-only caller never names it, and a caller whose
|
|
28385
|
+
* source mints on absence mints only once consent has actually opened the
|
|
28386
|
+
* write. `refreshFingerprints` takes its key the same way, for the same
|
|
28387
|
+
* reason.
|
|
28388
|
+
*
|
|
28389
|
+
* Resolved once per call, so the fingerprint and the version it is recorded
|
|
28390
|
+
* under can never come from two different epochs.
|
|
26981
28391
|
*/
|
|
26982
|
-
async tokenize(raw, meta3) {
|
|
28392
|
+
async tokenize(raw, meta3, fingerprintKey) {
|
|
26983
28393
|
if (!this.#isConsented()) return CONSENT_ABSENT;
|
|
26984
|
-
const
|
|
28394
|
+
const fpKey = fingerprintKey();
|
|
28395
|
+
const valueFingerprint = fingerprintValue(fpKey, raw);
|
|
26985
28396
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
26986
28397
|
const now = this.#now();
|
|
26987
28398
|
if (existing) {
|
|
@@ -26997,7 +28408,7 @@ var SecretVault = class {
|
|
|
26997
28408
|
{
|
|
26998
28409
|
pointerId: base32Encode(pointerId),
|
|
26999
28410
|
valueFingerprint,
|
|
27000
|
-
fingerprintKeyVersion:
|
|
28411
|
+
fingerprintKeyVersion: fpKey.version,
|
|
27001
28412
|
keyVersion: version2,
|
|
27002
28413
|
// Recorded so the row stays OPENABLE if the wire-format constant ever
|
|
27003
28414
|
// moves: it is part of this row's AEAD AAD. It is not a tag input —
|
|
@@ -27241,7 +28652,7 @@ var SecretVault = class {
|
|
|
27241
28652
|
purgeVault() {
|
|
27242
28653
|
const destroyed = this.#repo.purgeAll();
|
|
27243
28654
|
this.#repo.recordDeref({
|
|
27244
|
-
id:
|
|
28655
|
+
id: randomUUID12(),
|
|
27245
28656
|
pointerId: VAULT_PURGE_POINTER_ID,
|
|
27246
28657
|
at: this.#now(),
|
|
27247
28658
|
target: "human",
|
|
@@ -27310,7 +28721,7 @@ var SecretVault = class {
|
|
|
27310
28721
|
}
|
|
27311
28722
|
#audit(pointerId, opts, outcome) {
|
|
27312
28723
|
this.#repo.recordDeref({
|
|
27313
|
-
id:
|
|
28724
|
+
id: randomUUID12(),
|
|
27314
28725
|
pointerId,
|
|
27315
28726
|
at: this.#now(),
|
|
27316
28727
|
target: opts.target,
|
|
@@ -27325,12 +28736,12 @@ var SecretVault = class {
|
|
|
27325
28736
|
};
|
|
27326
28737
|
|
|
27327
28738
|
// ../packages/persistence/src/warn-era-cap.ts
|
|
27328
|
-
import { existsSync as
|
|
27329
|
-
import { join as
|
|
28739
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
28740
|
+
import { join as join8 } from "path";
|
|
27330
28741
|
|
|
27331
28742
|
// ../packages/plugin-sdk/src/config.ts
|
|
27332
|
-
import { existsSync as
|
|
27333
|
-
import { join as
|
|
28743
|
+
import { existsSync as existsSync6 } from "fs";
|
|
28744
|
+
import { join as join9 } from "path";
|
|
27334
28745
|
|
|
27335
28746
|
// ../packages/plugin-sdk/src/provider-env.ts
|
|
27336
28747
|
var booleanish = external_exports.string().optional().transform((v) => {
|
|
@@ -27351,9 +28762,9 @@ var providerEnvShape = {
|
|
|
27351
28762
|
var ProviderEnvSchema = external_exports.object(providerEnvShape);
|
|
27352
28763
|
|
|
27353
28764
|
// ../packages/plugin-sdk/src/config-inventory.ts
|
|
27354
|
-
import { readdirSync, readFileSync as
|
|
28765
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
|
|
27355
28766
|
import { homedir as homedir4 } from "os";
|
|
27356
|
-
import { basename as
|
|
28767
|
+
import { basename as basename3, join as join11 } from "path";
|
|
27357
28768
|
|
|
27358
28769
|
// ../rules/code-flaws/auth-jwt-no-verify.json
|
|
27359
28770
|
var auth_jwt_no_verify_default = {
|
|
@@ -29009,7 +30420,7 @@ var gcp_service_account_default = {
|
|
|
29009
30420
|
severity: "critical",
|
|
29010
30421
|
matcher: {
|
|
29011
30422
|
type: "regex",
|
|
29012
|
-
pattern: "
|
|
30423
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
29013
30424
|
flags: "g"
|
|
29014
30425
|
},
|
|
29015
30426
|
examples: [
|
|
@@ -29388,8 +30799,8 @@ function bundledDetections() {
|
|
|
29388
30799
|
}
|
|
29389
30800
|
|
|
29390
30801
|
// ../packages/plugin-sdk/src/repo.ts
|
|
29391
|
-
import { existsSync as
|
|
29392
|
-
import { basename, dirname as
|
|
30802
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
30803
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join10, sep as sep2 } from "path";
|
|
29393
30804
|
function resolveRepoIdentity(cwd) {
|
|
29394
30805
|
try {
|
|
29395
30806
|
const root = findGitRoot(cwd);
|
|
@@ -29402,7 +30813,7 @@ function resolveRepoIdentity(cwd) {
|
|
|
29402
30813
|
// win32) so the persistence layer's `/`-separated checkout-path patterns
|
|
29403
30814
|
// (the ghost sweep + the read-side worktree filter) match it as written.
|
|
29404
30815
|
url: url2 ?? headRoot.split(sep2).join("/"),
|
|
29405
|
-
name: (url2 ? slugFromUrl(url2) : void 0) ??
|
|
30816
|
+
name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
|
|
29406
30817
|
};
|
|
29407
30818
|
} catch {
|
|
29408
30819
|
return void 0;
|
|
@@ -29427,36 +30838,36 @@ function resolveHeadRoot(cwd) {
|
|
|
29427
30838
|
function findGitRoot(start) {
|
|
29428
30839
|
let dir = start;
|
|
29429
30840
|
for (; ; ) {
|
|
29430
|
-
if (
|
|
29431
|
-
const parent =
|
|
30841
|
+
if (existsSync7(join10(dir, ".git"))) return dir;
|
|
30842
|
+
const parent = dirname3(dir);
|
|
29432
30843
|
if (parent === dir) return void 0;
|
|
29433
30844
|
dir = parent;
|
|
29434
30845
|
}
|
|
29435
30846
|
}
|
|
29436
30847
|
function resolveGitContext(root) {
|
|
29437
|
-
const dotGit =
|
|
30848
|
+
const dotGit = join10(root, ".git");
|
|
29438
30849
|
try {
|
|
29439
|
-
if (
|
|
29440
|
-
return { configPath:
|
|
30850
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
30851
|
+
return { configPath: join10(dotGit, "config"), headRoot: root };
|
|
29441
30852
|
}
|
|
29442
30853
|
} catch {
|
|
29443
30854
|
return void 0;
|
|
29444
30855
|
}
|
|
29445
30856
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
29446
30857
|
if (!target) return void 0;
|
|
29447
|
-
const gitdir = isAbsolute(target) ? target :
|
|
29448
|
-
if (
|
|
29449
|
-
return { configPath:
|
|
30858
|
+
const gitdir = isAbsolute(target) ? target : join10(root, target);
|
|
30859
|
+
if (existsSync7(join10(gitdir, "config"))) {
|
|
30860
|
+
return { configPath: join10(gitdir, "config"), headRoot: root };
|
|
29450
30861
|
}
|
|
29451
|
-
const commonRaw = safeRead(
|
|
30862
|
+
const commonRaw = safeRead(join10(gitdir, "commondir"))?.trim();
|
|
29452
30863
|
if (!commonRaw) return void 0;
|
|
29453
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
29454
|
-
const headRoot =
|
|
29455
|
-
return { configPath:
|
|
30864
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join10(gitdir, commonRaw);
|
|
30865
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
|
|
30866
|
+
return { configPath: join10(commonGitDir, "config"), headRoot };
|
|
29456
30867
|
}
|
|
29457
30868
|
function safeRead(path) {
|
|
29458
30869
|
try {
|
|
29459
|
-
return
|
|
30870
|
+
return readFileSync6(path, "utf8");
|
|
29460
30871
|
} catch {
|
|
29461
30872
|
return void 0;
|
|
29462
30873
|
}
|
|
@@ -29494,18 +30905,23 @@ function slugFromUrl(url2) {
|
|
|
29494
30905
|
}
|
|
29495
30906
|
|
|
29496
30907
|
// ../packages/plugin-sdk/src/events.ts
|
|
29497
|
-
import { createHash as createHash4, randomUUID as
|
|
30908
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
30909
|
+
|
|
30910
|
+
// ../packages/plugin-sdk/src/isolated-scan.ts
|
|
30911
|
+
import { existsSync as existsSync8 } from "fs";
|
|
30912
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30913
|
+
import { Worker } from "worker_threads";
|
|
29498
30914
|
|
|
29499
30915
|
// ../packages/plugin-sdk/src/inventory-resolver.ts
|
|
29500
|
-
import { arch, hostname as
|
|
30916
|
+
import { arch, hostname as hostname4, platform, release as release2 } from "os";
|
|
29501
30917
|
|
|
29502
30918
|
// ../packages/plugin-sdk/src/nudge.ts
|
|
29503
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
29504
|
-
import { join as
|
|
30919
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
30920
|
+
import { join as join12 } from "path";
|
|
29505
30921
|
|
|
29506
30922
|
// ../packages/plugin-sdk/src/paths.ts
|
|
29507
|
-
import { readdirSync as
|
|
29508
|
-
import { basename as
|
|
30923
|
+
import { readdirSync as readdirSync4, realpathSync as realpathSync2 } from "fs";
|
|
30924
|
+
import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
|
|
29509
30925
|
function toPosix(path) {
|
|
29510
30926
|
return path.split(sep3).join("/");
|
|
29511
30927
|
}
|
|
@@ -29515,7 +30931,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
29515
30931
|
let root = null;
|
|
29516
30932
|
for (let level = 0; level < MAX_PROJECT_ROOT_LEVELS; level += 1) {
|
|
29517
30933
|
if (directoryHasMarker(dir, recognizeMarker)) root = dir;
|
|
29518
|
-
const parent =
|
|
30934
|
+
const parent = dirname4(dir);
|
|
29519
30935
|
if (parent === dir) break;
|
|
29520
30936
|
dir = parent;
|
|
29521
30937
|
}
|
|
@@ -29523,7 +30939,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
29523
30939
|
}
|
|
29524
30940
|
function directoryHasMarker(dir, recognizeMarker) {
|
|
29525
30941
|
try {
|
|
29526
|
-
for (const entry of
|
|
30942
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
29527
30943
|
if (entry.isFile() && recognizeMarker(entry.name) != null) return true;
|
|
29528
30944
|
}
|
|
29529
30945
|
} catch {
|
|
@@ -29534,13 +30950,13 @@ function directoryHasMarker(dir, recognizeMarker) {
|
|
|
29534
30950
|
function resolveNonGitProject(startDir, recognizeMarker) {
|
|
29535
30951
|
const projectRoot = findProjectRoot(startDir, recognizeMarker);
|
|
29536
30952
|
const realRoot = realpathSync2(projectRoot);
|
|
29537
|
-
return { root: projectRoot, projectKey: `path:${realRoot}`, project:
|
|
30953
|
+
return { root: projectRoot, projectKey: `path:${realRoot}`, project: basename4(realRoot) };
|
|
29538
30954
|
}
|
|
29539
30955
|
|
|
29540
30956
|
// ../packages/plugin-sdk/src/project-files.ts
|
|
29541
30957
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
29542
|
-
import { existsSync as
|
|
29543
|
-
import { basename as
|
|
30958
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
|
|
30959
|
+
import { basename as basename5, join as join13, relative, sep as sep4 } from "path";
|
|
29544
30960
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
29545
30961
|
".git",
|
|
29546
30962
|
"node_modules",
|
|
@@ -29560,7 +30976,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
29560
30976
|
var MAX_FILES = 2e4;
|
|
29561
30977
|
function readIgnoreLayer(dir) {
|
|
29562
30978
|
try {
|
|
29563
|
-
const content =
|
|
30979
|
+
const content = readFileSync9(join13(dir, ".gitignore"), "utf8");
|
|
29564
30980
|
return { base: dir, matcher: (0, import_ignore.default)().add(content) };
|
|
29565
30981
|
} catch {
|
|
29566
30982
|
return void 0;
|
|
@@ -29630,7 +31046,7 @@ function resolveProjectFiles(cwd) {
|
|
|
29630
31046
|
let visit3 = function(dir, layers) {
|
|
29631
31047
|
let dirents;
|
|
29632
31048
|
try {
|
|
29633
|
-
dirents =
|
|
31049
|
+
dirents = readdirSync5(dir, { withFileTypes: true, encoding: "utf8" });
|
|
29634
31050
|
} catch {
|
|
29635
31051
|
walk.lostSubtree = true;
|
|
29636
31052
|
return false;
|
|
@@ -29638,10 +31054,10 @@ function resolveProjectFiles(cwd) {
|
|
|
29638
31054
|
const layer = readIgnoreLayer(dir);
|
|
29639
31055
|
const dirLayers = layer ? [...layers, layer] : layers;
|
|
29640
31056
|
for (const entry of dirents) {
|
|
29641
|
-
const fullPath =
|
|
31057
|
+
const fullPath = join13(dir, entry.name);
|
|
29642
31058
|
if (entry.isDirectory()) {
|
|
29643
31059
|
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, fullPath, true)) continue;
|
|
29644
|
-
if (
|
|
31060
|
+
if (existsSync9(join13(fullPath, ".git"))) continue;
|
|
29645
31061
|
if (visit3(fullPath, dirLayers)) return true;
|
|
29646
31062
|
continue;
|
|
29647
31063
|
}
|
|
@@ -29652,7 +31068,7 @@ function resolveProjectFiles(cwd) {
|
|
|
29652
31068
|
const relPath = relative(root, fullPath).split(sep4).join("/");
|
|
29653
31069
|
files.push({
|
|
29654
31070
|
path: relPath,
|
|
29655
|
-
name:
|
|
31071
|
+
name: basename5(entry.name),
|
|
29656
31072
|
origin: classifyOrigin(relPath, entry.name),
|
|
29657
31073
|
defaultAccess: "approved"
|
|
29658
31074
|
});
|
|
@@ -29675,22 +31091,48 @@ function resolveProjectFiles(cwd) {
|
|
|
29675
31091
|
}
|
|
29676
31092
|
}
|
|
29677
31093
|
|
|
31094
|
+
// ../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
31095
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
31096
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
31097
|
+
return v;
|
|
31098
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
31099
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
31100
|
+
if (typeof v !== "string") return false;
|
|
31101
|
+
const normalized = v.trim().toLowerCase();
|
|
31102
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
31103
|
+
}, external_exports.boolean()).catch(false);
|
|
31104
|
+
var antigravityProviderEnvShape = {
|
|
31105
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
31106
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
31107
|
+
};
|
|
31108
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
31109
|
+
|
|
31110
|
+
// ../packages/plugin-sdk/src/provider-env-codex.ts
|
|
31111
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
31112
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
31113
|
+
return v;
|
|
31114
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
31115
|
+
var codexProviderEnvShape = {
|
|
31116
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
31117
|
+
};
|
|
31118
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
31119
|
+
|
|
29678
31120
|
// ../packages/plugin-sdk/src/runtime.ts
|
|
29679
|
-
import { randomUUID as
|
|
31121
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
29680
31122
|
|
|
29681
31123
|
// ../packages/plugin-sdk/src/suppressions.ts
|
|
29682
31124
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
29683
31125
|
|
|
29684
31126
|
// ../packages/plugin-sdk/src/throttle.ts
|
|
29685
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
29686
|
-
import { join as
|
|
31127
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
31128
|
+
import { join as join14 } from "path";
|
|
29687
31129
|
|
|
29688
31130
|
// ../packages/local-ops/src/egress-record.ts
|
|
29689
31131
|
function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
29690
31132
|
try {
|
|
29691
31133
|
if (!readWorkspaceSettings(base).dataSharesInPlace) return null;
|
|
29692
31134
|
const abs = resolve(target);
|
|
29693
|
-
const targetDir =
|
|
31135
|
+
const targetDir = statSync7(abs).isDirectory() ? abs : dirname5(abs);
|
|
29694
31136
|
const identity = resolveRepoIdentity(abs);
|
|
29695
31137
|
const worktreeRoot = resolveWorktreeRoot(abs);
|
|
29696
31138
|
let root;
|
|
@@ -29731,9 +31173,9 @@ function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
|
29731
31173
|
}
|
|
29732
31174
|
|
|
29733
31175
|
// ../packages/local-ops/src/fs-scan.ts
|
|
29734
|
-
import { createHash as createHash5, randomUUID as
|
|
29735
|
-
import { readdirSync as
|
|
29736
|
-
import { basename as
|
|
31176
|
+
import { createHash as createHash5, randomUUID as randomUUID15 } from "crypto";
|
|
31177
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync8 } from "fs";
|
|
31178
|
+
import { basename as basename6, extname, join as join15, relative as relative3, resolve as resolve2 } from "path";
|
|
29737
31179
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
29738
31180
|
var AKAIGNORE_FILENAME = ".akaignore";
|
|
29739
31181
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
@@ -29749,7 +31191,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
29749
31191
|
var MAX_BYTES = 1e6;
|
|
29750
31192
|
function readIgnoreLayer2(dir, filename) {
|
|
29751
31193
|
try {
|
|
29752
|
-
const content =
|
|
31194
|
+
const content = readFileSync10(join15(dir, filename), "utf8");
|
|
29753
31195
|
return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
|
|
29754
31196
|
} catch {
|
|
29755
31197
|
return void 0;
|
|
@@ -29768,7 +31210,7 @@ function evaluate(layers, absPath, isDir) {
|
|
|
29768
31210
|
function* collectFiles(target) {
|
|
29769
31211
|
let st;
|
|
29770
31212
|
try {
|
|
29771
|
-
st =
|
|
31213
|
+
st = statSync8(target);
|
|
29772
31214
|
} catch {
|
|
29773
31215
|
return;
|
|
29774
31216
|
}
|
|
@@ -29784,8 +31226,8 @@ function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
|
29784
31226
|
const dirMarkLayers = markLayer ? [...markLayers, markLayer] : markLayers;
|
|
29785
31227
|
const skipLayer = readIgnoreLayer2(dir, AKAIGNORE_FILENAME);
|
|
29786
31228
|
const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
|
|
29787
|
-
for (const entry of
|
|
29788
|
-
const path =
|
|
31229
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
31230
|
+
const path = join15(dir, entry.name);
|
|
29789
31231
|
if (entry.isDirectory()) {
|
|
29790
31232
|
const skipState = evaluate(dirSkipLayers, path, true);
|
|
29791
31233
|
if (skipState !== "unignored" && (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".") || skipState === "ignored")) {
|
|
@@ -29797,7 +31239,7 @@ function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
|
29797
31239
|
if (evaluate(dirSkipLayers, path, false) === "ignored") continue;
|
|
29798
31240
|
let size;
|
|
29799
31241
|
try {
|
|
29800
|
-
size =
|
|
31242
|
+
size = statSync8(path).size;
|
|
29801
31243
|
} catch {
|
|
29802
31244
|
continue;
|
|
29803
31245
|
}
|
|
@@ -29811,7 +31253,7 @@ function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
|
29811
31253
|
}
|
|
29812
31254
|
function extractFileEgress(file2, text) {
|
|
29813
31255
|
if (text.includes("\0")) return null;
|
|
29814
|
-
const name =
|
|
31256
|
+
const name = basename6(file2);
|
|
29815
31257
|
if (LOCKFILE_BASENAMES.has(name)) return null;
|
|
29816
31258
|
const kind = manifestKindOf(name);
|
|
29817
31259
|
const sdkHits = kind === null ? [] : extractManifestSdks(text, kind);
|
|
@@ -29819,7 +31261,8 @@ function extractFileEgress(file2, text) {
|
|
|
29819
31261
|
if (endpoints.length === 0 && sdkHits.length === 0) return null;
|
|
29820
31262
|
return { file: file2, vendored: isVendoredPath(toPosix(file2)), endpoints, sdkHits };
|
|
29821
31263
|
}
|
|
29822
|
-
function scanPathIntoStore(db, target, opts = {}) {
|
|
31264
|
+
async function scanPathIntoStore(db, target, opts = {}) {
|
|
31265
|
+
const matchText = opts.scanText ?? ((text) => Promise.resolve(scan(text, opts.rules)));
|
|
29823
31266
|
let scanned = 0;
|
|
29824
31267
|
let findingCount = 0;
|
|
29825
31268
|
const files = [];
|
|
@@ -29838,7 +31281,7 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29838
31281
|
for (const { path: file2, gitignored } of collectFiles(resolve2(target))) {
|
|
29839
31282
|
let text;
|
|
29840
31283
|
try {
|
|
29841
|
-
text =
|
|
31284
|
+
text = readFileSync10(file2, "utf8");
|
|
29842
31285
|
} catch {
|
|
29843
31286
|
continue;
|
|
29844
31287
|
}
|
|
@@ -29846,9 +31289,9 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29846
31289
|
const fileEgress = extractFileEgress(file2, text);
|
|
29847
31290
|
if (fileEgress) egressFiles.push(fileEgress);
|
|
29848
31291
|
const shielded = shieldPointers(text);
|
|
29849
|
-
const matches = dropShieldedFindings(
|
|
31292
|
+
const matches = dropShieldedFindings(await matchText(shielded.text), shielded.spans);
|
|
29850
31293
|
if (matches.length === 0) continue;
|
|
29851
|
-
const eventId =
|
|
31294
|
+
const eventId = randomUUID15();
|
|
29852
31295
|
const metadata = { filePath: file2 };
|
|
29853
31296
|
if (gitignored) metadata.gitignored = true;
|
|
29854
31297
|
const event = {
|
|
@@ -29866,7 +31309,7 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29866
31309
|
const key = resolveFingerprintKey();
|
|
29867
31310
|
const valueFingerprint = key ? fingerprintValue(key, m.rawMatch) : maskedMatch;
|
|
29868
31311
|
return {
|
|
29869
|
-
id:
|
|
31312
|
+
id: randomUUID15(),
|
|
29870
31313
|
eventId,
|
|
29871
31314
|
ruleId: m.ruleId,
|
|
29872
31315
|
category: m.category,
|
|
@@ -29891,12 +31334,12 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29891
31334
|
}
|
|
29892
31335
|
|
|
29893
31336
|
// ../packages/local-ops/src/project-inventory.ts
|
|
29894
|
-
import { statSync as
|
|
31337
|
+
import { statSync as statSync9 } from "fs";
|
|
29895
31338
|
import { resolve as resolve3 } from "path";
|
|
29896
31339
|
function recordProjectInventory(db, target) {
|
|
29897
31340
|
try {
|
|
29898
31341
|
const abs = resolve3(target);
|
|
29899
|
-
|
|
31342
|
+
statSync9(abs);
|
|
29900
31343
|
const identity = resolveRepoIdentity(abs);
|
|
29901
31344
|
if (!identity) return null;
|
|
29902
31345
|
const sourceProjectId2 = db.sourceProject.upsert({
|
|
@@ -29945,17 +31388,17 @@ function reinvokeArgv(subcommand, extraArgs = []) {
|
|
|
29945
31388
|
|
|
29946
31389
|
// ../packages/local-ops/src/update-cache.ts
|
|
29947
31390
|
import { spawn } from "child_process";
|
|
29948
|
-
import { existsSync as
|
|
29949
|
-
import { join as
|
|
31391
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11, rmSync as rmSync5 } from "fs";
|
|
31392
|
+
import { join as join16 } from "path";
|
|
29950
31393
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
29951
31394
|
function cachePath(home) {
|
|
29952
|
-
return
|
|
31395
|
+
return join16(dataDir(home), "update-check.json");
|
|
29953
31396
|
}
|
|
29954
31397
|
function readCache(home) {
|
|
29955
31398
|
const path = cachePath(home);
|
|
29956
|
-
if (!
|
|
31399
|
+
if (!existsSync10(path)) return null;
|
|
29957
31400
|
try {
|
|
29958
|
-
const raw = JSON.parse(
|
|
31401
|
+
const raw = JSON.parse(readFileSync11(path, "utf8"));
|
|
29959
31402
|
if (!isRecord(raw) || typeof raw.checkedAt !== "number" || !isRecord(raw.report)) return null;
|
|
29960
31403
|
return raw;
|
|
29961
31404
|
} catch {
|
|
@@ -29963,9 +31406,9 @@ function readCache(home) {
|
|
|
29963
31406
|
}
|
|
29964
31407
|
}
|
|
29965
31408
|
function writeCache(home, cache) {
|
|
29966
|
-
if (!
|
|
31409
|
+
if (!existsSync10(dataDir(home))) return;
|
|
29967
31410
|
try {
|
|
29968
|
-
|
|
31411
|
+
writeOwnerOnlyFileSync(cachePath(home), `${JSON.stringify(cache, null, 2)}
|
|
29969
31412
|
`);
|
|
29970
31413
|
} catch {
|
|
29971
31414
|
}
|
|
@@ -29975,7 +31418,7 @@ function isStale(cache, ttlMs = CHECK_TTL_MS) {
|
|
|
29975
31418
|
}
|
|
29976
31419
|
function clearCache(home) {
|
|
29977
31420
|
try {
|
|
29978
|
-
|
|
31421
|
+
rmSync5(cachePath(home), { force: true });
|
|
29979
31422
|
} catch {
|
|
29980
31423
|
}
|
|
29981
31424
|
}
|
|
@@ -30082,9 +31525,9 @@ var COMMAND_SPECS = [
|
|
|
30082
31525
|
{ name: "stats", summary: "Print findings / enforcement / detections from the local store" },
|
|
30083
31526
|
{
|
|
30084
31527
|
name: "detections",
|
|
30085
|
-
argHint: "[update]",
|
|
31528
|
+
argHint: "[update|unquarantine]",
|
|
30086
31529
|
summary: "List installed detection packs + available updates (updates are manual)",
|
|
30087
|
-
args: ["update"]
|
|
31530
|
+
args: ["update", "unquarantine"]
|
|
30088
31531
|
},
|
|
30089
31532
|
{
|
|
30090
31533
|
name: "plugins",
|
|
@@ -30092,6 +31535,11 @@ var COMMAND_SPECS = [
|
|
|
30092
31535
|
args: ["list", "install"]
|
|
30093
31536
|
},
|
|
30094
31537
|
{ name: "dashboard", summary: "Launch the local web dashboard and open it in the browser" },
|
|
31538
|
+
{
|
|
31539
|
+
name: "extension",
|
|
31540
|
+
summary: "Install/check the native-messaging host for the browser extension (Chrome)",
|
|
31541
|
+
args: ["install", "status"]
|
|
31542
|
+
},
|
|
30095
31543
|
{
|
|
30096
31544
|
name: "exception",
|
|
30097
31545
|
summary: "Manage detection exceptions (approve, add, list, show, revoke, rotate-key)",
|
|
@@ -30134,13 +31582,14 @@ function commandsHelp() {
|
|
|
30134
31582
|
}
|
|
30135
31583
|
|
|
30136
31584
|
// src/commands/check-updates.ts
|
|
30137
|
-
import { existsSync as
|
|
31585
|
+
import { existsSync as existsSync11 } from "fs";
|
|
30138
31586
|
import { parseArgs } from "util";
|
|
30139
31587
|
|
|
30140
31588
|
// src/lib/args.ts
|
|
31589
|
+
import { resolve as resolve4 } from "path";
|
|
30141
31590
|
var HOME_OPTION = { home: { type: "string" } };
|
|
30142
31591
|
function homeBase(home) {
|
|
30143
|
-
return home
|
|
31592
|
+
return home === void 0 ? defaultDataDir() : resolve4(home);
|
|
30144
31593
|
}
|
|
30145
31594
|
|
|
30146
31595
|
// src/commands/check-updates.ts
|
|
@@ -30165,7 +31614,7 @@ function runCheckUpdates(argv) {
|
|
|
30165
31614
|
out.write(`${String(ups.length)} update(s) available \u2014 run \`aka update\` to apply.
|
|
30166
31615
|
`);
|
|
30167
31616
|
}
|
|
30168
|
-
if (
|
|
31617
|
+
if (existsSync11(dataDir(home))) {
|
|
30169
31618
|
const notifiedPluginIds = readCache(home)?.notifiedPluginIds ?? [];
|
|
30170
31619
|
writeCache(home, { checkedAt: Date.now(), report, notifiedPluginIds });
|
|
30171
31620
|
}
|
|
@@ -30319,11 +31768,11 @@ function runCompletion(argv) {
|
|
|
30319
31768
|
|
|
30320
31769
|
// src/commands/dashboard.ts
|
|
30321
31770
|
import { spawn as spawn3 } from "child_process";
|
|
30322
|
-
import { existsSync as
|
|
31771
|
+
import { existsSync as existsSync12 } from "fs";
|
|
30323
31772
|
import { createRequire as createRequire2 } from "module";
|
|
30324
31773
|
import { createServer } from "net";
|
|
30325
|
-
import { dirname as
|
|
30326
|
-
import { fileURLToPath as
|
|
31774
|
+
import { dirname as dirname6, join as join17 } from "path";
|
|
31775
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
30327
31776
|
import { parseArgs as parseArgs2 } from "util";
|
|
30328
31777
|
|
|
30329
31778
|
// src/lib/open-url.ts
|
|
@@ -30340,20 +31789,20 @@ function openUrl(url2) {
|
|
|
30340
31789
|
|
|
30341
31790
|
// src/commands/dashboard.ts
|
|
30342
31791
|
var require3 = createRequire2(import.meta.url);
|
|
30343
|
-
var here =
|
|
31792
|
+
var here = dirname6(fileURLToPath3(import.meta.url));
|
|
30344
31793
|
var BIND_HOST = "127.0.0.1";
|
|
30345
31794
|
function pkgBase() {
|
|
30346
|
-
return isSea() ?
|
|
31795
|
+
return isSea() ? dirname6(process.execPath) : join17(here, "..");
|
|
30347
31796
|
}
|
|
30348
31797
|
function isPortFree(port) {
|
|
30349
|
-
return new Promise((
|
|
31798
|
+
return new Promise((resolve6) => {
|
|
30350
31799
|
const probe = createServer();
|
|
30351
31800
|
probe.once("error", () => {
|
|
30352
|
-
|
|
31801
|
+
resolve6(false);
|
|
30353
31802
|
});
|
|
30354
31803
|
probe.once("listening", () => {
|
|
30355
31804
|
probe.close(() => {
|
|
30356
|
-
|
|
31805
|
+
resolve6(true);
|
|
30357
31806
|
});
|
|
30358
31807
|
});
|
|
30359
31808
|
probe.listen(port, BIND_HOST);
|
|
@@ -30397,14 +31846,14 @@ async function runDashboard(argv) {
|
|
|
30397
31846
|
refreshUpdateMirror(dataDir());
|
|
30398
31847
|
const base = pkgBase();
|
|
30399
31848
|
const bundledServer = [
|
|
30400
|
-
|
|
30401
|
-
|
|
30402
|
-
].find((p) =>
|
|
31849
|
+
join17(base, "web-ui", "server.js"),
|
|
31850
|
+
join17(base, "web-ui", "web-ui", "server.js")
|
|
31851
|
+
].find((p) => existsSync12(p));
|
|
30403
31852
|
if (bundledServer) {
|
|
30404
31853
|
launchStandalone(bundledServer, port, shouldOpen, url2);
|
|
30405
31854
|
return;
|
|
30406
31855
|
}
|
|
30407
|
-
const webUiDir =
|
|
31856
|
+
const webUiDir = dirname6(require3.resolve("@akasecurity/web-ui/package.json"));
|
|
30408
31857
|
const nextBin = require3.resolve("next/dist/bin/next", { paths: [webUiDir] });
|
|
30409
31858
|
const startNext = () => {
|
|
30410
31859
|
const child = spawn3(
|
|
@@ -30418,7 +31867,7 @@ async function runDashboard(argv) {
|
|
|
30418
31867
|
onReadyOpen(child, shouldOpen, url2, `Starting the AKA dashboard at ${url2}
|
|
30419
31868
|
`);
|
|
30420
31869
|
};
|
|
30421
|
-
if (
|
|
31870
|
+
if (existsSync12(join17(webUiDir, ".next", "BUILD_ID"))) {
|
|
30422
31871
|
startNext();
|
|
30423
31872
|
return;
|
|
30424
31873
|
}
|
|
@@ -30441,7 +31890,7 @@ function launchStandalone(serverJs, port, shouldOpen, url2) {
|
|
|
30441
31890
|
return;
|
|
30442
31891
|
}
|
|
30443
31892
|
const child = spawn3(reinvoke.command, reinvoke.args, {
|
|
30444
|
-
cwd:
|
|
31893
|
+
cwd: dirname6(serverJs),
|
|
30445
31894
|
stdio: ["inherit", "pipe", "inherit"],
|
|
30446
31895
|
// Inherit the parent env so the child server sees PATH etc.; PORT/HOSTNAME tell
|
|
30447
31896
|
// Next's standalone server where to bind (its only port input).
|
|
@@ -30484,16 +31933,18 @@ function onReadyOpen(child, shouldOpen, url2, banner) {
|
|
|
30484
31933
|
|
|
30485
31934
|
// src/commands/detections.ts
|
|
30486
31935
|
import { parseArgs as parseArgs3 } from "util";
|
|
31936
|
+
var SUBCOMMANDS = ["update", "unquarantine"];
|
|
30487
31937
|
async function runDetections(argv) {
|
|
30488
31938
|
const { values, positionals } = parseArgs3({
|
|
30489
31939
|
args: argv,
|
|
30490
31940
|
options: { ...HOME_OPTION, all: { type: "boolean" } },
|
|
30491
31941
|
allowPositionals: true
|
|
30492
31942
|
});
|
|
30493
|
-
const
|
|
31943
|
+
const named = SUBCOMMANDS.find((s) => s === positionals[0]);
|
|
31944
|
+
const sub = named ?? "list";
|
|
30494
31945
|
if (sub === "list" && positionals.length > 0) {
|
|
30495
31946
|
process.stderr.write(
|
|
30496
|
-
`aka detections: unknown subcommand '${positionals[0] ?? ""}' (
|
|
31947
|
+
`aka detections: unknown subcommand '${positionals[0] ?? ""}' (expected one of: ${SUBCOMMANDS.join(", ")})
|
|
30497
31948
|
`
|
|
30498
31949
|
);
|
|
30499
31950
|
process.exitCode = 1;
|
|
@@ -30505,6 +31956,8 @@ async function runDetections(argv) {
|
|
|
30505
31956
|
db.installedPacks.recordInventory(bundledDetections(), cliRecordedBy());
|
|
30506
31957
|
if (sub === "update") {
|
|
30507
31958
|
await runUpdateSub(db, positionals.slice(1), values.all === true);
|
|
31959
|
+
} else if (sub === "unquarantine") {
|
|
31960
|
+
runUnquarantineSub(db);
|
|
30508
31961
|
} else {
|
|
30509
31962
|
await runListSub(db);
|
|
30510
31963
|
}
|
|
@@ -30527,6 +31980,15 @@ async function runListSub(db) {
|
|
|
30527
31980
|
${String(items.length)} pack(s) \xB7 ${String(items.reduce((n, i) => n + i.ruleCount, 0))} rule(s) \xB7 ${String(active)} enabled
|
|
30528
31981
|
`
|
|
30529
31982
|
);
|
|
31983
|
+
const quarantined = db.ruleProbeCache.countQuarantined();
|
|
31984
|
+
if (quarantined > 0) {
|
|
31985
|
+
out.write(
|
|
31986
|
+
`
|
|
31987
|
+
\u26A0 ${String(quarantined)} rule(s) quarantined for exceeding the ReDoS timing budget \u2014 they are excluded from every scan.
|
|
31988
|
+
aka detections unquarantine # forget the verdicts and measure them again
|
|
31989
|
+
`
|
|
31990
|
+
);
|
|
31991
|
+
}
|
|
30530
31992
|
if (counts.updates > 0) {
|
|
30531
31993
|
out.write(
|
|
30532
31994
|
`
|
|
@@ -30539,6 +32001,20 @@ ${String(items.length)} pack(s) \xB7 ${String(items.reduce((n, i) => n + i.ruleC
|
|
|
30539
32001
|
out.write("\n\u2713 All detection packs are up to date with this CLI.\n");
|
|
30540
32002
|
}
|
|
30541
32003
|
}
|
|
32004
|
+
function runUnquarantineSub(db) {
|
|
32005
|
+
const { refused, cleared } = db.ruleProbeCache.clearQuarantined();
|
|
32006
|
+
if (refused) {
|
|
32007
|
+
process.stderr.write(
|
|
32008
|
+
"aka detections unquarantine: the store refused the write (another process may be holding it). Nothing was cleared \u2014 try again.\n"
|
|
32009
|
+
);
|
|
32010
|
+
process.exitCode = 1;
|
|
32011
|
+
return;
|
|
32012
|
+
}
|
|
32013
|
+
process.stdout.write(
|
|
32014
|
+
cleared === 0 ? "\u2713 No quarantined rules \u2014 nothing to clear.\n" : `\u2713 Cleared ${String(cleared)} quarantine verdict(s). Each rule is measured again the next time it loads, and is re-quarantined if it still exceeds the budget.
|
|
32015
|
+
`
|
|
32016
|
+
);
|
|
32017
|
+
}
|
|
30542
32018
|
async function runUpdateSub(db, ids, all) {
|
|
30543
32019
|
const { items } = await db.detections.listDetections({ filter: "all" });
|
|
30544
32020
|
const byId = new Map(items.map((i) => [i.id, i]));
|
|
@@ -30660,11 +32136,11 @@ function terminalPrompter() {
|
|
|
30660
32136
|
},
|
|
30661
32137
|
isInteractive: process.stdin.isTTY && process.stdout.isTTY,
|
|
30662
32138
|
ask(question) {
|
|
30663
|
-
return new Promise((
|
|
32139
|
+
return new Promise((resolve6) => {
|
|
30664
32140
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
30665
32141
|
rl.question(question, (answer) => {
|
|
30666
32142
|
rl.close();
|
|
30667
|
-
|
|
32143
|
+
resolve6(answer);
|
|
30668
32144
|
});
|
|
30669
32145
|
});
|
|
30670
32146
|
},
|
|
@@ -30675,7 +32151,7 @@ function terminalPrompter() {
|
|
|
30675
32151
|
// fatal signals: a security tool must never brick the terminal it was
|
|
30676
32152
|
// invoked from.
|
|
30677
32153
|
askHidden(question) {
|
|
30678
|
-
return new Promise((
|
|
32154
|
+
return new Promise((resolve6, reject) => {
|
|
30679
32155
|
const stdin = process.stdin;
|
|
30680
32156
|
process.stdout.write(question);
|
|
30681
32157
|
stdin.setRawMode(true);
|
|
@@ -30698,7 +32174,7 @@ function terminalPrompter() {
|
|
|
30698
32174
|
restore();
|
|
30699
32175
|
process.stdout.write("\n");
|
|
30700
32176
|
if (err) reject(err);
|
|
30701
|
-
else
|
|
32177
|
+
else resolve6(state.value);
|
|
30702
32178
|
};
|
|
30703
32179
|
const onError = (err) => {
|
|
30704
32180
|
finish(err);
|
|
@@ -31052,7 +32528,6 @@ async function runApproveReveal(pointer, values, io, base) {
|
|
|
31052
32528
|
const vault = new SecretVault({
|
|
31053
32529
|
repo: db.secretVault,
|
|
31054
32530
|
keys: createKeyProvider(readWorkspaceSettings(base).vaultKeyCustody, keysDir(base)),
|
|
31055
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
31056
32531
|
// Read live so a consent revocation applies to the very next call.
|
|
31057
32532
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent)
|
|
31058
32533
|
});
|
|
@@ -31344,11 +32819,11 @@ async function runShow(argv, io) {
|
|
|
31344
32819
|
ensurePacks();
|
|
31345
32820
|
const rule = getLoadedRules().find((r) => r.id === ex.ruleId);
|
|
31346
32821
|
const ruleLine = rule ? `${ex.ruleId} (${ex.category} \xB7 ${rule.severity})` : `${ex.ruleId} (${ex.category})`;
|
|
31347
|
-
const fp = `${ex.valueFingerprint.slice(0, 4)}\u2026${ex.valueFingerprint.slice(-4)}`;
|
|
31348
32822
|
const label = (name) => ` ${name.padEnd(10)}`;
|
|
31349
32823
|
const lines = [
|
|
32824
|
+
`${label("Id")}${shortId(ex.id)}`,
|
|
31350
32825
|
`${label("Rule")}${ruleLine}`,
|
|
31351
|
-
`${label("Value")}${ex.maskedValue}
|
|
32826
|
+
`${label("Value")}${ex.maskedValue} (fingerprint key v${String(ex.keyVersion)})`,
|
|
31352
32827
|
`${label("Scope")}${scopeSummary(ex)}${ex.maxUses !== null ? ` \xB7 max ${String(ex.maxUses)} use${ex.maxUses === 1 ? "" : "s"}` : ""}`,
|
|
31353
32828
|
`${label("Reason")}"${ex.justification}"`,
|
|
31354
32829
|
`${label("Created")}${formatTimestamp(ex.createdAt)} by ${ex.createdBy} via ${viaLabel(ex.createdVia)}`,
|
|
@@ -31412,6 +32887,17 @@ async function runRevoke(argv, io) {
|
|
|
31412
32887
|
db.close();
|
|
31413
32888
|
}
|
|
31414
32889
|
}
|
|
32890
|
+
async function countApprovableBlocked(db, dir) {
|
|
32891
|
+
const key = (() => {
|
|
32892
|
+
try {
|
|
32893
|
+
return keyStateOf(readFingerprintKey(dir));
|
|
32894
|
+
} catch {
|
|
32895
|
+
return { status: "unreadable" };
|
|
32896
|
+
}
|
|
32897
|
+
})();
|
|
32898
|
+
const rows = await db.exceptions.recentBlocked(BLOCKED_DETECTIONS_RETENTION_MS);
|
|
32899
|
+
return rows.filter((row) => isMatchableUnder(row.keyVersion, key)).length;
|
|
32900
|
+
}
|
|
31415
32901
|
async function runRotateKey(argv, io) {
|
|
31416
32902
|
const { values } = parseArgs4({
|
|
31417
32903
|
args: argv,
|
|
@@ -31438,6 +32924,10 @@ written under the old key simply stop matching. They remain listed for audit.
|
|
|
31438
32924
|
`);
|
|
31439
32925
|
}
|
|
31440
32926
|
}
|
|
32927
|
+
io.out(`
|
|
32928
|
+
${rotationBlockedLedgerNote(await countApprovableBlocked(db, dir))}
|
|
32929
|
+
|
|
32930
|
+
`);
|
|
31441
32931
|
if (values.yes !== true) {
|
|
31442
32932
|
if (!io.isInteractive) {
|
|
31443
32933
|
throw new Error("pass --yes to rotate non-interactively");
|
|
@@ -31466,7 +32956,6 @@ Existing grants no longer match; re-approve deliberately where still needed.
|
|
|
31466
32956
|
const vault = new SecretVault({
|
|
31467
32957
|
repo: db.secretVault,
|
|
31468
32958
|
keys: createKeyProvider(readWorkspaceSettings(base).vaultKeyCustody, keysDir(base)),
|
|
31469
|
-
fingerprintKey: next,
|
|
31470
32959
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent)
|
|
31471
32960
|
});
|
|
31472
32961
|
const refreshed = await vault.refreshFingerprints(next);
|
|
@@ -31507,14 +32996,208 @@ async function runException(argv, io = terminalPrompter()) {
|
|
|
31507
32996
|
await handler(rest, io);
|
|
31508
32997
|
}
|
|
31509
32998
|
|
|
32999
|
+
// src/commands/extension.ts
|
|
33000
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
33001
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
33002
|
+
import { createRequire as createRequire3 } from "module";
|
|
33003
|
+
import { homedir as homedir5 } from "os";
|
|
33004
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
33005
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
33006
|
+
var require4 = createRequire3(import.meta.url);
|
|
33007
|
+
var here2 = dirname7(fileURLToPath4(import.meta.url));
|
|
33008
|
+
function cliRoot() {
|
|
33009
|
+
return isSea() ? dirname7(process.execPath) : join18(here2, "..");
|
|
33010
|
+
}
|
|
33011
|
+
var EXTENSION_ID = "mdoiaiemcnjnaokmcmgbikcdhgiemdof";
|
|
33012
|
+
var NATIVE_HOST_NAME = "com.akasecurity.aka";
|
|
33013
|
+
function nativeHostManifest(hostPath) {
|
|
33014
|
+
return {
|
|
33015
|
+
name: NATIVE_HOST_NAME,
|
|
33016
|
+
description: "AI Traffic Control native messaging host \u2014 bridges the browser extension to the local ~/.aka SQLite store.",
|
|
33017
|
+
path: hostPath,
|
|
33018
|
+
type: "stdio",
|
|
33019
|
+
allowed_origins: [`chrome-extension://${EXTENSION_ID}/`]
|
|
33020
|
+
};
|
|
33021
|
+
}
|
|
33022
|
+
function resolveHostScript(root = cliRoot()) {
|
|
33023
|
+
const bundled = join18(root, "native-host", "host.js");
|
|
33024
|
+
if (existsSync13(bundled)) return bundled;
|
|
33025
|
+
try {
|
|
33026
|
+
const pkgJson = require4.resolve("@akasecurity/plugin-browser-extension/package.json");
|
|
33027
|
+
const devBuilt = join18(dirname7(pkgJson), "native-host", "host.js");
|
|
33028
|
+
if (existsSync13(devBuilt)) return devBuilt;
|
|
33029
|
+
} catch {
|
|
33030
|
+
}
|
|
33031
|
+
return null;
|
|
33032
|
+
}
|
|
33033
|
+
function resolveExtensionDist(root = cliRoot()) {
|
|
33034
|
+
const bundled = join18(root, "extension");
|
|
33035
|
+
if (existsSync13(bundled)) return bundled;
|
|
33036
|
+
try {
|
|
33037
|
+
const pkgJson = require4.resolve("@akasecurity/plugin-browser-extension/package.json");
|
|
33038
|
+
const devDist = join18(dirname7(pkgJson), "dist");
|
|
33039
|
+
if (existsSync13(devDist)) return devDist;
|
|
33040
|
+
} catch {
|
|
33041
|
+
}
|
|
33042
|
+
return null;
|
|
33043
|
+
}
|
|
33044
|
+
function chromeManifestDir(platform2 = process.platform) {
|
|
33045
|
+
if (platform2 === "darwin") {
|
|
33046
|
+
return join18(
|
|
33047
|
+
homedir5(),
|
|
33048
|
+
"Library",
|
|
33049
|
+
"Application Support",
|
|
33050
|
+
"Google",
|
|
33051
|
+
"Chrome",
|
|
33052
|
+
"NativeMessagingHosts"
|
|
33053
|
+
);
|
|
33054
|
+
}
|
|
33055
|
+
if (platform2 === "win32") {
|
|
33056
|
+
return join18(homedir5(), ".aka", "native-messaging");
|
|
33057
|
+
}
|
|
33058
|
+
return join18(homedir5(), ".config", "google-chrome", "NativeMessagingHosts");
|
|
33059
|
+
}
|
|
33060
|
+
function resolveNodeForLauncher() {
|
|
33061
|
+
if (!isSea()) return process.execPath;
|
|
33062
|
+
try {
|
|
33063
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
33064
|
+
const out = execFileSync3(probe, ["node"], { encoding: "utf8" }).split(/\r?\n/)[0]?.trim();
|
|
33065
|
+
if (out === void 0 || out === "") return null;
|
|
33066
|
+
return out;
|
|
33067
|
+
} catch {
|
|
33068
|
+
return null;
|
|
33069
|
+
}
|
|
33070
|
+
}
|
|
33071
|
+
function launcherPath(manifestDir, platform2) {
|
|
33072
|
+
return join18(
|
|
33073
|
+
manifestDir,
|
|
33074
|
+
platform2 === "win32" ? `${NATIVE_HOST_NAME}.cmd` : `${NATIVE_HOST_NAME}.sh`
|
|
33075
|
+
);
|
|
33076
|
+
}
|
|
33077
|
+
function posixQuote(value) {
|
|
33078
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
33079
|
+
}
|
|
33080
|
+
function cmdQuote(value) {
|
|
33081
|
+
return `"${value.replaceAll("%", "%%")}"`;
|
|
33082
|
+
}
|
|
33083
|
+
function writeLauncher(manifestDir, hostScript, nodePath, platform2) {
|
|
33084
|
+
const path = launcherPath(manifestDir, platform2);
|
|
33085
|
+
const tmp = `${path}.tmp`;
|
|
33086
|
+
if (platform2 === "win32") {
|
|
33087
|
+
writeFileSync7(tmp, `@echo off\r
|
|
33088
|
+
${cmdQuote(nodePath)} ${cmdQuote(hostScript)} %*\r
|
|
33089
|
+
`);
|
|
33090
|
+
} else {
|
|
33091
|
+
writeFileSync7(tmp, `#!/bin/sh
|
|
33092
|
+
exec ${posixQuote(nodePath)} ${posixQuote(hostScript)} "$@"
|
|
33093
|
+
`, {
|
|
33094
|
+
mode: 493
|
|
33095
|
+
});
|
|
33096
|
+
}
|
|
33097
|
+
renameSync5(tmp, path);
|
|
33098
|
+
return path;
|
|
33099
|
+
}
|
|
33100
|
+
function windowsRegistryKey() {
|
|
33101
|
+
return `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`;
|
|
33102
|
+
}
|
|
33103
|
+
function registerWindowsHost(manifestPath) {
|
|
33104
|
+
execFileSync3("reg", [
|
|
33105
|
+
"add",
|
|
33106
|
+
windowsRegistryKey(),
|
|
33107
|
+
"/ve",
|
|
33108
|
+
"/t",
|
|
33109
|
+
"REG_SZ",
|
|
33110
|
+
"/d",
|
|
33111
|
+
manifestPath,
|
|
33112
|
+
"/f"
|
|
33113
|
+
]);
|
|
33114
|
+
}
|
|
33115
|
+
function runExtension(argv) {
|
|
33116
|
+
const sub = argv[0];
|
|
33117
|
+
if (sub === "install") {
|
|
33118
|
+
runInstall(chromeManifestDir());
|
|
33119
|
+
return;
|
|
33120
|
+
}
|
|
33121
|
+
if (sub === "status") {
|
|
33122
|
+
runStatus(chromeManifestDir());
|
|
33123
|
+
return;
|
|
33124
|
+
}
|
|
33125
|
+
process.stderr.write(
|
|
33126
|
+
`aka extension: unknown subcommand '${sub ?? ""}'
|
|
33127
|
+
Usage: aka extension <install|status>
|
|
33128
|
+
`
|
|
33129
|
+
);
|
|
33130
|
+
process.exitCode = 1;
|
|
33131
|
+
}
|
|
33132
|
+
function runInstall(manifestDir, hostScript = resolveHostScript(), extensionDist = resolveExtensionDist(), nodePath = resolveNodeForLauncher()) {
|
|
33133
|
+
if (!hostScript) {
|
|
33134
|
+
process.stderr.write(
|
|
33135
|
+
"aka extension install: could not find the native-messaging host script.\n In a workspace checkout, build it first: pnpm --filter @akasecurity/plugin-browser-extension build\n"
|
|
33136
|
+
);
|
|
33137
|
+
process.exitCode = 1;
|
|
33138
|
+
return;
|
|
33139
|
+
}
|
|
33140
|
+
if (!nodePath) {
|
|
33141
|
+
process.stderr.write(
|
|
33142
|
+
"aka extension install: no Node.js runtime found to run the native-messaging host.\n The host runs under Node; install Node.js (so `node` resolves on PATH) and re-run.\n"
|
|
33143
|
+
);
|
|
33144
|
+
process.exitCode = 1;
|
|
33145
|
+
return;
|
|
33146
|
+
}
|
|
33147
|
+
mkdirSync5(manifestDir, { recursive: true });
|
|
33148
|
+
const launcher = writeLauncher(manifestDir, hostScript, nodePath, process.platform);
|
|
33149
|
+
const manifestPath = join18(manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
33150
|
+
const tmp = `${manifestPath}.tmp`;
|
|
33151
|
+
writeFileSync7(tmp, `${JSON.stringify(nativeHostManifest(launcher), null, 2)}
|
|
33152
|
+
`, {
|
|
33153
|
+
mode: DATA_FILE_MODE
|
|
33154
|
+
});
|
|
33155
|
+
renameSync5(tmp, manifestPath);
|
|
33156
|
+
if (process.platform === "win32") {
|
|
33157
|
+
try {
|
|
33158
|
+
registerWindowsHost(manifestPath);
|
|
33159
|
+
} catch (err) {
|
|
33160
|
+
process.stderr.write(
|
|
33161
|
+
`aka extension install: could not register the Windows registry key (${err instanceof Error ? err.message : String(err)}) \u2014 the native host won't be discoverable by Chrome until this is fixed.
|
|
33162
|
+
`
|
|
33163
|
+
);
|
|
33164
|
+
process.exitCode = 1;
|
|
33165
|
+
return;
|
|
33166
|
+
}
|
|
33167
|
+
}
|
|
33168
|
+
process.stdout.write(
|
|
33169
|
+
`\u2713 Installed the AKA native-messaging host
|
|
33170
|
+
host script: ${hostScript}
|
|
33171
|
+
launcher: ${launcher}
|
|
33172
|
+
manifest: ${manifestPath}
|
|
33173
|
+
` + (process.platform === "win32" ? ` registry: ${windowsRegistryKey()}
|
|
33174
|
+
` : "") + `
|
|
33175
|
+
Next: load the extension in Chrome \u2014
|
|
33176
|
+
1. Open chrome://extensions
|
|
33177
|
+
2. Enable "Developer mode" (top right)
|
|
33178
|
+
3. Click "Load unpacked" and select:
|
|
33179
|
+
${extensionDist ?? "<run: pnpm --filter @akasecurity/plugin-browser-extension build>"}
|
|
33180
|
+
`
|
|
33181
|
+
);
|
|
33182
|
+
}
|
|
33183
|
+
function runStatus(manifestDir) {
|
|
33184
|
+
const manifestPath = join18(manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
33185
|
+
const installed = existsSync13(manifestPath);
|
|
33186
|
+
process.stdout.write(
|
|
33187
|
+
`native-messaging host: ${installed ? "installed" : "not installed"}
|
|
33188
|
+
manifest: ${manifestPath}
|
|
33189
|
+
` + (installed ? "" : " run `aka extension install` to set it up\n")
|
|
33190
|
+
);
|
|
33191
|
+
}
|
|
33192
|
+
|
|
31510
33193
|
// src/commands/init.ts
|
|
31511
|
-
import { existsSync as
|
|
31512
|
-
import { join as
|
|
33194
|
+
import { existsSync as existsSync15, lstatSync as lstatSync2, readdirSync as readdirSync7, readlinkSync, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
33195
|
+
import { dirname as dirname8, join as join19, resolve as resolve5 } from "path";
|
|
31513
33196
|
import * as readline from "readline/promises";
|
|
31514
33197
|
import { parseArgs as parseArgs6 } from "util";
|
|
31515
33198
|
|
|
31516
33199
|
// src/commands/plugins.ts
|
|
31517
|
-
import { existsSync as
|
|
33200
|
+
import { existsSync as existsSync14 } from "fs";
|
|
31518
33201
|
import { parseArgs as parseArgs5 } from "util";
|
|
31519
33202
|
function runPlugins(argv) {
|
|
31520
33203
|
const [sub, ...rest] = argv;
|
|
@@ -31531,7 +33214,7 @@ async function listPlugins(argv) {
|
|
|
31531
33214
|
const { values } = parseArgs5({ args: argv, options: HOME_OPTION });
|
|
31532
33215
|
const home = homeBase(values.home);
|
|
31533
33216
|
const active = /* @__PURE__ */ new Set();
|
|
31534
|
-
if (
|
|
33217
|
+
if (existsSync14(dbPath(home))) {
|
|
31535
33218
|
const db = openLocalDatabase(dataDir(home));
|
|
31536
33219
|
try {
|
|
31537
33220
|
const recent = await db.findings.recentFindings({ limit: 1e3 });
|
|
@@ -31540,7 +33223,7 @@ async function listPlugins(argv) {
|
|
|
31540
33223
|
db.close();
|
|
31541
33224
|
}
|
|
31542
33225
|
}
|
|
31543
|
-
const installed =
|
|
33226
|
+
const installed = installedAgentPluginVersions();
|
|
31544
33227
|
const out = process.stdout;
|
|
31545
33228
|
out.write("Agent plugins (the CLI is an optional hub \u2014 plugins also self-install):\n\n");
|
|
31546
33229
|
for (const a of AGENT_PLUGINS) {
|
|
@@ -31571,42 +33254,44 @@ function installPlugin(argv) {
|
|
|
31571
33254
|
return;
|
|
31572
33255
|
}
|
|
31573
33256
|
const ref = pluginRef(agent);
|
|
31574
|
-
|
|
33257
|
+
const cliBin = agent.cliBin;
|
|
33258
|
+
if (!ref || !cliBin) {
|
|
31575
33259
|
process.stdout.write(
|
|
31576
|
-
`${agent.
|
|
33260
|
+
agent.installHint ? `${agent.installHint}
|
|
33261
|
+
` : `${agent.name} has no automated install path yet \u2014 install it from the AKA marketplace in ${agent.name}, then run \`aka init\`.
|
|
31577
33262
|
`
|
|
31578
33263
|
);
|
|
31579
33264
|
return;
|
|
31580
33265
|
}
|
|
31581
|
-
if (!
|
|
33266
|
+
if (!createCliPluginManager(cliBin).available()) {
|
|
31582
33267
|
process.stdout.write(
|
|
31583
33268
|
`Installing ${agent.name}\u2026
|
|
31584
33269
|
|
|
31585
|
-
The \`
|
|
31586
|
-
Install it from inside
|
|
31587
|
-
|
|
31588
|
-
|
|
33270
|
+
The \`${cliBin}\` CLI isn't on your PATH, so I can't install it automatically.
|
|
33271
|
+
Install it from inside ${agent.name}:
|
|
33272
|
+
${cliBin} plugin marketplace add ${agent.marketplaceSource ?? ""}
|
|
33273
|
+
${cliBin} plugin install ${ref}
|
|
31589
33274
|
|
|
31590
33275
|
Then run \`aka init\` to set up the local store.
|
|
31591
33276
|
`
|
|
31592
33277
|
);
|
|
31593
33278
|
return;
|
|
31594
33279
|
}
|
|
31595
|
-
process.stdout.write(`Installing ${agent.name} via
|
|
33280
|
+
process.stdout.write(`Installing ${agent.name} via ${cliBin}\u2026
|
|
31596
33281
|
`);
|
|
31597
33282
|
const { ok } = installAgentPlugin(agent.id, "inherit");
|
|
31598
33283
|
if (ok) {
|
|
31599
33284
|
process.stdout.write(
|
|
31600
33285
|
`
|
|
31601
33286
|
\u2713 Installed ${agent.name}.
|
|
31602
|
-
\u21BB Restart
|
|
33287
|
+
\u21BB Restart ${agent.name} to load it.
|
|
31603
33288
|
Run \`aka init\` to scaffold your local store (if you haven't already).
|
|
31604
33289
|
`
|
|
31605
33290
|
);
|
|
31606
33291
|
} else {
|
|
31607
33292
|
process.stderr.write(
|
|
31608
33293
|
`
|
|
31609
|
-
\u2717 Install failed \u2014 see the output above, or add it in
|
|
33294
|
+
\u2717 Install failed \u2014 see the output above, or add it in ${agent.name} with \`${cliBin} plugin install ${ref}\`.
|
|
31610
33295
|
`
|
|
31611
33296
|
);
|
|
31612
33297
|
process.exitCode = 1;
|
|
@@ -31615,40 +33300,159 @@ Then run \`aka init\` to set up the local store.
|
|
|
31615
33300
|
|
|
31616
33301
|
// src/commands/init.ts
|
|
31617
33302
|
var PLUGIN_OFFER_IDENTITY = `${PRODUCT_NAME} \u2014 ${PRODUCT_TAGLINE}`;
|
|
33303
|
+
var STORE_DB = "the store database (including the prompt corpus)";
|
|
33304
|
+
var STORE_SETTINGS = "your settings file";
|
|
33305
|
+
function storeContents(home) {
|
|
33306
|
+
return /* @__PURE__ */ new Map([
|
|
33307
|
+
[home, "the store (including the prompt corpus in aka.db)"],
|
|
33308
|
+
[settingsDir(home), STORE_SETTINGS],
|
|
33309
|
+
[dataDir(home), STORE_DB],
|
|
33310
|
+
[keysDir(home), "the vault key"],
|
|
33311
|
+
[join19(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
33312
|
+
[dbPath(home), STORE_DB]
|
|
33313
|
+
]);
|
|
33314
|
+
}
|
|
33315
|
+
function storeTargets(home) {
|
|
33316
|
+
return [...storeContents(home).keys()];
|
|
33317
|
+
}
|
|
33318
|
+
function storeModeTargets(home) {
|
|
33319
|
+
const targets = new Set(storeTargets(home));
|
|
33320
|
+
const data = dataDir(home);
|
|
33321
|
+
try {
|
|
33322
|
+
for (const name of readdirSync7(data)) {
|
|
33323
|
+
if (name.endsWith(".partial")) continue;
|
|
33324
|
+
targets.add(join19(data, name));
|
|
33325
|
+
}
|
|
33326
|
+
} catch {
|
|
33327
|
+
}
|
|
33328
|
+
return [...targets];
|
|
33329
|
+
}
|
|
31618
33330
|
function looseStorePaths(home) {
|
|
31619
33331
|
if (process.platform === "win32") return [];
|
|
31620
|
-
|
|
31621
|
-
home,
|
|
31622
|
-
settingsDir(home),
|
|
31623
|
-
dataDir(home),
|
|
31624
|
-
join17(settingsDir(home), "settings.json"),
|
|
31625
|
-
dbPath(home)
|
|
31626
|
-
];
|
|
31627
|
-
return targets.filter((p) => {
|
|
33332
|
+
return storeModeTargets(home).filter((p) => {
|
|
31628
33333
|
try {
|
|
31629
|
-
|
|
33334
|
+
if (lstatSync2(p).isSymbolicLink()) return false;
|
|
33335
|
+
return (statSync10(p).mode & 63) !== 0;
|
|
31630
33336
|
} catch {
|
|
31631
33337
|
return false;
|
|
31632
33338
|
}
|
|
31633
33339
|
});
|
|
31634
33340
|
}
|
|
33341
|
+
function symlinkedStorePaths(home, platform2 = process.platform) {
|
|
33342
|
+
return [...storeContents(home)].flatMap(([path, holds]) => {
|
|
33343
|
+
try {
|
|
33344
|
+
if (!lstatSync2(path).isSymbolicLink()) return [];
|
|
33345
|
+
return [
|
|
33346
|
+
{
|
|
33347
|
+
path,
|
|
33348
|
+
target: linkTarget(path),
|
|
33349
|
+
holds,
|
|
33350
|
+
// existsSync follows the link, so a target that is gone reads as
|
|
33351
|
+
// absent here while lstat above still sees the link itself.
|
|
33352
|
+
missing: !existsSync15(path),
|
|
33353
|
+
mode: targetMode(path, platform2)
|
|
33354
|
+
}
|
|
33355
|
+
];
|
|
33356
|
+
} catch {
|
|
33357
|
+
return [];
|
|
33358
|
+
}
|
|
33359
|
+
});
|
|
33360
|
+
}
|
|
33361
|
+
function linkTarget(path) {
|
|
33362
|
+
try {
|
|
33363
|
+
return realpathSync3(path);
|
|
33364
|
+
} catch {
|
|
33365
|
+
return resolve5(dirname8(path), readlinkSync(path));
|
|
33366
|
+
}
|
|
33367
|
+
}
|
|
33368
|
+
function targetMode(path, platform2) {
|
|
33369
|
+
if (platform2 === "win32") return void 0;
|
|
33370
|
+
try {
|
|
33371
|
+
return statSync10(path).mode & 511;
|
|
33372
|
+
} catch {
|
|
33373
|
+
return void 0;
|
|
33374
|
+
}
|
|
33375
|
+
}
|
|
33376
|
+
function symlinkWarnings(paths, platform2 = process.platform) {
|
|
33377
|
+
const seeAlso = '(see the "Data at rest" note in SECURITY.md)';
|
|
33378
|
+
return paths.map(({ path, target, holds, missing, mode }) => {
|
|
33379
|
+
if (missing) {
|
|
33380
|
+
return ` \u26A0 ${path} is a symlink to ${target}, which does not exist \u2014 ${holds} cannot be written there until you create that target or remove the link ${seeAlso}
|
|
33381
|
+
`;
|
|
33382
|
+
}
|
|
33383
|
+
const inherited = mode === void 0 ? "" : ` (currently ${formatMode(mode)}${(mode & 63) !== 0 ? ", NOT owner-only" : ""})`;
|
|
33384
|
+
const kept = platform2 === "win32" ? "" : "permissions are never changed through a symlink, so ";
|
|
33385
|
+
const under = platform2 === "win32" ? "" : " under that target's own permissions";
|
|
33386
|
+
return ` \u26A0 ${path} is a symlink to ${target}${inherited} \u2014 ${kept}${holds} is written there${under} ${seeAlso}
|
|
33387
|
+
`;
|
|
33388
|
+
}).join("");
|
|
33389
|
+
}
|
|
33390
|
+
function formatMode(mode) {
|
|
33391
|
+
return `0${mode.toString(8).padStart(3, "0")}`;
|
|
33392
|
+
}
|
|
33393
|
+
function assertStoreLinksResolve(home) {
|
|
33394
|
+
for (const dir of [home, settingsDir(home), dataDir(home)]) {
|
|
33395
|
+
if (!isBrokenLink(dir)) continue;
|
|
33396
|
+
throw new Error(
|
|
33397
|
+
`${dir} is a symlink to ${linkTarget(dir)}, which does not exist \u2014 create that target or remove the link, then re-run \`aka init\``
|
|
33398
|
+
);
|
|
33399
|
+
}
|
|
33400
|
+
}
|
|
33401
|
+
function isBrokenLink(path) {
|
|
33402
|
+
try {
|
|
33403
|
+
return lstatSync2(path).isSymbolicLink() && !existsSync15(path);
|
|
33404
|
+
} catch {
|
|
33405
|
+
return false;
|
|
33406
|
+
}
|
|
33407
|
+
}
|
|
33408
|
+
function assertStorePathsAreDirectories(home) {
|
|
33409
|
+
for (const dir of [home, settingsDir(home), dataDir(home), keysDir(home)]) {
|
|
33410
|
+
if (!existsAsNonDirectory(dir)) continue;
|
|
33411
|
+
const occupant = isSymlink2(dir) ? `is a symlink to ${linkTarget(dir)}, which is not a directory \u2014 remove the link` : "exists but is not a directory \u2014 move that file aside or remove it";
|
|
33412
|
+
throw new Error(`${dir} ${occupant}. AKA keeps its store there; re-run \`aka init\` after.`);
|
|
33413
|
+
}
|
|
33414
|
+
}
|
|
33415
|
+
function isSymlink2(path) {
|
|
33416
|
+
try {
|
|
33417
|
+
return lstatSync2(path).isSymbolicLink();
|
|
33418
|
+
} catch {
|
|
33419
|
+
return false;
|
|
33420
|
+
}
|
|
33421
|
+
}
|
|
33422
|
+
function existsAsNonDirectory(path) {
|
|
33423
|
+
try {
|
|
33424
|
+
return !statSync10(path).isDirectory();
|
|
33425
|
+
} catch {
|
|
33426
|
+
return false;
|
|
33427
|
+
}
|
|
33428
|
+
}
|
|
31635
33429
|
async function runInit(argv) {
|
|
31636
33430
|
const { values } = parseArgs6({
|
|
31637
33431
|
args: argv,
|
|
31638
33432
|
options: { ...HOME_OPTION, yes: { type: "boolean", short: "y" } }
|
|
31639
33433
|
});
|
|
31640
33434
|
const home = homeBase(values.home);
|
|
33435
|
+
assertStoreLinksResolve(home);
|
|
33436
|
+
assertStorePathsAreDirectories(home);
|
|
31641
33437
|
ensureLayoutDirSync(home);
|
|
31642
33438
|
const settings = settingsDir(home);
|
|
31643
33439
|
ensureLayoutDirSync(settings);
|
|
31644
|
-
const settingsFile =
|
|
31645
|
-
|
|
31646
|
-
if (
|
|
31647
|
-
|
|
31648
|
-
settingsFile,
|
|
31649
|
-
|
|
33440
|
+
const settingsFile = join19(settings, "settings.json");
|
|
33441
|
+
let settingsCreated = false;
|
|
33442
|
+
if (!existsSync15(settingsFile)) {
|
|
33443
|
+
try {
|
|
33444
|
+
settingsCreated = withFileLock(settingsFile, () => {
|
|
33445
|
+
if (existsSync15(settingsFile)) return false;
|
|
33446
|
+
writeOwnerOnlyFileSync(
|
|
33447
|
+
settingsFile,
|
|
33448
|
+
`${JSON.stringify(defaultWorkspaceSettings(), null, 2)}
|
|
31650
33449
|
`
|
|
31651
|
-
|
|
33450
|
+
);
|
|
33451
|
+
return true;
|
|
33452
|
+
});
|
|
33453
|
+
} catch (err) {
|
|
33454
|
+
if (!(err instanceof FileLockError) || err.reason !== "timeout") throw err;
|
|
33455
|
+
}
|
|
31652
33456
|
}
|
|
31653
33457
|
tightenFile(settingsFile);
|
|
31654
33458
|
const db = openLocalDatabase(dataDir(home));
|
|
@@ -31664,14 +33468,15 @@ async function runInit(argv) {
|
|
|
31664
33468
|
db.close();
|
|
31665
33469
|
}
|
|
31666
33470
|
const loose = looseStorePaths(home);
|
|
33471
|
+
const symlinked = symlinkedStorePaths(home);
|
|
31667
33472
|
process.stdout.write(
|
|
31668
33473
|
`\u2713 Initialized AKA at ${home}
|
|
31669
|
-
settings: ${settingsFile}${settingsCreated ? "" : " (kept existing)"}
|
|
33474
|
+
settings: ${settingsFile}${settingsCreated ? "" : existsSync15(settingsFile) ? " (kept existing)" : " (not written \u2014 another process was writing it)"}
|
|
31670
33475
|
database: ${dbPath(home)}
|
|
31671
33476
|
seeded ${String(policyCount)} default policies, ${String(packCount)} detection pack(s)
|
|
31672
33477
|
` + (updatesAvailable > 0 ? ` \u2B06 ${String(updatesAvailable)} detection pack update(s) available \u2014 review with \`aka detections\`, apply with \`aka detections update --all\`
|
|
31673
33478
|
` : "") + (loose.length > 0 ? ` \u26A0 could not enforce owner-only permissions on ${loose.join(", ")} \u2014 this filesystem rejects chmod, so the store has no at-rest protection here (see the "Data at rest" note in SECURITY.md)
|
|
31674
|
-
` : "")
|
|
33479
|
+
` : "") + symlinkWarnings(symlinked)
|
|
31675
33480
|
);
|
|
31676
33481
|
await offerPluginInstall(values.yes === true);
|
|
31677
33482
|
}
|
|
@@ -31716,7 +33521,7 @@ async function confirm(question) {
|
|
|
31716
33521
|
}
|
|
31717
33522
|
|
|
31718
33523
|
// src/commands/scan.ts
|
|
31719
|
-
import { statSync as
|
|
33524
|
+
import { statSync as statSync11 } from "fs";
|
|
31720
33525
|
import { parseArgs as parseArgs7 } from "util";
|
|
31721
33526
|
function severityRank(s) {
|
|
31722
33527
|
return Severity.options.indexOf(s);
|
|
@@ -31730,7 +33535,7 @@ function renderEgressLine(egress) {
|
|
|
31730
33535
|
const cap = egress.truncated ? ` (capped at ${String(MAX_EGRESS_CALL_SITES_PER_PROJECT)} call sites)` : "";
|
|
31731
33536
|
return `Data shares: ${String(egress.destinations)} destination(s) \xB7 ${String(egress.endpoints)} endpoint(s) \xB7 ${String(egress.callSites)} call site(s)${cap}`;
|
|
31732
33537
|
}
|
|
31733
|
-
function runScan(argv) {
|
|
33538
|
+
async function runScan(argv) {
|
|
31734
33539
|
const { values, positionals } = parseArgs7({
|
|
31735
33540
|
args: argv,
|
|
31736
33541
|
options: {
|
|
@@ -31763,7 +33568,7 @@ function runScan(argv) {
|
|
|
31763
33568
|
failOn = parsed.data;
|
|
31764
33569
|
}
|
|
31765
33570
|
try {
|
|
31766
|
-
|
|
33571
|
+
statSync11(target);
|
|
31767
33572
|
} catch {
|
|
31768
33573
|
process.stderr.write(`aka scan: no such file or directory: ${target}
|
|
31769
33574
|
`);
|
|
@@ -31778,7 +33583,11 @@ function runScan(argv) {
|
|
|
31778
33583
|
let egress;
|
|
31779
33584
|
try {
|
|
31780
33585
|
const { ruleActions } = db.installedPacks.installedRuleset();
|
|
31781
|
-
result = scanPathIntoStore(db, target, {
|
|
33586
|
+
result = await scanPathIntoStore(db, target, {
|
|
33587
|
+
ruleActions,
|
|
33588
|
+
sourceTool: "cli",
|
|
33589
|
+
dataDir: storeDir
|
|
33590
|
+
});
|
|
31782
33591
|
inventory = recordProjectInventory(db, target);
|
|
31783
33592
|
egress = recordProjectEgress(db, target, result.egress, home);
|
|
31784
33593
|
} finally {
|
|
@@ -32649,19 +34458,20 @@ function applyCliUpdate2() {
|
|
|
32649
34458
|
function applyPluginUpdate2(status) {
|
|
32650
34459
|
const agent = findAgent(status.id);
|
|
32651
34460
|
const ref = agent ? pluginRef(agent) : void 0;
|
|
32652
|
-
|
|
34461
|
+
const cliBin = agent?.cliBin;
|
|
34462
|
+
if (!agent || !ref || !cliBin) {
|
|
32653
34463
|
process.stderr.write(`\u2717 ${status.name}: no update coordinates in the registry.
|
|
32654
34464
|
`);
|
|
32655
34465
|
return false;
|
|
32656
34466
|
}
|
|
32657
|
-
if (!
|
|
34467
|
+
if (!createCliPluginManager(cliBin).available()) {
|
|
32658
34468
|
process.stderr.write(
|
|
32659
|
-
`\u2717 ${status.name}: the \`
|
|
34469
|
+
`\u2717 ${status.name}: the \`${cliBin}\` CLI isn't on your PATH \u2014 install ${agent.name}, then run \`${cliBin} plugin update ${ref}\`.
|
|
32660
34470
|
`
|
|
32661
34471
|
);
|
|
32662
34472
|
return false;
|
|
32663
34473
|
}
|
|
32664
|
-
process.stdout.write(`Updating ${status.name} (
|
|
34474
|
+
process.stdout.write(`Updating ${status.name} (${cliBin} plugin update ${ref})\u2026
|
|
32665
34475
|
`);
|
|
32666
34476
|
const { ok } = applyPluginUpdate(status.id, "inherit");
|
|
32667
34477
|
process.stdout.write(ok ? `\u2713 ${status.name} updated.
|
|
@@ -32719,7 +34529,6 @@ async function runShow2(argv, io) {
|
|
|
32719
34529
|
const vault = new SecretVault({
|
|
32720
34530
|
repo: db.secretVault,
|
|
32721
34531
|
keys: createKeyProvider(readWorkspaceSettings(base).vaultKeyCustody, keysDir(base)),
|
|
32722
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
32723
34532
|
// Read live so a consent revocation applies to the very next call.
|
|
32724
34533
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent)
|
|
32725
34534
|
});
|
|
@@ -32807,6 +34616,7 @@ var COMMANDS = {
|
|
|
32807
34616
|
detections: runDetections,
|
|
32808
34617
|
plugins: runPlugins,
|
|
32809
34618
|
dashboard: runDashboard,
|
|
34619
|
+
extension: runExtension,
|
|
32810
34620
|
exception: (argv) => runException(argv),
|
|
32811
34621
|
vault: (argv) => runVault(argv),
|
|
32812
34622
|
tui: runTui,
|