@akasecurity/cli 0.9.4 → 0.9.6
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 +2394 -505
- 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 +31139 -0
- package/native-host/scan-worker.js +18055 -0
- package/package.json +11 -7
- package/web-ui/package.json +14 -7
- package/web-ui/packages/dashboard-ui/package.json +2 -0
- package/web-ui/packages/detections/package.json +3 -2
- package/web-ui/packages/persistence/package.json +3 -2
- package/web-ui/packages/plugin-sdk/package.json +5 -2
- 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 +9 -9
- package/web-ui/web-ui/.next/build-manifest.json +8 -8
- package/web-ui/web-ui/.next/prerender-manifest.json +3 -3
- 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 +9 -9
- 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/333.js +1 -0
- package/web-ui/web-ui/.next/server/chunks/363.js +12 -0
- package/web-ui/web-ui/.next/server/chunks/413.js +1 -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 +4 -4
- 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-66e4c8e40b72a3e8.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-1151de62cd9386c8.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-eaee9ff5ba1056e3.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-7fbcf876de9f09de.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-8f33eba279e45c1d.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-1d468a1a0cf12aea.js → page-bf9c74a239afdab2.js} +1 -1
- package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-e17e9d7c8547bc32.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-4bdf3d2ede57b3c7.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-88b768ca343a8524.js +1 -0
- package/web-ui/web-ui/.next/static/chunks/app/(app)/layout-a346cd78f5f28936.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-f79874ebd61fbf46.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-86e7e860ce98d0a4.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-557e1703a106d286.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-a1956abf86b48b8b.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-fe71f1fd0ab6cde6.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-2473d9063bd5eb69.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/2eca310301b96d98.css +2 -0
- package/web-ui/web-ui/.next/static/{cVKs25XbQsC1083sU5GOD → scT_YT84DnOMXJI2dA75N}/_buildManifest.js +1 -1
- package/web-ui/web-ui/dist/scan-worker.js +18055 -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/330.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/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 → scT_YT84DnOMXJI2dA75N}/_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, 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) {
|
|
@@ -2161,6 +2263,40 @@ function escapeRegExp2(value) {
|
|
|
2161
2263
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2162
2264
|
}
|
|
2163
2265
|
|
|
2266
|
+
// ../packages/detections/src/regex-cache.ts
|
|
2267
|
+
var singles = /* @__PURE__ */ new WeakMap();
|
|
2268
|
+
var keywordLists = /* @__PURE__ */ new WeakMap();
|
|
2269
|
+
var labelLists = /* @__PURE__ */ new WeakMap();
|
|
2270
|
+
function listCache(kind) {
|
|
2271
|
+
return kind === "keyword" ? keywordLists : labelLists;
|
|
2272
|
+
}
|
|
2273
|
+
function memoizedRegExp(owner, build) {
|
|
2274
|
+
const cached2 = singles.get(owner);
|
|
2275
|
+
if (cached2 !== void 0) {
|
|
2276
|
+
cached2.lastIndex = 0;
|
|
2277
|
+
return cached2;
|
|
2278
|
+
}
|
|
2279
|
+
const compiled = build();
|
|
2280
|
+
singles.set(owner, compiled);
|
|
2281
|
+
return compiled;
|
|
2282
|
+
}
|
|
2283
|
+
function memoizedRegExpList(kind, owner, build) {
|
|
2284
|
+
const cache = listCache(kind);
|
|
2285
|
+
const cached2 = cache.get(owner);
|
|
2286
|
+
if (cached2 !== void 0) {
|
|
2287
|
+
if (cached2.stateful) {
|
|
2288
|
+
for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
|
|
2289
|
+
}
|
|
2290
|
+
return cached2.entries;
|
|
2291
|
+
}
|
|
2292
|
+
const entries = build();
|
|
2293
|
+
cache.set(owner, {
|
|
2294
|
+
entries,
|
|
2295
|
+
stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
|
|
2296
|
+
});
|
|
2297
|
+
return entries;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2164
2300
|
// ../packages/detections/src/matchers/limits.ts
|
|
2165
2301
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
2166
2302
|
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
@@ -2171,10 +2307,17 @@ var KeywordMatcher = class {
|
|
|
2171
2307
|
if (rule.matcher.type !== "keyword") return [];
|
|
2172
2308
|
const { keywords, caseSensitive } = rule.matcher;
|
|
2173
2309
|
const spans = [];
|
|
2174
|
-
|
|
2175
|
-
|
|
2310
|
+
const compiled = memoizedRegExpList(
|
|
2311
|
+
"keyword",
|
|
2312
|
+
rule.matcher,
|
|
2313
|
+
() => keywords.map((kw) => {
|
|
2314
|
+
if (kw.length === 0) return void 0;
|
|
2315
|
+
return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
2316
|
+
})
|
|
2317
|
+
);
|
|
2318
|
+
for (const re of compiled) {
|
|
2319
|
+
if (re === void 0) continue;
|
|
2176
2320
|
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
2177
|
-
const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
2178
2321
|
let m;
|
|
2179
2322
|
while ((m = re.exec(text)) !== null) {
|
|
2180
2323
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -2190,7 +2333,10 @@ var RegexMatcher = class {
|
|
|
2190
2333
|
match(text, rule) {
|
|
2191
2334
|
if (rule.matcher.type !== "regex") return [];
|
|
2192
2335
|
const { pattern, flags, captureGroup } = rule.matcher;
|
|
2193
|
-
const re =
|
|
2336
|
+
const re = memoizedRegExp(
|
|
2337
|
+
rule.matcher,
|
|
2338
|
+
() => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
|
|
2339
|
+
);
|
|
2194
2340
|
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
2195
2341
|
const spans = [];
|
|
2196
2342
|
let m;
|
|
@@ -2298,11 +2444,15 @@ function isCorroborated(candidate, candidates, text) {
|
|
|
2298
2444
|
const labels = req.labels;
|
|
2299
2445
|
if (labels && labels.length > 0) {
|
|
2300
2446
|
const haystack = text.slice(Math.max(0, winStart), winEnd);
|
|
2301
|
-
for (const
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2447
|
+
for (const re of memoizedRegExpList(
|
|
2448
|
+
"label",
|
|
2449
|
+
req,
|
|
2450
|
+
() => labels.map((label) => {
|
|
2451
|
+
const trimmed = label.trim();
|
|
2452
|
+
return trimmed.length === 0 ? void 0 : new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
|
|
2453
|
+
})
|
|
2454
|
+
)) {
|
|
2455
|
+
if (re?.test(haystack)) return true;
|
|
2306
2456
|
}
|
|
2307
2457
|
}
|
|
2308
2458
|
return false;
|
|
@@ -2486,6 +2636,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
2486
2636
|
{
|
|
2487
2637
|
tag: "0018_serious_tana_nile",
|
|
2488
2638
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
2639
|
+
},
|
|
2640
|
+
{
|
|
2641
|
+
tag: "0019_audit_started_at_index",
|
|
2642
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
2643
|
+
},
|
|
2644
|
+
{
|
|
2645
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
2646
|
+
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
2647
|
}
|
|
2490
2648
|
];
|
|
2491
2649
|
|
|
@@ -17346,7 +17504,17 @@ var Finding = external_exports.object({
|
|
|
17346
17504
|
}).meta({ id: "Finding" });
|
|
17347
17505
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
17348
17506
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
17349
|
-
var FindingProvider = external_exports.enum([
|
|
17507
|
+
var FindingProvider = external_exports.enum([
|
|
17508
|
+
"claudecode",
|
|
17509
|
+
"claudedesktop",
|
|
17510
|
+
"cursor",
|
|
17511
|
+
"copilot",
|
|
17512
|
+
"chatgpt",
|
|
17513
|
+
"claudeai",
|
|
17514
|
+
"codex",
|
|
17515
|
+
"antigravity",
|
|
17516
|
+
"api"
|
|
17517
|
+
]).meta({ id: "FindingProvider" });
|
|
17350
17518
|
var FindingCategory = external_exports.enum([
|
|
17351
17519
|
"secret",
|
|
17352
17520
|
"pii",
|
|
@@ -17400,7 +17568,16 @@ var FindingInstance = external_exports.object({
|
|
|
17400
17568
|
confidence: external_exports.number().min(0).max(1),
|
|
17401
17569
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
17402
17570
|
// that predate the resolution feature stay valid.
|
|
17403
|
-
status: FindingStatus.optional()
|
|
17571
|
+
status: FindingStatus.optional(),
|
|
17572
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
17573
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
17574
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
17575
|
+
// MOST RECENT detection event, not the first.
|
|
17576
|
+
eventId: external_exports.string().optional(),
|
|
17577
|
+
// The session that event belongs to, when it has one — the seam a
|
|
17578
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
17579
|
+
// outside a session.
|
|
17580
|
+
sessionId: external_exports.string().optional()
|
|
17404
17581
|
}).meta({ id: "FindingInstance" });
|
|
17405
17582
|
var FindingGroup = external_exports.object({
|
|
17406
17583
|
id: external_exports.string(),
|
|
@@ -17444,7 +17621,11 @@ var FindingFacets = external_exports.object({
|
|
|
17444
17621
|
// for every instance, so every group lands in a bucket; a status-less
|
|
17445
17622
|
// group (possible only for callers whose rows carry no statuses) is
|
|
17446
17623
|
// counted under no value.
|
|
17447
|
-
status: external_exports.array(FindingFacetItem)
|
|
17624
|
+
status: external_exports.array(FindingFacetItem),
|
|
17625
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
17626
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
17627
|
+
// because a group spans tools.
|
|
17628
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
17448
17629
|
}).meta({ id: "FindingFacets" });
|
|
17449
17630
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
17450
17631
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -17462,6 +17643,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
17462
17643
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
17463
17644
|
// session → findings drilldown). Findings without a session never match.
|
|
17464
17645
|
sessionId: external_exports.string().optional(),
|
|
17646
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
17647
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
17648
|
+
// means all time — this list has no default window.
|
|
17649
|
+
from: external_exports.iso.datetime().optional(),
|
|
17650
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
17651
|
+
// has already advanced past its sort position. This is what keeps the
|
|
17652
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
17653
|
+
// paginates: the target group is appended out of sort order rather than
|
|
17654
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
17655
|
+
includeId: external_exports.string().optional(),
|
|
17465
17656
|
groupBy: external_exports.literal("type").optional(),
|
|
17466
17657
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
17467
17658
|
cursor: external_exports.string().optional()
|
|
@@ -17506,15 +17697,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
17506
17697
|
detection: FindingDetectionRef,
|
|
17507
17698
|
policy: FindingPolicyRef
|
|
17508
17699
|
}).meta({ id: "FindingInstanceDetail" });
|
|
17700
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
17701
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
17702
|
+
severity: external_exports.array(Severity).optional(),
|
|
17703
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
17704
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
17705
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
17706
|
+
action: external_exports.array(FindingAction).optional(),
|
|
17707
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
17708
|
+
// the grouped query's group-level fold.
|
|
17709
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
17710
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
17711
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
17712
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
17713
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
17714
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
17715
|
+
repo: external_exports.string().optional(),
|
|
17716
|
+
file: 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(200).optional(),
|
|
17721
|
+
cursor: external_exports.string().optional()
|
|
17722
|
+
});
|
|
17723
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
17724
|
+
// Instances matching the filters across the whole scope, not just this
|
|
17725
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
17726
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
17727
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
17728
|
+
// dimension still excludes its own filter.
|
|
17729
|
+
facets: FindingFacets,
|
|
17730
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
17731
|
+
nextCursor: external_exports.string().nullable()
|
|
17732
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
17733
|
+
var FindingLocationFile = external_exports.object({
|
|
17734
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
17735
|
+
// with no file attribution).
|
|
17736
|
+
file: external_exports.string(),
|
|
17737
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
17738
|
+
maxSeverity: Severity,
|
|
17739
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
17740
|
+
// Folded from the instances' derived statuses with the same
|
|
17741
|
+
// open-dominates precedence a group uses.
|
|
17742
|
+
status: FindingStatus.optional(),
|
|
17743
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
17744
|
+
// chips, and the count is what conveys scale.
|
|
17745
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
17746
|
+
}).meta({ id: "FindingLocationFile" });
|
|
17747
|
+
var FindingLocationRepo = external_exports.object({
|
|
17748
|
+
/** Empty when the instances carried no repo attribute. */
|
|
17749
|
+
repo: external_exports.string(),
|
|
17750
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
17751
|
+
maxSeverity: Severity,
|
|
17752
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
17753
|
+
status: FindingStatus.optional(),
|
|
17754
|
+
files: external_exports.array(FindingLocationFile)
|
|
17755
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
17756
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
17757
|
+
severity: external_exports.array(Severity).optional(),
|
|
17758
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
17759
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
17760
|
+
action: external_exports.array(FindingAction).optional(),
|
|
17761
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
17762
|
+
// instances that match, and folds its status from those.
|
|
17763
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
17764
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
17765
|
+
q: external_exports.string().optional(),
|
|
17766
|
+
sessionId: external_exports.string().optional(),
|
|
17767
|
+
from: external_exports.iso.datetime().optional(),
|
|
17768
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
17769
|
+
});
|
|
17770
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
17771
|
+
totals: external_exports.object({
|
|
17772
|
+
findings: external_exports.number().int().nonnegative(),
|
|
17773
|
+
repos: external_exports.number().int().nonnegative(),
|
|
17774
|
+
files: external_exports.number().int().nonnegative()
|
|
17775
|
+
}),
|
|
17776
|
+
/** Sorted by max severity, then most recent. */
|
|
17777
|
+
items: external_exports.array(FindingLocationRepo),
|
|
17778
|
+
/** Whether `limit` truncated the repo list. */
|
|
17779
|
+
hasMore: external_exports.boolean()
|
|
17780
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
17509
17781
|
|
|
17510
17782
|
// ../packages/schema/src/zod/harness-map.ts
|
|
17511
|
-
var Harness = external_exports.enum([
|
|
17783
|
+
var Harness = external_exports.enum([
|
|
17784
|
+
"claudecode",
|
|
17785
|
+
"cursor",
|
|
17786
|
+
"copilot",
|
|
17787
|
+
"codex",
|
|
17788
|
+
"antigravity",
|
|
17789
|
+
"windsurf",
|
|
17790
|
+
"claudedesktop",
|
|
17791
|
+
"chatgpt",
|
|
17792
|
+
"claudeai",
|
|
17793
|
+
"api"
|
|
17794
|
+
]).meta({ id: "Harness" });
|
|
17512
17795
|
var TOOL_TO_HARNESS = {
|
|
17513
17796
|
"claude-code": "claudecode",
|
|
17514
17797
|
"claude-desktop": "claudedesktop",
|
|
17515
17798
|
"github-copilot": "copilot",
|
|
17516
17799
|
cursor: "cursor",
|
|
17517
|
-
chatgpt: "chatgpt"
|
|
17800
|
+
chatgpt: "chatgpt",
|
|
17801
|
+
codex: "codex",
|
|
17802
|
+
antigravity: "antigravity",
|
|
17803
|
+
"claude-ai": "claudeai"
|
|
17518
17804
|
};
|
|
17519
17805
|
|
|
17520
17806
|
// ../packages/schema/src/zod/meta.ts
|
|
@@ -17972,7 +18258,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
17972
18258
|
// ../packages/schema/src/zod/event.ts
|
|
17973
18259
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
17974
18260
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
17975
|
-
var SourceTool = external_exports.enum([
|
|
18261
|
+
var SourceTool = external_exports.enum([
|
|
18262
|
+
"claude-code",
|
|
18263
|
+
"claude-desktop",
|
|
18264
|
+
"cursor",
|
|
18265
|
+
"chatgpt",
|
|
18266
|
+
"claude-ai",
|
|
18267
|
+
"github-copilot",
|
|
18268
|
+
"codex",
|
|
18269
|
+
"antigravity",
|
|
18270
|
+
"cli",
|
|
18271
|
+
"unknown"
|
|
18272
|
+
]).meta({ id: "SourceTool" });
|
|
17976
18273
|
var EventMetadata = external_exports.object({
|
|
17977
18274
|
sessionId: external_exports.string().optional(),
|
|
17978
18275
|
repo: external_exports.string().optional(),
|
|
@@ -18043,7 +18340,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
18043
18340
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
18044
18341
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
18045
18342
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
18046
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
18343
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
18047
18344
|
var AccessCounts = external_exports.object({
|
|
18048
18345
|
open: external_exports.number().int().nonnegative(),
|
|
18049
18346
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -18312,9 +18609,19 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
18312
18609
|
useCount: true,
|
|
18313
18610
|
conditions: true
|
|
18314
18611
|
});
|
|
18612
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
18315
18613
|
function isMatchableUnder(keyVersion, key) {
|
|
18316
18614
|
return key.status === "present" && key.version === keyVersion;
|
|
18317
18615
|
}
|
|
18616
|
+
var LEDGER_WINDOW_HOURS = 24;
|
|
18617
|
+
function rotationBlockedLedgerNote(stillApprovable) {
|
|
18618
|
+
if (stillApprovable <= 0) {
|
|
18619
|
+
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.";
|
|
18620
|
+
}
|
|
18621
|
+
const window = `from the last ${String(LEDGER_WINDOW_HOURS)} hours`;
|
|
18622
|
+
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`;
|
|
18623
|
+
return `${subject}. They stay listed as a record of what was blocked \u2014 trigger the detection again to approve under the new key.`;
|
|
18624
|
+
}
|
|
18318
18625
|
|
|
18319
18626
|
// ../packages/schema/src/zod/rule.ts
|
|
18320
18627
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -19125,6 +19432,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
19125
19432
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
19126
19433
|
}).meta({ id: "EgressWriteSummary" });
|
|
19127
19434
|
|
|
19435
|
+
// ../packages/schema/src/zod/exception-action.ts
|
|
19436
|
+
var confirmation = external_exports.string().optional();
|
|
19437
|
+
var ApproveBlockedInput = external_exports.object({
|
|
19438
|
+
reference: external_exports.string(),
|
|
19439
|
+
scope: external_exports.string(),
|
|
19440
|
+
reason: external_exports.string(),
|
|
19441
|
+
confirmation
|
|
19442
|
+
});
|
|
19443
|
+
var AddExceptionInput = external_exports.object({
|
|
19444
|
+
ruleId: external_exports.string(),
|
|
19445
|
+
value: external_exports.string(),
|
|
19446
|
+
scope: external_exports.string(),
|
|
19447
|
+
reason: external_exports.string(),
|
|
19448
|
+
confirmation
|
|
19449
|
+
});
|
|
19450
|
+
var GrantRevealInput = external_exports.object({
|
|
19451
|
+
pointer: external_exports.string(),
|
|
19452
|
+
scope: external_exports.string(),
|
|
19453
|
+
justification: external_exports.string(),
|
|
19454
|
+
confirmation
|
|
19455
|
+
});
|
|
19456
|
+
var RevokeExceptionInput = external_exports.object({
|
|
19457
|
+
id: external_exports.string(),
|
|
19458
|
+
reason: external_exports.string()
|
|
19459
|
+
});
|
|
19460
|
+
var RotateKeyInput = external_exports.object({
|
|
19461
|
+
confirmation: external_exports.string()
|
|
19462
|
+
});
|
|
19463
|
+
|
|
19128
19464
|
// ../packages/schema/src/zod/findings-group-build.ts
|
|
19129
19465
|
function toApiAction(dbVal) {
|
|
19130
19466
|
const map2 = {
|
|
@@ -19180,6 +19516,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
19180
19516
|
repo: r.repo,
|
|
19181
19517
|
file: r.file,
|
|
19182
19518
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
19519
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
19520
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
19183
19521
|
action: toApiAction(effectiveDbAction),
|
|
19184
19522
|
detectedAt: r.occurredAt,
|
|
19185
19523
|
confidence: r.confidence,
|
|
@@ -19311,14 +19649,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
19311
19649
|
}
|
|
19312
19650
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
19313
19651
|
var SEVERITY_RANK2 = SEVERITY_ORDER;
|
|
19652
|
+
function compareFindingGroupOrder(a, b) {
|
|
19653
|
+
const rankA = SEVERITY_RANK2[a.severity] ?? -1;
|
|
19654
|
+
const rankB = SEVERITY_RANK2[b.severity] ?? -1;
|
|
19655
|
+
const severityDiff = rankA - rankB;
|
|
19656
|
+
if (severityDiff !== 0) return severityDiff;
|
|
19657
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
19658
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
19659
|
+
return a.id.localeCompare(b.id);
|
|
19660
|
+
}
|
|
19314
19661
|
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
|
-
});
|
|
19662
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
19322
19663
|
}
|
|
19323
19664
|
function computeFindingFacets(allGroups, opts) {
|
|
19324
19665
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -19374,15 +19715,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
19374
19715
|
for (const g of forStatus) {
|
|
19375
19716
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
19376
19717
|
}
|
|
19377
|
-
const
|
|
19718
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
19719
|
+
return {
|
|
19720
|
+
severity: toItems2(severityMap),
|
|
19721
|
+
provider: toItems2(providerMap),
|
|
19722
|
+
action: toItems2(actionMap),
|
|
19723
|
+
subtype: toItems2(subtypeMap),
|
|
19724
|
+
status: toItems2(statusMap)
|
|
19725
|
+
};
|
|
19726
|
+
}
|
|
19727
|
+
|
|
19728
|
+
// ../packages/schema/src/zod/findings-flat-build.ts
|
|
19729
|
+
function rowHaystack(row) {
|
|
19730
|
+
return [
|
|
19731
|
+
row.ruleId,
|
|
19732
|
+
row.category,
|
|
19733
|
+
row.maskedMatch,
|
|
19734
|
+
row.repo,
|
|
19735
|
+
row.file,
|
|
19736
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
19737
|
+
row.id
|
|
19738
|
+
].join(" ").toLowerCase();
|
|
19739
|
+
}
|
|
19740
|
+
function matchesDimension(row, opts, dimension) {
|
|
19741
|
+
switch (dimension) {
|
|
19742
|
+
case "severity":
|
|
19743
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
19744
|
+
case "subtype":
|
|
19745
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
19746
|
+
case "providers":
|
|
19747
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
19748
|
+
case "actions":
|
|
19749
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
19750
|
+
case "statuses":
|
|
19751
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
19752
|
+
case "tools":
|
|
19753
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
19754
|
+
case "repo":
|
|
19755
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
19756
|
+
case "file":
|
|
19757
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
19758
|
+
case "q":
|
|
19759
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
19760
|
+
}
|
|
19761
|
+
}
|
|
19762
|
+
var DIMENSIONS = [
|
|
19763
|
+
"severity",
|
|
19764
|
+
"subtype",
|
|
19765
|
+
"providers",
|
|
19766
|
+
"actions",
|
|
19767
|
+
"statuses",
|
|
19768
|
+
"tools",
|
|
19769
|
+
"repo",
|
|
19770
|
+
"file",
|
|
19771
|
+
"q"
|
|
19772
|
+
];
|
|
19773
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
19774
|
+
for (const dimension of DIMENSIONS) {
|
|
19775
|
+
if (dimension === except) continue;
|
|
19776
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
19777
|
+
}
|
|
19778
|
+
return true;
|
|
19779
|
+
}
|
|
19780
|
+
function toItems(counts) {
|
|
19781
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
19782
|
+
}
|
|
19783
|
+
function bump(counts, value) {
|
|
19784
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
19785
|
+
}
|
|
19786
|
+
function createInstanceFacetAccumulator(opts) {
|
|
19787
|
+
const severity = /* @__PURE__ */ new Map();
|
|
19788
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
19789
|
+
const provider = /* @__PURE__ */ new Map();
|
|
19790
|
+
const action = /* @__PURE__ */ new Map();
|
|
19791
|
+
const status = /* @__PURE__ */ new Map();
|
|
19792
|
+
const tool = /* @__PURE__ */ new Map();
|
|
19793
|
+
return {
|
|
19794
|
+
add(row) {
|
|
19795
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
19796
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
19797
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
19798
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
19799
|
+
}
|
|
19800
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
19801
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
19802
|
+
bump(status, row.status);
|
|
19803
|
+
}
|
|
19804
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
19805
|
+
bump(tool, row.toolName);
|
|
19806
|
+
}
|
|
19807
|
+
},
|
|
19808
|
+
facets: () => ({
|
|
19809
|
+
severity: toItems(severity),
|
|
19810
|
+
subtype: toItems(subtype),
|
|
19811
|
+
provider: toItems(provider),
|
|
19812
|
+
action: toItems(action),
|
|
19813
|
+
status: toItems(status),
|
|
19814
|
+
tool: toItems(tool)
|
|
19815
|
+
})
|
|
19816
|
+
};
|
|
19817
|
+
}
|
|
19818
|
+
function toInstanceDetail(row) {
|
|
19819
|
+
const category = toApiCategory(row.category);
|
|
19820
|
+
return {
|
|
19821
|
+
id: row.id,
|
|
19822
|
+
provider: toApiProvider(row.sourceTool),
|
|
19823
|
+
repo: row.repo,
|
|
19824
|
+
file: row.file,
|
|
19825
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
19826
|
+
eventId: row.eventId,
|
|
19827
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
19828
|
+
action: toApiAction(row.actionTaken),
|
|
19829
|
+
detectedAt: row.occurredAt,
|
|
19830
|
+
confidence: row.confidence,
|
|
19831
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
19832
|
+
groupId: row.ruleId,
|
|
19833
|
+
category,
|
|
19834
|
+
subtype: row.ruleId,
|
|
19835
|
+
severity: row.severity,
|
|
19836
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
19837
|
+
detection: { id: row.ruleId, name: null },
|
|
19838
|
+
policy: { id: `category:${category}`, name: category }
|
|
19839
|
+
};
|
|
19840
|
+
}
|
|
19841
|
+
var SEVERITY_ORDER2 = {
|
|
19842
|
+
critical: 0,
|
|
19843
|
+
high: 1,
|
|
19844
|
+
medium: 2,
|
|
19845
|
+
low: 3
|
|
19846
|
+
};
|
|
19847
|
+
function newLocationAccumulator() {
|
|
19378
19848
|
return {
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
19849
|
+
instanceCount: 0,
|
|
19850
|
+
// Sorts after every known severity, so the first row always wins the
|
|
19851
|
+
// comparison below rather than an unknown value pinning the location.
|
|
19852
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
19853
|
+
maxSeverity: "low",
|
|
19854
|
+
latestDetectedAt: "",
|
|
19855
|
+
statuses: [],
|
|
19856
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
19384
19857
|
};
|
|
19385
19858
|
}
|
|
19859
|
+
function addToLocation(acc, row) {
|
|
19860
|
+
acc.instanceCount += 1;
|
|
19861
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
19862
|
+
if (rank < acc.maxSeverityRank) {
|
|
19863
|
+
acc.maxSeverityRank = rank;
|
|
19864
|
+
acc.maxSeverity = row.severity;
|
|
19865
|
+
}
|
|
19866
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
19867
|
+
acc.statuses.push(row.status);
|
|
19868
|
+
acc.ruleIds.add(row.ruleId);
|
|
19869
|
+
}
|
|
19386
19870
|
|
|
19387
19871
|
// ../packages/schema/src/zod/installed-pack.ts
|
|
19388
19872
|
var InstalledPack = external_exports.object({
|
|
@@ -19523,6 +20007,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
19523
20007
|
revealGrantId: external_exports.string().nullable(),
|
|
19524
20008
|
sightings: external_exports.array(VaultSighting)
|
|
19525
20009
|
});
|
|
20010
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
20011
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
20012
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
20013
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
20014
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
20015
|
+
// Opaque; names the last row of the page just served.
|
|
20016
|
+
cursor: external_exports.string().optional()
|
|
20017
|
+
});
|
|
20018
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
20019
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
20020
|
+
// independent, so paging never changes what the count claims.
|
|
20021
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
20022
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
20023
|
+
// `null` once the last page is reached.
|
|
20024
|
+
nextCursor: external_exports.string().nullable()
|
|
20025
|
+
});
|
|
20026
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
20027
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
20028
|
+
cursor: external_exports.string().optional()
|
|
20029
|
+
});
|
|
20030
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
20031
|
+
// Reused values across the whole store — the number the section's claim
|
|
20032
|
+
// ("values detected in more than one place") is about.
|
|
20033
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
20034
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
20035
|
+
nextCursor: external_exports.string().nullable()
|
|
20036
|
+
});
|
|
20037
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
20038
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
20039
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
20040
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
20041
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
20042
|
+
includeBatched: external_exports.boolean().optional(),
|
|
20043
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
20044
|
+
cursor: external_exports.string().optional()
|
|
20045
|
+
});
|
|
20046
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
20047
|
+
items: external_exports.array(VaultDeref),
|
|
20048
|
+
nextCursor: external_exports.string().nullable(),
|
|
20049
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
20050
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
20051
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
20052
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
20053
|
+
});
|
|
19526
20054
|
var VaultKeyCustody = external_exports.string();
|
|
19527
20055
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
19528
20056
|
var VAULT_CONSENT_VERSION = 1;
|
|
@@ -19902,7 +20430,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
19902
20430
|
// Omit for both kinds.
|
|
19903
20431
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
19904
20432
|
});
|
|
19905
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
20433
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
19906
20434
|
var ScanCoverageProvider = external_exports.object({
|
|
19907
20435
|
provider: Provider,
|
|
19908
20436
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -20214,9 +20742,8 @@ var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].m
|
|
|
20214
20742
|
);
|
|
20215
20743
|
|
|
20216
20744
|
// ../packages/persistence/src/database.ts
|
|
20217
|
-
import { randomUUID as
|
|
20218
|
-
import {
|
|
20219
|
-
import { join as join2, sep } from "path";
|
|
20745
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
20746
|
+
import { join as join3, sep } from "path";
|
|
20220
20747
|
import { DatabaseSync } from "node:sqlite";
|
|
20221
20748
|
|
|
20222
20749
|
// ../packages/persistence/src/ids.ts
|
|
@@ -20250,74 +20777,280 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
20250
20777
|
);
|
|
20251
20778
|
}
|
|
20252
20779
|
|
|
20253
|
-
// ../packages/persistence/src/internal/
|
|
20254
|
-
|
|
20255
|
-
|
|
20780
|
+
// ../packages/persistence/src/internal/snapshot.ts
|
|
20781
|
+
import { randomUUID } from "crypto";
|
|
20782
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
20783
|
+
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
20784
|
+
|
|
20785
|
+
// ../packages/persistence/src/paths.ts
|
|
20786
|
+
import {
|
|
20787
|
+
chmodSync,
|
|
20788
|
+
linkSync,
|
|
20789
|
+
lstatSync,
|
|
20790
|
+
mkdirSync,
|
|
20791
|
+
renameSync,
|
|
20792
|
+
rmSync,
|
|
20793
|
+
writeFileSync
|
|
20794
|
+
} from "fs";
|
|
20795
|
+
import { threadId } from "worker_threads";
|
|
20796
|
+
var DATA_DIR_MODE = 448;
|
|
20797
|
+
var DATA_FILE_MODE = 384;
|
|
20798
|
+
var DB_FILENAME = "aka.db";
|
|
20799
|
+
function isSymlink(path) {
|
|
20800
|
+
try {
|
|
20801
|
+
return lstatSync(path).isSymbolicLink();
|
|
20802
|
+
} catch {
|
|
20803
|
+
return false;
|
|
20804
|
+
}
|
|
20256
20805
|
}
|
|
20257
|
-
function
|
|
20258
|
-
|
|
20806
|
+
function chmodBestEffort(path, mode) {
|
|
20807
|
+
if (isSymlink(path)) return;
|
|
20808
|
+
try {
|
|
20809
|
+
chmodSync(path, mode);
|
|
20810
|
+
} catch {
|
|
20811
|
+
}
|
|
20259
20812
|
}
|
|
20260
|
-
function
|
|
20261
|
-
|
|
20813
|
+
function tightenDir(dir) {
|
|
20814
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
20262
20815
|
}
|
|
20263
|
-
function
|
|
20264
|
-
|
|
20816
|
+
function ensureDataDirSync(dir) {
|
|
20817
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
20818
|
+
tightenDir(dir);
|
|
20265
20819
|
}
|
|
20266
|
-
|
|
20267
|
-
|
|
20268
|
-
|
|
20269
|
-
function
|
|
20270
|
-
|
|
20271
|
-
|
|
20272
|
-
|
|
20820
|
+
function dbSidecars(file2) {
|
|
20821
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
20822
|
+
}
|
|
20823
|
+
function tightenFile(file2) {
|
|
20824
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
20825
|
+
}
|
|
20826
|
+
function tightenPerms(file2) {
|
|
20827
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
20828
|
+
}
|
|
20829
|
+
function writeOwnerOnlyFileSync(file2, data) {
|
|
20830
|
+
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
20831
|
+
try {
|
|
20832
|
+
rmSync(tmp, { force: true });
|
|
20833
|
+
} catch {
|
|
20834
|
+
}
|
|
20835
|
+
try {
|
|
20836
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
20837
|
+
renameSync(tmp, file2);
|
|
20838
|
+
} finally {
|
|
20273
20839
|
try {
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
} catch (error51) {
|
|
20277
|
-
try {
|
|
20278
|
-
db.exec(`ROLLBACK TO ${savepoint}`);
|
|
20279
|
-
db.exec(`RELEASE ${savepoint}`);
|
|
20280
|
-
} catch {
|
|
20281
|
-
}
|
|
20282
|
-
throw error51;
|
|
20840
|
+
rmSync(tmp, { force: true });
|
|
20841
|
+
} catch {
|
|
20283
20842
|
}
|
|
20284
|
-
return;
|
|
20285
20843
|
}
|
|
20286
|
-
|
|
20844
|
+
tightenFile(file2);
|
|
20845
|
+
}
|
|
20846
|
+
function classifyOccupant(file2) {
|
|
20287
20847
|
try {
|
|
20288
|
-
|
|
20289
|
-
|
|
20290
|
-
} catch (
|
|
20848
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
20849
|
+
return { kind: "gone" };
|
|
20850
|
+
} catch (err) {
|
|
20851
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
20852
|
+
return { kind: "unknown", cause: err };
|
|
20853
|
+
}
|
|
20854
|
+
}
|
|
20855
|
+
var KeyUnclaimableError = class extends Error {
|
|
20856
|
+
code = "key-unclaimable";
|
|
20857
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
20858
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
20859
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
20860
|
+
// captured and then lost, which is worse than its plain absence.
|
|
20861
|
+
constructor(message, cause) {
|
|
20862
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
20863
|
+
this.name = "KeyUnclaimableError";
|
|
20864
|
+
}
|
|
20865
|
+
};
|
|
20866
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
20867
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
20868
|
+
try {
|
|
20869
|
+
rmSync(tmp, { force: true });
|
|
20870
|
+
} catch {
|
|
20871
|
+
}
|
|
20872
|
+
let created;
|
|
20873
|
+
try {
|
|
20874
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
20875
|
+
created = publishByLink(tmp, file2, data);
|
|
20876
|
+
} finally {
|
|
20291
20877
|
try {
|
|
20292
|
-
|
|
20878
|
+
rmSync(tmp, { force: true });
|
|
20293
20879
|
} catch {
|
|
20294
20880
|
}
|
|
20295
|
-
throw error51;
|
|
20296
20881
|
}
|
|
20882
|
+
if (created) tightenFile(file2);
|
|
20883
|
+
return created;
|
|
20297
20884
|
}
|
|
20298
|
-
|
|
20299
|
-
|
|
20885
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
20886
|
+
function publishByLink(tmp, file2, data) {
|
|
20300
20887
|
try {
|
|
20301
|
-
|
|
20888
|
+
linkSync(tmp, file2);
|
|
20302
20889
|
return true;
|
|
20303
|
-
} catch (
|
|
20304
|
-
|
|
20305
|
-
return false;
|
|
20890
|
+
} catch (err) {
|
|
20891
|
+
const code = err.code;
|
|
20892
|
+
if (code === "EEXIST") return false;
|
|
20893
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
20894
|
+
}
|
|
20895
|
+
try {
|
|
20896
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
20897
|
+
return true;
|
|
20898
|
+
} catch (err) {
|
|
20899
|
+
if (err.code === "EEXIST") return false;
|
|
20900
|
+
throw err;
|
|
20306
20901
|
}
|
|
20307
20902
|
}
|
|
20308
20903
|
|
|
20309
|
-
// ../packages/persistence/src/internal/
|
|
20310
|
-
function
|
|
20311
|
-
|
|
20312
|
-
`);
|
|
20904
|
+
// ../packages/persistence/src/internal/snapshot.ts
|
|
20905
|
+
function backupPath(file2, tag) {
|
|
20906
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
20313
20907
|
}
|
|
20314
|
-
|
|
20315
|
-
|
|
20316
|
-
|
|
20317
|
-
const
|
|
20318
|
-
|
|
20319
|
-
|
|
20320
|
-
|
|
20908
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
20909
|
+
function reapStalePartials(file2) {
|
|
20910
|
+
const dir = dirname2(file2);
|
|
20911
|
+
const prefix = `${basename(file2)}.`;
|
|
20912
|
+
let entries;
|
|
20913
|
+
try {
|
|
20914
|
+
entries = readdirSync2(dir);
|
|
20915
|
+
} catch {
|
|
20916
|
+
return;
|
|
20917
|
+
}
|
|
20918
|
+
for (const name of entries) {
|
|
20919
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
20920
|
+
const partial2 = join2(dir, name);
|
|
20921
|
+
try {
|
|
20922
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
20923
|
+
rmSync2(partial2, { force: true });
|
|
20924
|
+
}
|
|
20925
|
+
} catch {
|
|
20926
|
+
}
|
|
20927
|
+
}
|
|
20928
|
+
}
|
|
20929
|
+
function snapshotStore(db, backup) {
|
|
20930
|
+
const partial2 = `${backup}.partial`;
|
|
20931
|
+
try {
|
|
20932
|
+
rmSync2(partial2, { force: true });
|
|
20933
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
20934
|
+
tightenFile(partial2);
|
|
20935
|
+
renameSync2(partial2, backup);
|
|
20936
|
+
} catch (error51) {
|
|
20937
|
+
try {
|
|
20938
|
+
rmSync2(partial2, { force: true });
|
|
20939
|
+
} catch {
|
|
20940
|
+
}
|
|
20941
|
+
throw error51;
|
|
20942
|
+
}
|
|
20943
|
+
}
|
|
20944
|
+
function moveStoreAside(file2, backup) {
|
|
20945
|
+
const undo = [];
|
|
20946
|
+
renameSync2(file2, backup);
|
|
20947
|
+
undo.push([backup, file2]);
|
|
20948
|
+
try {
|
|
20949
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
20950
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
20951
|
+
try {
|
|
20952
|
+
renameSync2(sidecar, moved);
|
|
20953
|
+
undo.push([moved, sidecar]);
|
|
20954
|
+
} catch {
|
|
20955
|
+
rmSync2(sidecar, { force: true });
|
|
20956
|
+
}
|
|
20957
|
+
}
|
|
20958
|
+
} catch (error51) {
|
|
20959
|
+
for (const [from, to] of undo.reverse()) {
|
|
20960
|
+
try {
|
|
20961
|
+
renameSync2(from, to);
|
|
20962
|
+
} catch {
|
|
20963
|
+
}
|
|
20964
|
+
}
|
|
20965
|
+
throw error51;
|
|
20966
|
+
}
|
|
20967
|
+
tightenPerms(backup);
|
|
20968
|
+
}
|
|
20969
|
+
function discardStore(file2, backup) {
|
|
20970
|
+
try {
|
|
20971
|
+
rmSync2(file2, { force: true });
|
|
20972
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
20973
|
+
rmSync2(sidecar, { force: true });
|
|
20974
|
+
}
|
|
20975
|
+
} catch (error51) {
|
|
20976
|
+
if (existsSync2(file2)) {
|
|
20977
|
+
try {
|
|
20978
|
+
rmSync2(backup, { force: true });
|
|
20979
|
+
} catch {
|
|
20980
|
+
}
|
|
20981
|
+
}
|
|
20982
|
+
throw error51;
|
|
20983
|
+
}
|
|
20984
|
+
}
|
|
20985
|
+
|
|
20986
|
+
// ../packages/persistence/src/internal/sql-text.ts
|
|
20987
|
+
function escapeLikePattern(s) {
|
|
20988
|
+
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
20989
|
+
}
|
|
20990
|
+
function placeholders(n) {
|
|
20991
|
+
return Array.from({ length: n }, () => "?").join(", ");
|
|
20992
|
+
}
|
|
20993
|
+
function containsPattern(q) {
|
|
20994
|
+
return `%${escapeLikePattern(q)}%`;
|
|
20995
|
+
}
|
|
20996
|
+
function likeAny(exprs) {
|
|
20997
|
+
return `(${exprs.map((e) => `${e} LIKE ? ESCAPE '\\'`).join(" OR ")})`;
|
|
20998
|
+
}
|
|
20999
|
+
|
|
21000
|
+
// ../packages/persistence/src/internal/transactions.ts
|
|
21001
|
+
var savepointSeq = 0;
|
|
21002
|
+
function withTransaction(db, fn, mode = "DEFERRED") {
|
|
21003
|
+
if (db.isTransaction) {
|
|
21004
|
+
const savepoint = `aka_sp_${String(savepointSeq += 1)}`;
|
|
21005
|
+
db.exec(`SAVEPOINT ${savepoint}`);
|
|
21006
|
+
try {
|
|
21007
|
+
fn();
|
|
21008
|
+
db.exec(`RELEASE ${savepoint}`);
|
|
21009
|
+
} catch (error51) {
|
|
21010
|
+
try {
|
|
21011
|
+
db.exec(`ROLLBACK TO ${savepoint}`);
|
|
21012
|
+
db.exec(`RELEASE ${savepoint}`);
|
|
21013
|
+
} catch {
|
|
21014
|
+
}
|
|
21015
|
+
throw error51;
|
|
21016
|
+
}
|
|
21017
|
+
return;
|
|
21018
|
+
}
|
|
21019
|
+
db.exec(mode === "IMMEDIATE" ? "BEGIN IMMEDIATE" : "BEGIN");
|
|
21020
|
+
try {
|
|
21021
|
+
fn();
|
|
21022
|
+
db.exec("COMMIT");
|
|
21023
|
+
} catch (error51) {
|
|
21024
|
+
try {
|
|
21025
|
+
db.exec("ROLLBACK");
|
|
21026
|
+
} catch {
|
|
21027
|
+
}
|
|
21028
|
+
throw error51;
|
|
21029
|
+
}
|
|
21030
|
+
}
|
|
21031
|
+
function failOpenTransaction(db, fn, mode = "DEFERRED") {
|
|
21032
|
+
const nested = db.isTransaction;
|
|
21033
|
+
try {
|
|
21034
|
+
withTransaction(db, fn, mode);
|
|
21035
|
+
return true;
|
|
21036
|
+
} catch (error51) {
|
|
21037
|
+
if (!db.isTransaction && nested) throw error51;
|
|
21038
|
+
return false;
|
|
21039
|
+
}
|
|
21040
|
+
}
|
|
21041
|
+
|
|
21042
|
+
// ../packages/persistence/src/internal/warn.ts
|
|
21043
|
+
function akaWarn(message) {
|
|
21044
|
+
process.stderr.write(`[aka] ${message}
|
|
21045
|
+
`);
|
|
21046
|
+
}
|
|
21047
|
+
|
|
21048
|
+
// ../packages/persistence/src/db/migrations/introspection.ts
|
|
21049
|
+
function evidenceObjects(sql) {
|
|
21050
|
+
const objects = [];
|
|
21051
|
+
for (const m of sql.matchAll(/CREATE TABLE (?:IF NOT EXISTS )?`([^`]+)`/g)) {
|
|
21052
|
+
if (m[1] !== void 0 && !m[1].startsWith("__new_")) {
|
|
21053
|
+
objects.push({ kind: "table", name: m[1] });
|
|
20321
21054
|
}
|
|
20322
21055
|
}
|
|
20323
21056
|
for (const m of sql.matchAll(/ALTER TABLE `([^`]+)` ADD (?:COLUMN )?`([^`]+)`/g)) {
|
|
@@ -20391,55 +21124,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
20391
21124
|
return out;
|
|
20392
21125
|
}
|
|
20393
21126
|
|
|
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
21127
|
// ../packages/persistence/src/migrations.ts
|
|
20444
21128
|
function describeObject(object2) {
|
|
20445
21129
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -20555,9 +21239,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
20555
21239
|
}
|
|
20556
21240
|
}
|
|
20557
21241
|
function backupBeforeLegacyDrop(db, file2) {
|
|
20558
|
-
|
|
20559
|
-
|
|
20560
|
-
|
|
21242
|
+
reapStalePartials(file2);
|
|
21243
|
+
const backup = backupPath(file2, "pre-drop");
|
|
21244
|
+
snapshotStore(db, backup);
|
|
20561
21245
|
return backup;
|
|
20562
21246
|
}
|
|
20563
21247
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -20901,6 +21585,25 @@ function parseJsonObject(s) {
|
|
|
20901
21585
|
return void 0;
|
|
20902
21586
|
}
|
|
20903
21587
|
|
|
21588
|
+
// ../packages/persistence/src/internal/keyset-cursor.ts
|
|
21589
|
+
function encodeKeysetCursor(payload) {
|
|
21590
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
21591
|
+
}
|
|
21592
|
+
function decodeKeysetCursor(cursor) {
|
|
21593
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
21594
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
21595
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
21596
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
21597
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
21598
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
21599
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
21600
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
21601
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
21602
|
+
return parsed;
|
|
21603
|
+
}
|
|
21604
|
+
return null;
|
|
21605
|
+
}
|
|
21606
|
+
|
|
20904
21607
|
// ../packages/persistence/src/repositories/activity.ts
|
|
20905
21608
|
var DAY_MS = 864e5;
|
|
20906
21609
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -20946,16 +21649,6 @@ function utcWindow(nowMs) {
|
|
|
20946
21649
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
20947
21650
|
return { startMs, endMs: startMs + DAY_MS };
|
|
20948
21651
|
}
|
|
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
21652
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
20960
21653
|
session: "session",
|
|
20961
21654
|
prompt: "prompt",
|
|
@@ -21100,7 +21793,7 @@ var SqliteActivityRepository = class {
|
|
|
21100
21793
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
21101
21794
|
}
|
|
21102
21795
|
listSessions(query) {
|
|
21103
|
-
const cursor = query.cursor ?
|
|
21796
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
21104
21797
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
21105
21798
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
21106
21799
|
const conditions = [SESSION_ROOT];
|
|
@@ -21174,7 +21867,7 @@ var SqliteActivityRepository = class {
|
|
|
21174
21867
|
)
|
|
21175
21868
|
);
|
|
21176
21869
|
const last = page[page.length - 1];
|
|
21177
|
-
const nextCursor = hasMore && last ?
|
|
21870
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
21178
21871
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
21179
21872
|
}
|
|
21180
21873
|
getSession(sessionId) {
|
|
@@ -22047,7 +22740,7 @@ var SqliteEventsRepository = class {
|
|
|
22047
22740
|
};
|
|
22048
22741
|
|
|
22049
22742
|
// ../packages/persistence/src/repositories/exceptions.ts
|
|
22050
|
-
import { randomUUID } from "crypto";
|
|
22743
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
22051
22744
|
|
|
22052
22745
|
// ../packages/persistence/src/internal/sqlite-errors.ts
|
|
22053
22746
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -22083,8 +22776,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
22083
22776
|
AND conditions IS NULL
|
|
22084
22777
|
AND ${ACTIVE_PREDICATE}`;
|
|
22085
22778
|
var SqliteExceptionsRepository = class {
|
|
22086
|
-
constructor(db) {
|
|
22779
|
+
constructor(db, now = () => Date.now()) {
|
|
22087
22780
|
this.db = db;
|
|
22781
|
+
this.now = now;
|
|
22088
22782
|
this.consumeStmt = db.prepare(
|
|
22089
22783
|
`UPDATE exceptions
|
|
22090
22784
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -22102,6 +22796,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22102
22796
|
);
|
|
22103
22797
|
}
|
|
22104
22798
|
db;
|
|
22799
|
+
now;
|
|
22105
22800
|
consumeStmt;
|
|
22106
22801
|
insertBlockedStmt;
|
|
22107
22802
|
sweepBlockedStmt;
|
|
@@ -22128,8 +22823,8 @@ var SqliteExceptionsRepository = class {
|
|
|
22128
22823
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
22129
22824
|
);
|
|
22130
22825
|
}
|
|
22131
|
-
const id =
|
|
22132
|
-
const now =
|
|
22826
|
+
const id = randomUUID2();
|
|
22827
|
+
const now = this.now();
|
|
22133
22828
|
try {
|
|
22134
22829
|
this.insertExceptionRow(id, input, now);
|
|
22135
22830
|
} catch (err) {
|
|
@@ -22207,7 +22902,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22207
22902
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
22208
22903
|
const rows = allRows(
|
|
22209
22904
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
22210
|
-
opts?.includeTerminal ? {} : { now:
|
|
22905
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
22211
22906
|
);
|
|
22212
22907
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
22213
22908
|
return Promise.resolve(exceptions);
|
|
@@ -22242,7 +22937,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22242
22937
|
* already revoked.
|
|
22243
22938
|
*/
|
|
22244
22939
|
revoke(id, revokedBy, reason) {
|
|
22245
|
-
const now =
|
|
22940
|
+
const now = this.now();
|
|
22246
22941
|
const result = this.db.prepare(
|
|
22247
22942
|
`UPDATE exceptions
|
|
22248
22943
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -22256,7 +22951,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22256
22951
|
* callers must treat identically — means it does not and the detection is
|
|
22257
22952
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
22258
22953
|
*/
|
|
22259
|
-
consume(id, now =
|
|
22954
|
+
consume(id, now = this.now()) {
|
|
22260
22955
|
const result = this.consumeStmt.run({ id, now });
|
|
22261
22956
|
return Promise.resolve(Number(result.changes) === 1);
|
|
22262
22957
|
}
|
|
@@ -22265,7 +22960,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22265
22960
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
22266
22961
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
22267
22962
|
*/
|
|
22268
|
-
activeBundleEntries(keyVersion, now =
|
|
22963
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
22269
22964
|
const rows = allRows(
|
|
22270
22965
|
this.db.prepare(
|
|
22271
22966
|
`SELECT * FROM exceptions
|
|
@@ -22297,7 +22992,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22297
22992
|
* than the retention window on every write, so the ledger self-limits.
|
|
22298
22993
|
*/
|
|
22299
22994
|
recordBlocked(entry) {
|
|
22300
|
-
const now =
|
|
22995
|
+
const now = this.now();
|
|
22301
22996
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
22302
22997
|
this.insertBlockedStmt.run({
|
|
22303
22998
|
reference: entry.reference,
|
|
@@ -22320,7 +23015,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22320
23015
|
WHERE blocked_at > :cutoff
|
|
22321
23016
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
22322
23017
|
),
|
|
22323
|
-
{ cutoff:
|
|
23018
|
+
{ cutoff: this.now() - windowMs }
|
|
22324
23019
|
);
|
|
22325
23020
|
return Promise.resolve(
|
|
22326
23021
|
rows.map((row) => ({
|
|
@@ -22348,8 +23043,9 @@ var SqliteExceptionsRepository = class {
|
|
|
22348
23043
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
22349
23044
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
22350
23045
|
*/
|
|
22351
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
23046
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
22352
23047
|
try {
|
|
23048
|
+
const at = now ?? this.now();
|
|
22353
23049
|
const row = getRow(
|
|
22354
23050
|
this.db.prepare(
|
|
22355
23051
|
`SELECT id FROM exceptions
|
|
@@ -22358,7 +23054,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22358
23054
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22359
23055
|
LIMIT 1`
|
|
22360
23056
|
),
|
|
22361
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
23057
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
22362
23058
|
);
|
|
22363
23059
|
return Promise.resolve(row ?? null);
|
|
22364
23060
|
} catch (err) {
|
|
@@ -22372,7 +23068,7 @@ var SqliteExceptionsRepository = class {
|
|
|
22372
23068
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
22373
23069
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
22374
23070
|
*/
|
|
22375
|
-
sweepTerminal(retentionMs, now =
|
|
23071
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
22376
23072
|
const result = this.db.prepare(
|
|
22377
23073
|
`DELETE FROM exceptions
|
|
22378
23074
|
WHERE updated_at < :cutoff
|
|
@@ -22435,6 +23131,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
22435
23131
|
|
|
22436
23132
|
// ../packages/persistence/src/repositories/findings.ts
|
|
22437
23133
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
23134
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
23135
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
23136
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
23137
|
+
function compareLocationOrder(a, b) {
|
|
23138
|
+
return compareFindingGroupOrder(
|
|
23139
|
+
{
|
|
23140
|
+
severity: a.maxSeverity,
|
|
23141
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
23142
|
+
id: ""
|
|
23143
|
+
},
|
|
23144
|
+
{
|
|
23145
|
+
severity: b.maxSeverity,
|
|
23146
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
23147
|
+
id: ""
|
|
23148
|
+
}
|
|
23149
|
+
);
|
|
23150
|
+
}
|
|
22438
23151
|
var CONCAT_SEP = ",";
|
|
22439
23152
|
var TUPLE_SEP = "|";
|
|
22440
23153
|
function splitConcat(value) {
|
|
@@ -22447,6 +23160,33 @@ function deriveInstanceStatus(row) {
|
|
|
22447
23160
|
latestResolutionStatus: row.latest_status
|
|
22448
23161
|
});
|
|
22449
23162
|
}
|
|
23163
|
+
function encodeGroupCursor(group) {
|
|
23164
|
+
const payload = {
|
|
23165
|
+
sev: group.severity,
|
|
23166
|
+
t: group.latestDetectedAt,
|
|
23167
|
+
id: group.id
|
|
23168
|
+
};
|
|
23169
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23170
|
+
}
|
|
23171
|
+
function decodeGroupCursor(cursor) {
|
|
23172
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23173
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
23174
|
+
return {
|
|
23175
|
+
severity: parsed.sev,
|
|
23176
|
+
latestDetectedAt: parsed.t,
|
|
23177
|
+
id: parsed.id
|
|
23178
|
+
};
|
|
23179
|
+
}
|
|
23180
|
+
return null;
|
|
23181
|
+
}
|
|
23182
|
+
function firstAfter(sorted, cursor) {
|
|
23183
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
23184
|
+
return index === -1 ? sorted.length : index;
|
|
23185
|
+
}
|
|
23186
|
+
function findDeepLinked(sorted, page, id) {
|
|
23187
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
23188
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
23189
|
+
}
|
|
22450
23190
|
var DAY_MS3 = 864e5;
|
|
22451
23191
|
var SqliteFindingsRepository = class {
|
|
22452
23192
|
constructor(db) {
|
|
@@ -22556,8 +23296,13 @@ var SqliteFindingsRepository = class {
|
|
|
22556
23296
|
*/
|
|
22557
23297
|
listGroupedFindings(query) {
|
|
22558
23298
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
22559
|
-
const
|
|
22560
|
-
const
|
|
23299
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
23300
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
23301
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
23302
|
+
const sessionParams = {
|
|
23303
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
23304
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
23305
|
+
};
|
|
22561
23306
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
22562
23307
|
predicate,
|
|
22563
23308
|
params: sessionParams
|
|
@@ -22565,7 +23310,8 @@ var SqliteFindingsRepository = class {
|
|
|
22565
23310
|
const rows = allRows(
|
|
22566
23311
|
this.db.prepare(
|
|
22567
23312
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
22568
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
23313
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
23314
|
+
kind, finding_key, latest_status
|
|
22569
23315
|
FROM (
|
|
22570
23316
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
22571
23317
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -22575,6 +23321,7 @@ var SqliteFindingsRepository = class {
|
|
|
22575
23321
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
22576
23322
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
22577
23323
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
23324
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
22578
23325
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
22579
23326
|
latest.status AS latest_status,
|
|
22580
23327
|
ROW_NUMBER() OVER (
|
|
@@ -22606,6 +23353,8 @@ var SqliteFindingsRepository = class {
|
|
|
22606
23353
|
repo: r.repo ?? "",
|
|
22607
23354
|
file: r.file ?? "",
|
|
22608
23355
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
23356
|
+
eventId: r.event_id,
|
|
23357
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
22609
23358
|
status: deriveInstanceStatus(r)
|
|
22610
23359
|
}));
|
|
22611
23360
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -22629,18 +23378,23 @@ var SqliteFindingsRepository = class {
|
|
|
22629
23378
|
groups: sorted.length
|
|
22630
23379
|
};
|
|
22631
23380
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
23381
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
23382
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
23383
|
+
const page = sorted.slice(start, start + limit);
|
|
23384
|
+
const lastOnPage = page.at(-1);
|
|
23385
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
23386
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
22632
23387
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
22633
|
-
const
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
);
|
|
23388
|
+
const narrow = (g) => statusSet ? {
|
|
23389
|
+
...g,
|
|
23390
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
23391
|
+
} : g;
|
|
23392
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
22639
23393
|
return Promise.resolve({
|
|
22640
23394
|
totals,
|
|
22641
23395
|
facets,
|
|
22642
23396
|
items,
|
|
22643
|
-
nextCursor
|
|
23397
|
+
nextCursor,
|
|
22644
23398
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
22645
23399
|
});
|
|
22646
23400
|
}
|
|
@@ -22672,6 +23426,266 @@ var SqliteFindingsRepository = class {
|
|
|
22672
23426
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
22673
23427
|
* path repeating across tuples.)
|
|
22674
23428
|
*/
|
|
23429
|
+
/**
|
|
23430
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
23431
|
+
* paged by keyset.
|
|
23432
|
+
*
|
|
23433
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
23434
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
23435
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
23436
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
23437
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
23438
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
23439
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
23440
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
23441
|
+
* none of the mappers names", which no IN-list can say.
|
|
23442
|
+
*
|
|
23443
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
23444
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
23445
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
23446
|
+
* while the counting runs, and only the page itself is retained.
|
|
23447
|
+
*/
|
|
23448
|
+
listFindingInstances(query) {
|
|
23449
|
+
const opts = {
|
|
23450
|
+
severity: query.severity,
|
|
23451
|
+
subtype: query.subtype,
|
|
23452
|
+
providers: query.provider,
|
|
23453
|
+
actions: query.action,
|
|
23454
|
+
statuses: query.status,
|
|
23455
|
+
tools: query.tool,
|
|
23456
|
+
repo: query.repo,
|
|
23457
|
+
file: query.file,
|
|
23458
|
+
q: query.q
|
|
23459
|
+
};
|
|
23460
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
23461
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23462
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
23463
|
+
const items = [];
|
|
23464
|
+
let total = 0;
|
|
23465
|
+
let last;
|
|
23466
|
+
let hasMore = false;
|
|
23467
|
+
for (const row of this.scanFindingRows({
|
|
23468
|
+
sessionId: query.sessionId,
|
|
23469
|
+
from: query.from
|
|
23470
|
+
})) {
|
|
23471
|
+
accumulator.add(row);
|
|
23472
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
23473
|
+
total += 1;
|
|
23474
|
+
if (items.length < limit) {
|
|
23475
|
+
items.push(toInstanceDetail(row));
|
|
23476
|
+
last = row;
|
|
23477
|
+
} else {
|
|
23478
|
+
hasMore = true;
|
|
23479
|
+
}
|
|
23480
|
+
}
|
|
23481
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
23482
|
+
if (cursor !== null) {
|
|
23483
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
23484
|
+
return Promise.resolve({
|
|
23485
|
+
totals: { findings: total },
|
|
23486
|
+
facets: accumulator.facets(),
|
|
23487
|
+
items: resumed.items,
|
|
23488
|
+
nextCursor: resumed.nextCursor
|
|
23489
|
+
});
|
|
23490
|
+
}
|
|
23491
|
+
return Promise.resolve({
|
|
23492
|
+
totals: { findings: total },
|
|
23493
|
+
facets: accumulator.facets(),
|
|
23494
|
+
items,
|
|
23495
|
+
nextCursor
|
|
23496
|
+
});
|
|
23497
|
+
}
|
|
23498
|
+
/**
|
|
23499
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
23500
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
23501
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
23502
|
+
* uses, so a later page costs less than the first rather than more.
|
|
23503
|
+
*/
|
|
23504
|
+
pageAfter(cursor, opts, limit, query) {
|
|
23505
|
+
const items = [];
|
|
23506
|
+
let last;
|
|
23507
|
+
let hasMore = false;
|
|
23508
|
+
for (const row of this.scanFindingRows({
|
|
23509
|
+
sessionId: query.sessionId,
|
|
23510
|
+
from: query.from,
|
|
23511
|
+
after: cursor
|
|
23512
|
+
})) {
|
|
23513
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
23514
|
+
if (items.length < limit) {
|
|
23515
|
+
items.push(toInstanceDetail(row));
|
|
23516
|
+
last = row;
|
|
23517
|
+
} else {
|
|
23518
|
+
hasMore = true;
|
|
23519
|
+
break;
|
|
23520
|
+
}
|
|
23521
|
+
}
|
|
23522
|
+
return {
|
|
23523
|
+
items,
|
|
23524
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
23525
|
+
};
|
|
23526
|
+
}
|
|
23527
|
+
/**
|
|
23528
|
+
* The same findings folded by location: repository, then file within it.
|
|
23529
|
+
*
|
|
23530
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
23531
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
23532
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
23533
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
23534
|
+
* filter can name it.
|
|
23535
|
+
*/
|
|
23536
|
+
listFindingLocations(query) {
|
|
23537
|
+
const opts = {
|
|
23538
|
+
severity: query.severity,
|
|
23539
|
+
subtype: query.subtype,
|
|
23540
|
+
providers: query.provider,
|
|
23541
|
+
actions: query.action,
|
|
23542
|
+
statuses: query.status,
|
|
23543
|
+
tools: query.tool,
|
|
23544
|
+
q: query.q
|
|
23545
|
+
};
|
|
23546
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
23547
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
23548
|
+
let total = 0;
|
|
23549
|
+
for (const row of this.scanFindingRows({
|
|
23550
|
+
sessionId: query.sessionId,
|
|
23551
|
+
from: query.from
|
|
23552
|
+
})) {
|
|
23553
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
23554
|
+
total += 1;
|
|
23555
|
+
let files = byRepo.get(row.repo);
|
|
23556
|
+
if (files === void 0) {
|
|
23557
|
+
files = /* @__PURE__ */ new Map();
|
|
23558
|
+
byRepo.set(row.repo, files);
|
|
23559
|
+
}
|
|
23560
|
+
let acc = files.get(row.file);
|
|
23561
|
+
if (acc === void 0) {
|
|
23562
|
+
acc = newLocationAccumulator();
|
|
23563
|
+
files.set(row.file, acc);
|
|
23564
|
+
}
|
|
23565
|
+
addToLocation(acc, row);
|
|
23566
|
+
}
|
|
23567
|
+
let fileCount = 0;
|
|
23568
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
23569
|
+
fileCount += files.size;
|
|
23570
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
23571
|
+
file: file2,
|
|
23572
|
+
instanceCount: acc.instanceCount,
|
|
23573
|
+
maxSeverity: acc.maxSeverity,
|
|
23574
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
23575
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
23576
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
23577
|
+
})).sort(compareLocationOrder);
|
|
23578
|
+
const rollup = fileRows.reduce(
|
|
23579
|
+
(a, f) => ({
|
|
23580
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
23581
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
23582
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
23583
|
+
}),
|
|
23584
|
+
{
|
|
23585
|
+
instanceCount: 0,
|
|
23586
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
23587
|
+
latestDetectedAt: ""
|
|
23588
|
+
}
|
|
23589
|
+
);
|
|
23590
|
+
const statuses = fileRows.map((f) => f.status);
|
|
23591
|
+
const folded = foldGroupStatus(statuses);
|
|
23592
|
+
return {
|
|
23593
|
+
repo,
|
|
23594
|
+
instanceCount: rollup.instanceCount,
|
|
23595
|
+
maxSeverity: rollup.maxSeverity,
|
|
23596
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
23597
|
+
...folded === void 0 ? {} : { status: folded },
|
|
23598
|
+
files: fileRows
|
|
23599
|
+
};
|
|
23600
|
+
});
|
|
23601
|
+
repos.sort(compareLocationOrder);
|
|
23602
|
+
return Promise.resolve({
|
|
23603
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
23604
|
+
items: repos.slice(0, limit),
|
|
23605
|
+
hasMore: repos.length > limit
|
|
23606
|
+
});
|
|
23607
|
+
}
|
|
23608
|
+
/**
|
|
23609
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
23610
|
+
*
|
|
23611
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
23612
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
23613
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
23614
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
23615
|
+
* rather than one unbounded result set.
|
|
23616
|
+
*
|
|
23617
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
23618
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
23619
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
23620
|
+
* a window over the whole resolution table once per batch.
|
|
23621
|
+
*
|
|
23622
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
23623
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
23624
|
+
* dimension — see listFindingInstances.
|
|
23625
|
+
*/
|
|
23626
|
+
*scanFindingRows(scope) {
|
|
23627
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
23628
|
+
const params = [];
|
|
23629
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
23630
|
+
conditions.push("e.root_session_id = ?");
|
|
23631
|
+
params.push(scope.sessionId);
|
|
23632
|
+
}
|
|
23633
|
+
if (scope.from !== void 0) {
|
|
23634
|
+
conditions.push("e.started_at >= ?");
|
|
23635
|
+
params.push(isoToEpochMillis(scope.from));
|
|
23636
|
+
}
|
|
23637
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
23638
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
23639
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
23640
|
+
e.started_at AS occurred_at,
|
|
23641
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
23642
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
23643
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
23644
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
23645
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
23646
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
23647
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
23648
|
+
FROM inspection_findings f
|
|
23649
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
23650
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
23651
|
+
WHERE ${conditions.join(" AND ")}
|
|
23652
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
23653
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
23654
|
+
LIMIT ?`;
|
|
23655
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
23656
|
+
for (; ; ) {
|
|
23657
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
23658
|
+
...params,
|
|
23659
|
+
after.startedAtMs,
|
|
23660
|
+
after.startedAtMs,
|
|
23661
|
+
after.id,
|
|
23662
|
+
SCAN_BATCH_ROWS
|
|
23663
|
+
]);
|
|
23664
|
+
for (const r of rows) {
|
|
23665
|
+
yield {
|
|
23666
|
+
id: r.id,
|
|
23667
|
+
ruleId: r.rule_id,
|
|
23668
|
+
category: r.category,
|
|
23669
|
+
severity: r.severity,
|
|
23670
|
+
maskedMatch: r.masked_match,
|
|
23671
|
+
actionTaken: r.action_taken,
|
|
23672
|
+
confidence: r.confidence,
|
|
23673
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
23674
|
+
sourceTool: r.source_tool,
|
|
23675
|
+
repo: r.repo ?? "",
|
|
23676
|
+
file: r.file ?? "",
|
|
23677
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
23678
|
+
eventId: r.event_id,
|
|
23679
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
23680
|
+
status: deriveInstanceStatus(r)
|
|
23681
|
+
};
|
|
23682
|
+
}
|
|
23683
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
23684
|
+
const lastRow = rows[rows.length - 1];
|
|
23685
|
+
if (lastRow === void 0) return;
|
|
23686
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
23687
|
+
}
|
|
23688
|
+
}
|
|
22675
23689
|
groupAggregates(withSearchText, scope) {
|
|
22676
23690
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
22677
23691
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -22932,7 +23946,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
22932
23946
|
};
|
|
22933
23947
|
|
|
22934
23948
|
// ../packages/persistence/src/repositories/installed-packs.ts
|
|
22935
|
-
import { createHash as createHash2, randomUUID as
|
|
23949
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
22936
23950
|
|
|
22937
23951
|
// ../packages/persistence/src/semver.ts
|
|
22938
23952
|
function parse4(version2) {
|
|
@@ -23083,7 +24097,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
23083
24097
|
let behind = false;
|
|
23084
24098
|
for (const row of rows) {
|
|
23085
24099
|
const params = {
|
|
23086
|
-
id:
|
|
24100
|
+
id: randomUUID3(),
|
|
23087
24101
|
namespace: row.namespace,
|
|
23088
24102
|
packId: row.packId,
|
|
23089
24103
|
version: row.version,
|
|
@@ -23095,7 +24109,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
23095
24109
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
23096
24110
|
this.upsertAvailableStmt.run({
|
|
23097
24111
|
...params,
|
|
23098
|
-
id:
|
|
24112
|
+
id: randomUUID3(),
|
|
23099
24113
|
recordedBy: meta3?.recordedBy ?? null
|
|
23100
24114
|
});
|
|
23101
24115
|
} else {
|
|
@@ -23418,14 +24432,15 @@ var SqliteInventoryRepository = class {
|
|
|
23418
24432
|
};
|
|
23419
24433
|
|
|
23420
24434
|
// ../packages/persistence/src/repositories/inventory-assets.ts
|
|
23421
|
-
import { randomUUID as
|
|
24435
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
23422
24436
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
23423
24437
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
23424
24438
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
23425
24439
|
var HARNESS_LABELS = {
|
|
23426
24440
|
claudecode: "Claude Code",
|
|
23427
24441
|
cursor: "Cursor",
|
|
23428
|
-
codex: "Codex"
|
|
24442
|
+
codex: "Codex",
|
|
24443
|
+
antigravity: "Antigravity"
|
|
23429
24444
|
};
|
|
23430
24445
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
23431
24446
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -23440,6 +24455,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
23440
24455
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
23441
24456
|
if (t.includes("cursor")) return "cursor";
|
|
23442
24457
|
if (t.includes("codex")) return "codex";
|
|
24458
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
23443
24459
|
return null;
|
|
23444
24460
|
}
|
|
23445
24461
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -23898,7 +24914,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
23898
24914
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
23899
24915
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
23900
24916
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
23901
|
-
).run({ id:
|
|
24917
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
23902
24918
|
}
|
|
23903
24919
|
return true;
|
|
23904
24920
|
}
|
|
@@ -23919,7 +24935,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
23919
24935
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
23920
24936
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
23921
24937
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
23922
|
-
).run({ id:
|
|
24938
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
23923
24939
|
}
|
|
23924
24940
|
this.configRowsCache = void 0;
|
|
23925
24941
|
return "ok";
|
|
@@ -24216,7 +25232,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
24216
25232
|
};
|
|
24217
25233
|
|
|
24218
25234
|
// ../packages/persistence/src/repositories/policies.ts
|
|
24219
|
-
import { randomUUID as
|
|
25235
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
24220
25236
|
var SqlitePoliciesRepository = class {
|
|
24221
25237
|
constructor(db) {
|
|
24222
25238
|
this.db = db;
|
|
@@ -24251,7 +25267,7 @@ var SqlitePoliciesRepository = class {
|
|
|
24251
25267
|
failOpenTransaction(this.db, () => {
|
|
24252
25268
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
24253
25269
|
stmt.run({
|
|
24254
|
-
id:
|
|
25270
|
+
id: randomUUID5(),
|
|
24255
25271
|
target: JSON.stringify({ category }),
|
|
24256
25272
|
action,
|
|
24257
25273
|
now: Date.now()
|
|
@@ -24271,7 +25287,7 @@ var SqlitePoliciesRepository = class {
|
|
|
24271
25287
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
24272
25288
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
24273
25289
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
24274
|
-
).run({ id:
|
|
25290
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
24275
25291
|
}
|
|
24276
25292
|
// Caps every global per-category policy currently set to block/redact down
|
|
24277
25293
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -24339,7 +25355,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
24339
25355
|
};
|
|
24340
25356
|
|
|
24341
25357
|
// ../packages/persistence/src/repositories/project-files.ts
|
|
24342
|
-
import { randomUUID as
|
|
25358
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
24343
25359
|
var SqliteProjectFilesRepository = class {
|
|
24344
25360
|
constructor(db) {
|
|
24345
25361
|
this.db = db;
|
|
@@ -24371,7 +25387,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
24371
25387
|
const stamp = Math.max(now, maxStamp + 1);
|
|
24372
25388
|
for (const file2 of scan2.files) {
|
|
24373
25389
|
this.upsertStmt.run({
|
|
24374
|
-
id:
|
|
25390
|
+
id: randomUUID6(),
|
|
24375
25391
|
projectId,
|
|
24376
25392
|
path: file2.path,
|
|
24377
25393
|
name: file2.name,
|
|
@@ -24385,7 +25401,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
24385
25401
|
};
|
|
24386
25402
|
|
|
24387
25403
|
// ../packages/persistence/src/repositories/resolutions.ts
|
|
24388
|
-
import { randomUUID as
|
|
25404
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
24389
25405
|
var SqliteResolutionsRepository = class {
|
|
24390
25406
|
constructor(db, now = () => Date.now()) {
|
|
24391
25407
|
this.db = db;
|
|
@@ -24439,7 +25455,7 @@ var SqliteResolutionsRepository = class {
|
|
|
24439
25455
|
*/
|
|
24440
25456
|
insertResolution(r) {
|
|
24441
25457
|
this.insertStmt.run({
|
|
24442
|
-
id:
|
|
25458
|
+
id: randomUUID7(),
|
|
24443
25459
|
findingKey: r.findingKey,
|
|
24444
25460
|
status: FindingStatus.parse(r.status),
|
|
24445
25461
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -24498,13 +25514,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
24498
25514
|
this.readStmt = db.prepare(
|
|
24499
25515
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
24500
25516
|
);
|
|
25517
|
+
this.countQuarantinedStmt = db.prepare(
|
|
25518
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
25519
|
+
);
|
|
25520
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
25521
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
25522
|
+
);
|
|
24501
25523
|
}
|
|
24502
25524
|
db;
|
|
24503
25525
|
upsertStmt;
|
|
24504
25526
|
readStmt;
|
|
25527
|
+
countQuarantinedStmt;
|
|
25528
|
+
clearQuarantinedStmt;
|
|
24505
25529
|
getVerdict(ruleKey) {
|
|
24506
25530
|
return getRow(this.readStmt, { ruleKey });
|
|
24507
25531
|
}
|
|
25532
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
25533
|
+
countQuarantined() {
|
|
25534
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
25535
|
+
}
|
|
25536
|
+
/**
|
|
25537
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
25538
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
25539
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
25540
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
25541
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
25542
|
+
*
|
|
25543
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
25544
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
25545
|
+
*
|
|
25546
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
25547
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
25548
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
25549
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
25550
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
25551
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
25552
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
25553
|
+
* to restore are silently still disabled.
|
|
25554
|
+
*/
|
|
25555
|
+
clearQuarantined() {
|
|
25556
|
+
const before = this.countQuarantined();
|
|
25557
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
25558
|
+
this.clearQuarantinedStmt.run();
|
|
25559
|
+
});
|
|
25560
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
25561
|
+
}
|
|
24508
25562
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
24509
25563
|
failOpenTransaction(this.db, () => {
|
|
24510
25564
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -24559,7 +25613,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
24559
25613
|
};
|
|
24560
25614
|
|
|
24561
25615
|
// ../packages/persistence/src/repositories/secret-vault.ts
|
|
24562
|
-
import { randomUUID as
|
|
25616
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
25617
|
+
function pageLimit(requested, fallback) {
|
|
25618
|
+
if (requested === void 0) return fallback;
|
|
25619
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
25620
|
+
}
|
|
25621
|
+
function encodeReuseCursor(payload) {
|
|
25622
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
25623
|
+
}
|
|
25624
|
+
function decodeReuseCursor(cursor) {
|
|
25625
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
25626
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
25627
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
25628
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
25629
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
25630
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
25631
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
25632
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
25633
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
25634
|
+
}
|
|
25635
|
+
return null;
|
|
25636
|
+
}
|
|
25637
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
25638
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
25639
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
25640
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
25641
|
+
function toSighting(row) {
|
|
25642
|
+
return {
|
|
25643
|
+
location: row.location,
|
|
25644
|
+
kind: row.kind,
|
|
25645
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
25646
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
25647
|
+
};
|
|
25648
|
+
}
|
|
24563
25649
|
var SELECT_COLUMNS = `
|
|
24564
25650
|
pointer_id AS pointerId,
|
|
24565
25651
|
value_fingerprint AS valueFingerprint,
|
|
@@ -24743,39 +25829,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
24743
25829
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
24744
25830
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
24745
25831
|
).run({
|
|
24746
|
-
id:
|
|
25832
|
+
id: randomUUID8(),
|
|
24747
25833
|
pointerId: entry.pointerId,
|
|
24748
25834
|
location: entry.location,
|
|
24749
25835
|
kind: entry.kind,
|
|
24750
25836
|
now
|
|
24751
25837
|
});
|
|
24752
25838
|
}
|
|
24753
|
-
|
|
25839
|
+
/**
|
|
25840
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
25841
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
25842
|
+
* the caller never has to distinguish "none" from "missing".
|
|
25843
|
+
*
|
|
25844
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
25845
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
25846
|
+
*/
|
|
25847
|
+
sightingsFor(pointerIds) {
|
|
25848
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
25849
|
+
if (pointerIds.length === 0) return byPointer;
|
|
24754
25850
|
const rows = allRows(
|
|
24755
25851
|
this.db.prepare(
|
|
24756
|
-
`SELECT location, kind, first_seen, last_seen
|
|
24757
|
-
|
|
25852
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
25853
|
+
FROM secret_vault_sighting
|
|
25854
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
25855
|
+
ORDER BY last_seen DESC`
|
|
24758
25856
|
),
|
|
24759
|
-
|
|
25857
|
+
pointerIds
|
|
24760
25858
|
);
|
|
25859
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
25860
|
+
return byPointer;
|
|
25861
|
+
}
|
|
25862
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
25863
|
+
toInventoryEntries(rows) {
|
|
25864
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
24761
25865
|
return rows.map((r) => ({
|
|
24762
|
-
|
|
24763
|
-
|
|
25866
|
+
pointerId: r.pointer_id,
|
|
25867
|
+
category: r.category,
|
|
25868
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
25869
|
+
maskedMatch: r.masked_match,
|
|
25870
|
+
occurrences: r.occurrence_count,
|
|
24764
25871
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
24765
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
25872
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
25873
|
+
revealGrantId: r.grant_id,
|
|
25874
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
24766
25875
|
}));
|
|
24767
25876
|
}
|
|
24768
25877
|
/**
|
|
24769
|
-
* The dashboard inventory
|
|
24770
|
-
* its sightings and the active
|
|
24771
|
-
* Raw-free by construction — neither
|
|
24772
|
-
* columns are selected.
|
|
25878
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
25879
|
+
* value's descriptor data joined with its sightings and the active
|
|
25880
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
25881
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
25882
|
+
*
|
|
25883
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
25884
|
+
* sees never depends on how far they have paged.
|
|
24773
25885
|
*/
|
|
24774
|
-
listInventory(now = Date.now()) {
|
|
25886
|
+
listInventory(query = {}, now = Date.now()) {
|
|
25887
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
25888
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
25889
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
24775
25890
|
const rows = allRows(
|
|
24776
25891
|
this.db.prepare(
|
|
24777
|
-
`SELECT
|
|
24778
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
25892
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
24779
25893
|
(SELECT e.id FROM exceptions e
|
|
24780
25894
|
WHERE e.rule_id = v.rule_id
|
|
24781
25895
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -24783,45 +25897,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
24783
25897
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
24784
25898
|
LIMIT 1) AS grant_id
|
|
24785
25899
|
FROM secret_vault v
|
|
24786
|
-
|
|
25900
|
+
${where}
|
|
25901
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
25902
|
+
LIMIT :limit`
|
|
24787
25903
|
),
|
|
24788
|
-
{
|
|
25904
|
+
bindParams({
|
|
25905
|
+
now,
|
|
25906
|
+
limit: limit + 1,
|
|
25907
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
25908
|
+
})
|
|
24789
25909
|
);
|
|
24790
|
-
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
|
|
24796
|
-
|
|
24797
|
-
|
|
24798
|
-
|
|
24799
|
-
|
|
24800
|
-
|
|
25910
|
+
const hasMore = rows.length > limit;
|
|
25911
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
25912
|
+
const last = page[page.length - 1];
|
|
25913
|
+
return {
|
|
25914
|
+
totals: { values: this.countEntries() },
|
|
25915
|
+
items: this.toInventoryEntries(page),
|
|
25916
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
25917
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
25918
|
+
};
|
|
25919
|
+
}
|
|
25920
|
+
/**
|
|
25921
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
25922
|
+
* than one location — most-reused first, one page at a time.
|
|
25923
|
+
*
|
|
25924
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
25925
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
25926
|
+
* under-report exactly the values a reader most needs to see.
|
|
25927
|
+
*/
|
|
25928
|
+
listReuse(query = {}, now = Date.now()) {
|
|
25929
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
25930
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
25931
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
25932
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
25933
|
+
const rows = allRows(
|
|
25934
|
+
this.db.prepare(
|
|
25935
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
25936
|
+
(SELECT e.id FROM exceptions e
|
|
25937
|
+
WHERE e.rule_id = v.rule_id
|
|
25938
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
25939
|
+
AND e.key_version = v.fingerprint_key_version
|
|
25940
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
25941
|
+
LIMIT 1) AS grant_id
|
|
25942
|
+
FROM secret_vault v
|
|
25943
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
25944
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
25945
|
+
LIMIT :limit`
|
|
25946
|
+
),
|
|
25947
|
+
bindParams({
|
|
25948
|
+
now,
|
|
25949
|
+
limit: limit + 1,
|
|
25950
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
25951
|
+
})
|
|
25952
|
+
);
|
|
25953
|
+
const hasMore = rows.length > limit;
|
|
25954
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
25955
|
+
const last = page[page.length - 1];
|
|
25956
|
+
return {
|
|
25957
|
+
totals: { reused: this.countReused() },
|
|
25958
|
+
items: this.toInventoryEntries(page),
|
|
25959
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
25960
|
+
};
|
|
24801
25961
|
}
|
|
24802
25962
|
/**
|
|
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.
|
|
25963
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
25964
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
25965
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
25966
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
25967
|
+
*
|
|
25968
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
25969
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
25970
|
+
* the reader pages.
|
|
24807
25971
|
*/
|
|
24808
|
-
listDerefs(
|
|
24809
|
-
const limit =
|
|
24810
|
-
const
|
|
25972
|
+
listDerefs(query = {}) {
|
|
25973
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
25974
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
25975
|
+
const conditions = [];
|
|
25976
|
+
if (query.includeBatched !== true) {
|
|
25977
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
25978
|
+
}
|
|
25979
|
+
if (cursor !== null) {
|
|
25980
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
25981
|
+
}
|
|
25982
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
24811
25983
|
const rows = allRows(
|
|
24812
25984
|
this.db.prepare(
|
|
24813
25985
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
24814
25986
|
FROM secret_vault_deref ${where}
|
|
24815
|
-
ORDER BY at DESC,
|
|
25987
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
24816
25988
|
),
|
|
24817
|
-
{
|
|
25989
|
+
bindParams({
|
|
25990
|
+
limit: limit + 1,
|
|
25991
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
25992
|
+
})
|
|
24818
25993
|
);
|
|
24819
|
-
const
|
|
25994
|
+
const hasMore = rows.length > limit;
|
|
25995
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
25996
|
+
const last = page[page.length - 1];
|
|
25997
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
24820
25998
|
this.db,
|
|
24821
25999
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
24822
26000
|
);
|
|
24823
26001
|
return {
|
|
24824
|
-
|
|
26002
|
+
items: page.map((r) => ({
|
|
24825
26003
|
id: r.id,
|
|
24826
26004
|
pointerId: r.pointer_id,
|
|
24827
26005
|
at: new Date(r.at).toISOString(),
|
|
@@ -24831,12 +26009,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
24831
26009
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
24832
26010
|
pointerCount: r.pointer_count
|
|
24833
26011
|
})),
|
|
26012
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
24834
26013
|
hiddenBatched
|
|
24835
26014
|
};
|
|
24836
26015
|
}
|
|
24837
26016
|
countEntries() {
|
|
24838
26017
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
24839
26018
|
}
|
|
26019
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
26020
|
+
countReused() {
|
|
26021
|
+
return countScalar(
|
|
26022
|
+
this.db,
|
|
26023
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
26024
|
+
);
|
|
26025
|
+
}
|
|
24840
26026
|
};
|
|
24841
26027
|
|
|
24842
26028
|
// ../packages/persistence/src/repositories/security.ts
|
|
@@ -24851,7 +26037,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
24851
26037
|
var SCAN_COVERAGE = [
|
|
24852
26038
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
24853
26039
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
24854
|
-
{ provider: "codex", coverage:
|
|
26040
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
26041
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
26042
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
24855
26043
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
24856
26044
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
24857
26045
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -25184,7 +26372,7 @@ var SqliteSecurityRepository = class {
|
|
|
25184
26372
|
};
|
|
25185
26373
|
|
|
25186
26374
|
// ../packages/persistence/src/repositories/shares.ts
|
|
25187
|
-
import { randomUUID as
|
|
26375
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
25188
26376
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
25189
26377
|
var IN_CHUNK = 500;
|
|
25190
26378
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -25440,7 +26628,7 @@ var SqliteSharesRepository = class {
|
|
|
25440
26628
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
25441
26629
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
25442
26630
|
).run({
|
|
25443
|
-
id:
|
|
26631
|
+
id: randomUUID9(),
|
|
25444
26632
|
destinationId,
|
|
25445
26633
|
host: dest.host,
|
|
25446
26634
|
decision,
|
|
@@ -25589,7 +26777,7 @@ var SqliteSharesRepository = class {
|
|
|
25589
26777
|
let destinationId = destIds.get(hit.host);
|
|
25590
26778
|
if (destinationId === void 0) {
|
|
25591
26779
|
destStmt.run({
|
|
25592
|
-
id:
|
|
26780
|
+
id: randomUUID9(),
|
|
25593
26781
|
kind: hit.kind,
|
|
25594
26782
|
name: hit.name,
|
|
25595
26783
|
host: hit.host,
|
|
@@ -25605,7 +26793,7 @@ var SqliteSharesRepository = class {
|
|
|
25605
26793
|
let endpointId = endpointIds.get(endpointKey);
|
|
25606
26794
|
if (endpointId === void 0) {
|
|
25607
26795
|
endpointStmt.run({
|
|
25608
|
-
id:
|
|
26796
|
+
id: randomUUID9(),
|
|
25609
26797
|
destinationId,
|
|
25610
26798
|
method: hit.method,
|
|
25611
26799
|
transport: hit.transport,
|
|
@@ -25618,7 +26806,7 @@ var SqliteSharesRepository = class {
|
|
|
25618
26806
|
endpointIds.set(endpointKey, endpointId);
|
|
25619
26807
|
}
|
|
25620
26808
|
siteStmt.run({
|
|
25621
|
-
id:
|
|
26809
|
+
id: randomUUID9(),
|
|
25622
26810
|
endpointId,
|
|
25623
26811
|
project: input.project,
|
|
25624
26812
|
projectKey: input.projectKey,
|
|
@@ -25983,6 +27171,9 @@ function purgeSampleData(db) {
|
|
|
25983
27171
|
}
|
|
25984
27172
|
|
|
25985
27173
|
// ../packages/persistence/src/database.ts
|
|
27174
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
27175
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
27176
|
+
);
|
|
25986
27177
|
function linkHost(input, hostId) {
|
|
25987
27178
|
return hostId ? { ...input, hostId } : input;
|
|
25988
27179
|
}
|
|
@@ -26004,21 +27195,34 @@ function openWithPragmas(file2) {
|
|
|
26004
27195
|
}
|
|
26005
27196
|
return db;
|
|
26006
27197
|
}
|
|
26007
|
-
function backupLegacyStore(file2) {
|
|
26008
|
-
|
|
26009
|
-
|
|
26010
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
27198
|
+
function backupLegacyStore(db, file2) {
|
|
27199
|
+
reapStalePartials(file2);
|
|
27200
|
+
const backup = backupPath(file2, "legacy");
|
|
27201
|
+
let snapshotted = false;
|
|
27202
|
+
let snapshotError;
|
|
27203
|
+
try {
|
|
27204
|
+
snapshotStore(db, backup);
|
|
27205
|
+
snapshotted = true;
|
|
27206
|
+
} catch (error51) {
|
|
27207
|
+
snapshotError = error51;
|
|
27208
|
+
} finally {
|
|
27209
|
+
db.close();
|
|
27210
|
+
}
|
|
27211
|
+
if (!snapshotted) {
|
|
27212
|
+
akaWarn(
|
|
27213
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
27214
|
+
);
|
|
27215
|
+
moveStoreAside(file2, backup);
|
|
27216
|
+
return backup;
|
|
26013
27217
|
}
|
|
27218
|
+
discardStore(file2, backup);
|
|
26014
27219
|
return backup;
|
|
26015
27220
|
}
|
|
26016
27221
|
function openAndInitialize(file2) {
|
|
26017
27222
|
let db = openWithPragmas(file2);
|
|
26018
27223
|
try {
|
|
26019
27224
|
if (isForeignSqliteLineage(db)) {
|
|
26020
|
-
db
|
|
26021
|
-
const backup = backupLegacyStore(file2);
|
|
27225
|
+
const backup = backupLegacyStore(db, file2);
|
|
26022
27226
|
db = openWithPragmas(file2);
|
|
26023
27227
|
akaWarn(
|
|
26024
27228
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -26062,7 +27266,7 @@ function openAndInitialize(file2) {
|
|
|
26062
27266
|
}
|
|
26063
27267
|
function openLocalDatabase(dir) {
|
|
26064
27268
|
ensureDataDirSync(dir);
|
|
26065
|
-
const file2 =
|
|
27269
|
+
const file2 = join3(dir, DB_FILENAME);
|
|
26066
27270
|
const {
|
|
26067
27271
|
db,
|
|
26068
27272
|
events,
|
|
@@ -26179,7 +27383,7 @@ function openLocalDatabase(dir) {
|
|
|
26179
27383
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
26180
27384
|
if (!definitionId) continue;
|
|
26181
27385
|
inspectionFindings.insertFinding({
|
|
26182
|
-
id:
|
|
27386
|
+
id: randomUUID10(),
|
|
26183
27387
|
auditEventId: record2.scanEvent.id,
|
|
26184
27388
|
inspectionDefinitionId: definitionId,
|
|
26185
27389
|
span: finding.span,
|
|
@@ -26285,8 +27489,224 @@ function openLocalDatabase(dir) {
|
|
|
26285
27489
|
transaction,
|
|
26286
27490
|
close: () => {
|
|
26287
27491
|
db.close();
|
|
27492
|
+
},
|
|
27493
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
27494
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
27495
|
+
};
|
|
27496
|
+
}
|
|
27497
|
+
|
|
27498
|
+
// ../packages/persistence/src/file-lock.ts
|
|
27499
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
27500
|
+
import {
|
|
27501
|
+
closeSync,
|
|
27502
|
+
existsSync as existsSync3,
|
|
27503
|
+
openSync,
|
|
27504
|
+
readFileSync as readFileSync2,
|
|
27505
|
+
rmSync as rmSync3,
|
|
27506
|
+
statSync as statSync2,
|
|
27507
|
+
writeFileSync as writeFileSync2
|
|
27508
|
+
} from "fs";
|
|
27509
|
+
import { hostname as hostname3 } from "os";
|
|
27510
|
+
var LOCK_SUFFIX = ".lock";
|
|
27511
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
27512
|
+
var DEFAULT_STALE_MS = 2e3;
|
|
27513
|
+
var RETRY_INTERVAL_MS = 5;
|
|
27514
|
+
var RETRYABLE_CREATE_ERRNOS = /* @__PURE__ */ new Set(["EEXIST", "EACCES", "EPERM", "EBUSY"]);
|
|
27515
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
27516
|
+
function sleepSync(ms) {
|
|
27517
|
+
Atomics.wait(PARK, 0, 0, ms);
|
|
27518
|
+
}
|
|
27519
|
+
var FileLockError = class extends Error {
|
|
27520
|
+
reason;
|
|
27521
|
+
file;
|
|
27522
|
+
holderPid;
|
|
27523
|
+
constructor(reason, file2, detail, holderPid, options) {
|
|
27524
|
+
super(
|
|
27525
|
+
`cannot lock ${file2} for writing: ${detail}` + (holderPid === void 0 ? "" : ` (held by pid ${String(holderPid)})`),
|
|
27526
|
+
options
|
|
27527
|
+
);
|
|
27528
|
+
this.name = "FileLockError";
|
|
27529
|
+
this.reason = reason;
|
|
27530
|
+
this.file = file2;
|
|
27531
|
+
this.holderPid = holderPid;
|
|
27532
|
+
}
|
|
27533
|
+
};
|
|
27534
|
+
function lockPathFor(file2) {
|
|
27535
|
+
return `${file2}${LOCK_SUFFIX}`;
|
|
27536
|
+
}
|
|
27537
|
+
function readLockBody(lock) {
|
|
27538
|
+
let raw;
|
|
27539
|
+
try {
|
|
27540
|
+
raw = readFileSync2(lock, "utf8");
|
|
27541
|
+
} catch {
|
|
27542
|
+
return null;
|
|
27543
|
+
}
|
|
27544
|
+
try {
|
|
27545
|
+
const parsed = JSON.parse(raw);
|
|
27546
|
+
const { pid, token, at, host } = parsed;
|
|
27547
|
+
if (typeof pid !== "number" || typeof token !== "string" || typeof at !== "number") return null;
|
|
27548
|
+
return { pid, token, at, host: typeof host === "string" ? host : "" };
|
|
27549
|
+
} catch {
|
|
27550
|
+
return null;
|
|
27551
|
+
}
|
|
27552
|
+
}
|
|
27553
|
+
function holderIsAlive(pid) {
|
|
27554
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
27555
|
+
try {
|
|
27556
|
+
process.kill(pid, 0);
|
|
27557
|
+
return true;
|
|
27558
|
+
} catch (err) {
|
|
27559
|
+
return err.code !== "ESRCH";
|
|
27560
|
+
}
|
|
27561
|
+
}
|
|
27562
|
+
function directoryAcceptsCreates(lock) {
|
|
27563
|
+
const probe = `${lock}.probe-${randomUUID11()}`;
|
|
27564
|
+
try {
|
|
27565
|
+
closeSync(openSync(probe, "wx", DATA_FILE_MODE));
|
|
27566
|
+
return true;
|
|
27567
|
+
} catch {
|
|
27568
|
+
return false;
|
|
27569
|
+
} finally {
|
|
27570
|
+
try {
|
|
27571
|
+
rmSync3(probe, { force: true });
|
|
27572
|
+
} catch {
|
|
27573
|
+
}
|
|
27574
|
+
}
|
|
27575
|
+
}
|
|
27576
|
+
function tryAcquire(lock, file2) {
|
|
27577
|
+
const token = randomUUID11();
|
|
27578
|
+
let fd;
|
|
27579
|
+
try {
|
|
27580
|
+
fd = openSync(lock, "wx", DATA_FILE_MODE);
|
|
27581
|
+
} catch (err) {
|
|
27582
|
+
const code = err.code ?? "";
|
|
27583
|
+
if (code === "EEXIST") return null;
|
|
27584
|
+
if (RETRYABLE_CREATE_ERRNOS.has(code) && (existsSync3(lock) || directoryAcceptsCreates(lock))) {
|
|
27585
|
+
return null;
|
|
27586
|
+
}
|
|
27587
|
+
throw new FileLockError(
|
|
27588
|
+
"unavailable",
|
|
27589
|
+
file2,
|
|
27590
|
+
err instanceof Error ? err.message : String(err),
|
|
27591
|
+
void 0,
|
|
27592
|
+
{ cause: err }
|
|
27593
|
+
);
|
|
27594
|
+
}
|
|
27595
|
+
const body = { pid: process.pid, token, at: Date.now(), host: hostname3() };
|
|
27596
|
+
try {
|
|
27597
|
+
writeFileSync2(fd, `${JSON.stringify(body)}
|
|
27598
|
+
`);
|
|
27599
|
+
} catch {
|
|
27600
|
+
try {
|
|
27601
|
+
closeSync(fd);
|
|
27602
|
+
} catch {
|
|
27603
|
+
}
|
|
27604
|
+
rmSync3(lock, { force: true });
|
|
27605
|
+
return null;
|
|
27606
|
+
}
|
|
27607
|
+
try {
|
|
27608
|
+
closeSync(fd);
|
|
27609
|
+
} catch {
|
|
27610
|
+
}
|
|
27611
|
+
return token;
|
|
27612
|
+
}
|
|
27613
|
+
function isAbandoned(body, lock, staleMs) {
|
|
27614
|
+
if (!body) {
|
|
27615
|
+
try {
|
|
27616
|
+
return Date.now() - statSync2(lock).mtimeMs >= staleMs;
|
|
27617
|
+
} catch {
|
|
27618
|
+
return false;
|
|
27619
|
+
}
|
|
27620
|
+
}
|
|
27621
|
+
if (Date.now() - body.at < staleMs) return false;
|
|
27622
|
+
if (body.host !== hostname3() || !holderIsAlive(body.pid)) return true;
|
|
27623
|
+
return Date.now() - body.at >= abandonWindow(staleMs);
|
|
27624
|
+
}
|
|
27625
|
+
function breakIfStale(lock, staleMs) {
|
|
27626
|
+
const breaker = `${lock}.break`;
|
|
27627
|
+
let fd;
|
|
27628
|
+
try {
|
|
27629
|
+
fd = openSync(breaker, "wx", DATA_FILE_MODE);
|
|
27630
|
+
} catch {
|
|
27631
|
+
reapAbandonedBreaker(breaker);
|
|
27632
|
+
return false;
|
|
27633
|
+
}
|
|
27634
|
+
try {
|
|
27635
|
+
closeSync(fd);
|
|
27636
|
+
} catch {
|
|
27637
|
+
}
|
|
27638
|
+
try {
|
|
27639
|
+
if (!existsSync3(lock) || !isAbandoned(readLockBody(lock), lock, staleMs)) return false;
|
|
27640
|
+
rmSync3(lock, { force: true });
|
|
27641
|
+
return true;
|
|
27642
|
+
} catch {
|
|
27643
|
+
return false;
|
|
27644
|
+
} finally {
|
|
27645
|
+
try {
|
|
27646
|
+
rmSync3(breaker, { force: true });
|
|
27647
|
+
} catch {
|
|
26288
27648
|
}
|
|
26289
|
-
}
|
|
27649
|
+
}
|
|
27650
|
+
}
|
|
27651
|
+
var BREAKER_ABANDONED_MS = 1e4;
|
|
27652
|
+
function reapAbandonedBreaker(breaker) {
|
|
27653
|
+
try {
|
|
27654
|
+
if (Date.now() - statSync2(breaker).mtimeMs >= BREAKER_ABANDONED_MS) {
|
|
27655
|
+
rmSync3(breaker, { force: true });
|
|
27656
|
+
}
|
|
27657
|
+
} catch {
|
|
27658
|
+
}
|
|
27659
|
+
}
|
|
27660
|
+
function abandonWindow(staleMs) {
|
|
27661
|
+
return Math.max(staleMs * 30, 6e4);
|
|
27662
|
+
}
|
|
27663
|
+
function release(lock, token) {
|
|
27664
|
+
try {
|
|
27665
|
+
if (readLockBody(lock)?.token !== token) return;
|
|
27666
|
+
rmSync3(lock, { force: true });
|
|
27667
|
+
} catch {
|
|
27668
|
+
}
|
|
27669
|
+
}
|
|
27670
|
+
function withFileLock(file2, fn, options = {}) {
|
|
27671
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
27672
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
27673
|
+
const lock = lockPathFor(file2);
|
|
27674
|
+
const deadline = Date.now() + timeoutMs;
|
|
27675
|
+
let token = tryAcquire(lock, file2);
|
|
27676
|
+
while (token === null) {
|
|
27677
|
+
if (breakIfStale(lock, staleMs)) {
|
|
27678
|
+
token = tryAcquire(lock, file2);
|
|
27679
|
+
continue;
|
|
27680
|
+
}
|
|
27681
|
+
if (Date.now() >= deadline) {
|
|
27682
|
+
throw new FileLockError(
|
|
27683
|
+
"timeout",
|
|
27684
|
+
file2,
|
|
27685
|
+
`still held after ${String(timeoutMs)}ms`,
|
|
27686
|
+
readLockBody(lock)?.pid
|
|
27687
|
+
);
|
|
27688
|
+
}
|
|
27689
|
+
sleepSync(RETRY_INTERVAL_MS);
|
|
27690
|
+
token = tryAcquire(lock, file2);
|
|
27691
|
+
}
|
|
27692
|
+
try {
|
|
27693
|
+
const result = fn();
|
|
27694
|
+
if (isThenable(result)) {
|
|
27695
|
+
void result.then(
|
|
27696
|
+
() => void 0,
|
|
27697
|
+
() => void 0
|
|
27698
|
+
);
|
|
27699
|
+
throw new TypeError(
|
|
27700
|
+
`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.`
|
|
27701
|
+
);
|
|
27702
|
+
}
|
|
27703
|
+
return result;
|
|
27704
|
+
} finally {
|
|
27705
|
+
release(lock, token);
|
|
27706
|
+
}
|
|
27707
|
+
}
|
|
27708
|
+
function isThenable(value) {
|
|
27709
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
26290
27710
|
}
|
|
26291
27711
|
|
|
26292
27712
|
// ../packages/persistence/src/finding-key.ts
|
|
@@ -26301,13 +27721,13 @@ function computeFindingKey(input) {
|
|
|
26301
27721
|
|
|
26302
27722
|
// ../packages/persistence/src/fingerprint.ts
|
|
26303
27723
|
import { createHmac, randomBytes } from "crypto";
|
|
26304
|
-
import { existsSync as
|
|
26305
|
-
import { join as
|
|
27724
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
27725
|
+
import { join as join4 } from "path";
|
|
26306
27726
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
26307
|
-
var
|
|
27727
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
26308
27728
|
var KEY_MATERIAL_BYTES = 32;
|
|
26309
27729
|
function keyFilePath(dataDir2) {
|
|
26310
|
-
return
|
|
27730
|
+
return join4(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
26311
27731
|
}
|
|
26312
27732
|
function parseKeyFile(raw) {
|
|
26313
27733
|
const parsed = JSON.parse(raw);
|
|
@@ -26345,8 +27765,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
26345
27765
|
}
|
|
26346
27766
|
};
|
|
26347
27767
|
function storedKeyVersionFloor(dataDir2) {
|
|
26348
|
-
const file2 =
|
|
26349
|
-
if (!
|
|
27768
|
+
const file2 = join4(dataDir2, DB_FILENAME);
|
|
27769
|
+
if (!existsSync4(file2)) return 0;
|
|
26350
27770
|
let db;
|
|
26351
27771
|
try {
|
|
26352
27772
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -26371,18 +27791,42 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
26371
27791
|
db?.close();
|
|
26372
27792
|
}
|
|
26373
27793
|
}
|
|
27794
|
+
function serializeKey(key) {
|
|
27795
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
27796
|
+
}
|
|
26374
27797
|
function writeKeyFile(dataDir2, key) {
|
|
26375
27798
|
ensureDataDirSync(dataDir2);
|
|
26376
|
-
|
|
26377
|
-
const body = JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
26378
|
-
writeOwnerOnlyFileSync(file2, `${body}
|
|
27799
|
+
writeOwnerOnlyFileSync(keyFilePath(dataDir2), `${serializeKey(key)}
|
|
26379
27800
|
`);
|
|
26380
27801
|
return key;
|
|
26381
27802
|
}
|
|
27803
|
+
function createKeyFile(dataDir2, key) {
|
|
27804
|
+
ensureDataDirSync(dataDir2);
|
|
27805
|
+
const file2 = keyFilePath(dataDir2);
|
|
27806
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
27807
|
+
`)) return key;
|
|
27808
|
+
const winner = readFingerprintKey(dataDir2);
|
|
27809
|
+
if (winner) {
|
|
27810
|
+
tightenFile(file2);
|
|
27811
|
+
return winner;
|
|
27812
|
+
}
|
|
27813
|
+
const occupant = classifyOccupant(file2);
|
|
27814
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
27815
|
+
}
|
|
27816
|
+
function occupantMessage(file2, kind) {
|
|
27817
|
+
switch (kind) {
|
|
27818
|
+
case "symlink":
|
|
27819
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
27820
|
+
case "gone":
|
|
27821
|
+
return "exception key file was removed while it was being created";
|
|
27822
|
+
case "unknown":
|
|
27823
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
27824
|
+
}
|
|
27825
|
+
}
|
|
26382
27826
|
function readFingerprintKey(dataDir2) {
|
|
26383
27827
|
let raw;
|
|
26384
27828
|
try {
|
|
26385
|
-
raw =
|
|
27829
|
+
raw = readFileSync3(keyFilePath(dataDir2), "utf8");
|
|
26386
27830
|
} catch (err) {
|
|
26387
27831
|
if (err.code === "ENOENT") return null;
|
|
26388
27832
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26395,7 +27839,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
26395
27839
|
tightenFile(keyFilePath(dataDir2));
|
|
26396
27840
|
return existing;
|
|
26397
27841
|
}
|
|
26398
|
-
return
|
|
27842
|
+
return createKeyFile(dataDir2, {
|
|
26399
27843
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
26400
27844
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
26401
27845
|
});
|
|
@@ -26421,31 +27865,32 @@ function keyStateOf(key) {
|
|
|
26421
27865
|
import { renameSync as renameSync3 } from "fs";
|
|
26422
27866
|
import { mkdir } from "fs/promises";
|
|
26423
27867
|
import { homedir as homedir3 } from "os";
|
|
26424
|
-
import { join as
|
|
27868
|
+
import { join as join5 } from "path";
|
|
26425
27869
|
function defaultDataDir() {
|
|
26426
|
-
return
|
|
27870
|
+
return join5(homedir3(), ".aka");
|
|
26427
27871
|
}
|
|
26428
27872
|
function settingsDir(base = defaultDataDir()) {
|
|
26429
|
-
return
|
|
27873
|
+
return join5(base, "settings");
|
|
26430
27874
|
}
|
|
26431
27875
|
function dataDir(base = defaultDataDir()) {
|
|
26432
|
-
return
|
|
27876
|
+
return join5(base, "data");
|
|
26433
27877
|
}
|
|
26434
27878
|
function dbPath(base = defaultDataDir()) {
|
|
26435
|
-
return
|
|
27879
|
+
return join5(dataDir(base), "aka.db");
|
|
26436
27880
|
}
|
|
26437
27881
|
function keysDir(base = defaultDataDir()) {
|
|
26438
|
-
return
|
|
27882
|
+
return join5(base, "keys");
|
|
26439
27883
|
}
|
|
26440
27884
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
26441
27885
|
ensureDataDirSync(dir);
|
|
26442
27886
|
}
|
|
26443
27887
|
|
|
26444
27888
|
// ../packages/persistence/src/settings.ts
|
|
26445
|
-
import { readFileSync as
|
|
26446
|
-
import { join as
|
|
27889
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
27890
|
+
import { join as join6 } from "path";
|
|
27891
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
26447
27892
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
26448
|
-
const record2 = readJson(
|
|
27893
|
+
const record2 = readJson(join6(settingsDir(base), SETTINGS_FILENAME));
|
|
26449
27894
|
if (!record2) return defaultWorkspaceSettings();
|
|
26450
27895
|
try {
|
|
26451
27896
|
return WorkspaceSettings.parse(record2);
|
|
@@ -26456,7 +27901,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
26456
27901
|
function readJson(file2) {
|
|
26457
27902
|
let text;
|
|
26458
27903
|
try {
|
|
26459
|
-
text =
|
|
27904
|
+
text = readFileSync4(file2, "utf8");
|
|
26460
27905
|
} catch {
|
|
26461
27906
|
return null;
|
|
26462
27907
|
}
|
|
@@ -26577,13 +28022,18 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
26577
28022
|
import {
|
|
26578
28023
|
chmodSync as chmodSync2,
|
|
26579
28024
|
mkdirSync as mkdirSync2,
|
|
26580
|
-
readFileSync as
|
|
28025
|
+
readFileSync as readFileSync5,
|
|
26581
28026
|
renameSync as renameSync4,
|
|
26582
|
-
rmSync as
|
|
26583
|
-
statSync,
|
|
26584
|
-
writeFileSync as
|
|
28027
|
+
rmSync as rmSync4,
|
|
28028
|
+
statSync as statSync3,
|
|
28029
|
+
writeFileSync as writeFileSync3
|
|
26585
28030
|
} from "fs";
|
|
26586
|
-
import { join as
|
|
28031
|
+
import { join as join7 } from "path";
|
|
28032
|
+
var VAULT_OCCUPANT_REASON = {
|
|
28033
|
+
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
28034
|
+
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
28035
|
+
unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
|
|
28036
|
+
};
|
|
26587
28037
|
var VaultKeyEpochMissingError = class extends Error {
|
|
26588
28038
|
version;
|
|
26589
28039
|
constructor(version2) {
|
|
@@ -26676,28 +28126,28 @@ function claimRotationLock(lock, owner) {
|
|
|
26676
28126
|
throw asError(err);
|
|
26677
28127
|
}
|
|
26678
28128
|
try {
|
|
26679
|
-
|
|
28129
|
+
writeFileSync3(join7(lock, LOCK_OWNER_FILE), `${owner}
|
|
26680
28130
|
`, { mode: DATA_FILE_MODE });
|
|
26681
28131
|
return true;
|
|
26682
28132
|
} catch (err) {
|
|
26683
|
-
|
|
28133
|
+
rmSync4(lock, { recursive: true, force: true });
|
|
26684
28134
|
throw asError(err);
|
|
26685
28135
|
}
|
|
26686
28136
|
}
|
|
26687
28137
|
function acquireRotationLock(keysDir2) {
|
|
26688
|
-
const lock =
|
|
28138
|
+
const lock = join7(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
26689
28139
|
const owner = randomBytes2(16).toString("hex");
|
|
26690
28140
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
26691
28141
|
let held;
|
|
26692
28142
|
try {
|
|
26693
|
-
held =
|
|
28143
|
+
held = statSync3(lock);
|
|
26694
28144
|
} catch {
|
|
26695
28145
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26696
28146
|
}
|
|
26697
28147
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
26698
28148
|
const aside = `${lock}.stale.${owner}`;
|
|
26699
28149
|
try {
|
|
26700
|
-
const now =
|
|
28150
|
+
const now = statSync3(lock);
|
|
26701
28151
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
26702
28152
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
26703
28153
|
}
|
|
@@ -26706,17 +28156,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
26706
28156
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
26707
28157
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
26708
28158
|
}
|
|
26709
|
-
|
|
28159
|
+
rmSync4(aside, { recursive: true, force: true });
|
|
26710
28160
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
26711
28161
|
return { lock, owner };
|
|
26712
28162
|
}
|
|
26713
28163
|
function releaseRotationLock(lease) {
|
|
26714
28164
|
try {
|
|
26715
|
-
if (
|
|
28165
|
+
if (readFileSync5(join7(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
26716
28166
|
} catch {
|
|
26717
28167
|
return;
|
|
26718
28168
|
}
|
|
26719
|
-
|
|
28169
|
+
rmSync4(lease.lock, { recursive: true, force: true });
|
|
26720
28170
|
}
|
|
26721
28171
|
function withRotationLock(keysDir2, work) {
|
|
26722
28172
|
ensureDataDirSync(keysDir2);
|
|
@@ -26733,7 +28183,7 @@ var FileKeyProvider = class {
|
|
|
26733
28183
|
this.#keysDir = keysDir2;
|
|
26734
28184
|
}
|
|
26735
28185
|
get filePath() {
|
|
26736
|
-
return
|
|
28186
|
+
return join7(this.#keysDir, VAULT_KEY_FILENAME);
|
|
26737
28187
|
}
|
|
26738
28188
|
loadOrCreate() {
|
|
26739
28189
|
return asAsync(() => {
|
|
@@ -26763,7 +28213,7 @@ var FileKeyProvider = class {
|
|
|
26763
28213
|
#read() {
|
|
26764
28214
|
let raw;
|
|
26765
28215
|
try {
|
|
26766
|
-
raw =
|
|
28216
|
+
raw = readFileSync5(this.filePath, "utf8");
|
|
26767
28217
|
} catch (err) {
|
|
26768
28218
|
if (err.code === "ENOENT") return null;
|
|
26769
28219
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -26771,34 +28221,32 @@ var FileKeyProvider = class {
|
|
|
26771
28221
|
return parseKeyring(raw);
|
|
26772
28222
|
}
|
|
26773
28223
|
/**
|
|
26774
|
-
* First mint: the keyring is
|
|
26775
|
-
*
|
|
26776
|
-
*
|
|
26777
|
-
*
|
|
26778
|
-
*
|
|
26779
|
-
*
|
|
26780
|
-
*
|
|
26781
|
-
*
|
|
28224
|
+
* First mint: the keyring is CREATED, never replaced, so two processes racing
|
|
28225
|
+
* a fresh machine cannot each mint a different epoch 1 — with tmp + rename
|
|
28226
|
+
* the loser's replace would orphan everything the winner had already sealed.
|
|
28227
|
+
* The loser re-reads and adopts the winner's keyring; it minted nothing.
|
|
28228
|
+
*
|
|
28229
|
+
* `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
|
|
28230
|
+
* at the final path, so the keyring never exists at zero length: a reader —
|
|
28231
|
+
* including the loser, re-reading in order to adopt — sees the file absent or
|
|
28232
|
+
* whole, and never mistakes a live keyring for a corrupt one. A corrupt file
|
|
28233
|
+
* still throws from the parse and is never re-minted over.
|
|
26782
28234
|
*/
|
|
26783
28235
|
#createExclusive() {
|
|
26784
28236
|
ensureDataDirSync(this.#keysDir);
|
|
26785
28237
|
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;
|
|
28238
|
+
if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
28239
|
+
`)) return keyring;
|
|
28240
|
+
const winner = this.#read();
|
|
28241
|
+
if (!winner) {
|
|
28242
|
+
const occupant = classifyOccupant(this.filePath);
|
|
28243
|
+
throw new KeyUnclaimableError(
|
|
28244
|
+
`vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
|
|
28245
|
+
occupant.cause
|
|
28246
|
+
);
|
|
26799
28247
|
}
|
|
26800
28248
|
tightenFileMode(this.filePath);
|
|
26801
|
-
return
|
|
28249
|
+
return winner;
|
|
26802
28250
|
}
|
|
26803
28251
|
/**
|
|
26804
28252
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
@@ -26809,7 +28257,7 @@ var FileKeyProvider = class {
|
|
|
26809
28257
|
ensureDataDirSync(this.#keysDir);
|
|
26810
28258
|
const file2 = this.filePath;
|
|
26811
28259
|
const tmp = `${file2}.tmp`;
|
|
26812
|
-
|
|
28260
|
+
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
26813
28261
|
`, { mode: DATA_FILE_MODE });
|
|
26814
28262
|
renameSync4(tmp, file2);
|
|
26815
28263
|
tightenFileMode(file2);
|
|
@@ -26935,7 +28383,7 @@ function createKeyProvider(custody, keysDir2) {
|
|
|
26935
28383
|
}
|
|
26936
28384
|
|
|
26937
28385
|
// ../packages/persistence/src/vault/vault.ts
|
|
26938
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
28386
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
26939
28387
|
var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
|
|
26940
28388
|
var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
|
|
26941
28389
|
var VAULT_PURGE_POINTER_ID = "*";
|
|
@@ -26962,14 +28410,12 @@ function parsePointer(token) {
|
|
|
26962
28410
|
var SecretVault = class {
|
|
26963
28411
|
#repo;
|
|
26964
28412
|
#keys;
|
|
26965
|
-
#fingerprintKey;
|
|
26966
28413
|
#isConsented;
|
|
26967
28414
|
#verifyGrant;
|
|
26968
28415
|
#now;
|
|
26969
28416
|
constructor(deps) {
|
|
26970
28417
|
this.#repo = deps.repo;
|
|
26971
28418
|
this.#keys = deps.keys;
|
|
26972
|
-
this.#fingerprintKey = deps.fingerprintKey;
|
|
26973
28419
|
this.#isConsented = deps.isConsented;
|
|
26974
28420
|
this.#verifyGrant = deps.verifyGrant;
|
|
26975
28421
|
this.#now = deps.now ?? (() => Date.now());
|
|
@@ -26978,10 +28424,23 @@ var SecretVault = class {
|
|
|
26978
28424
|
* Store a value and return the pointer that stands for it. The same value
|
|
26979
28425
|
* always yields the same pointer on this machine — one row, one pointer id,
|
|
26980
28426
|
* one category — which is what makes dedup and reuse counting work.
|
|
28427
|
+
*
|
|
28428
|
+
* `fingerprintKey` is the exception-key epoch this value's fingerprint is
|
|
28429
|
+
* derived under — a different key from the vault's, with different rotation
|
|
28430
|
+
* semantics. It is a parameter of the WRITE rather than a constructor dep,
|
|
28431
|
+
* and a thunk rather than a value, so that the only way to reach a key is to
|
|
28432
|
+
* store something: a read-only caller never names it, and a caller whose
|
|
28433
|
+
* source mints on absence mints only once consent has actually opened the
|
|
28434
|
+
* write. `refreshFingerprints` takes its key the same way, for the same
|
|
28435
|
+
* reason.
|
|
28436
|
+
*
|
|
28437
|
+
* Resolved once per call, so the fingerprint and the version it is recorded
|
|
28438
|
+
* under can never come from two different epochs.
|
|
26981
28439
|
*/
|
|
26982
|
-
async tokenize(raw, meta3) {
|
|
28440
|
+
async tokenize(raw, meta3, fingerprintKey) {
|
|
26983
28441
|
if (!this.#isConsented()) return CONSENT_ABSENT;
|
|
26984
|
-
const
|
|
28442
|
+
const fpKey = fingerprintKey();
|
|
28443
|
+
const valueFingerprint = fingerprintValue(fpKey, raw);
|
|
26985
28444
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
26986
28445
|
const now = this.#now();
|
|
26987
28446
|
if (existing) {
|
|
@@ -26997,7 +28456,7 @@ var SecretVault = class {
|
|
|
26997
28456
|
{
|
|
26998
28457
|
pointerId: base32Encode(pointerId),
|
|
26999
28458
|
valueFingerprint,
|
|
27000
|
-
fingerprintKeyVersion:
|
|
28459
|
+
fingerprintKeyVersion: fpKey.version,
|
|
27001
28460
|
keyVersion: version2,
|
|
27002
28461
|
// Recorded so the row stays OPENABLE if the wire-format constant ever
|
|
27003
28462
|
// moves: it is part of this row's AEAD AAD. It is not a tag input —
|
|
@@ -27241,7 +28700,7 @@ var SecretVault = class {
|
|
|
27241
28700
|
purgeVault() {
|
|
27242
28701
|
const destroyed = this.#repo.purgeAll();
|
|
27243
28702
|
this.#repo.recordDeref({
|
|
27244
|
-
id:
|
|
28703
|
+
id: randomUUID12(),
|
|
27245
28704
|
pointerId: VAULT_PURGE_POINTER_ID,
|
|
27246
28705
|
at: this.#now(),
|
|
27247
28706
|
target: "human",
|
|
@@ -27310,7 +28769,7 @@ var SecretVault = class {
|
|
|
27310
28769
|
}
|
|
27311
28770
|
#audit(pointerId, opts, outcome) {
|
|
27312
28771
|
this.#repo.recordDeref({
|
|
27313
|
-
id:
|
|
28772
|
+
id: randomUUID12(),
|
|
27314
28773
|
pointerId,
|
|
27315
28774
|
at: this.#now(),
|
|
27316
28775
|
target: opts.target,
|
|
@@ -27325,12 +28784,12 @@ var SecretVault = class {
|
|
|
27325
28784
|
};
|
|
27326
28785
|
|
|
27327
28786
|
// ../packages/persistence/src/warn-era-cap.ts
|
|
27328
|
-
import { existsSync as
|
|
27329
|
-
import { join as
|
|
28787
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
28788
|
+
import { join as join8 } from "path";
|
|
27330
28789
|
|
|
27331
28790
|
// ../packages/plugin-sdk/src/config.ts
|
|
27332
|
-
import { existsSync as
|
|
27333
|
-
import { join as
|
|
28791
|
+
import { existsSync as existsSync6 } from "fs";
|
|
28792
|
+
import { join as join9 } from "path";
|
|
27334
28793
|
|
|
27335
28794
|
// ../packages/plugin-sdk/src/provider-env.ts
|
|
27336
28795
|
var booleanish = external_exports.string().optional().transform((v) => {
|
|
@@ -27351,9 +28810,9 @@ var providerEnvShape = {
|
|
|
27351
28810
|
var ProviderEnvSchema = external_exports.object(providerEnvShape);
|
|
27352
28811
|
|
|
27353
28812
|
// ../packages/plugin-sdk/src/config-inventory.ts
|
|
27354
|
-
import { readdirSync, readFileSync as
|
|
28813
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
|
|
27355
28814
|
import { homedir as homedir4 } from "os";
|
|
27356
|
-
import { basename as
|
|
28815
|
+
import { basename as basename3, join as join11 } from "path";
|
|
27357
28816
|
|
|
27358
28817
|
// ../rules/code-flaws/auth-jwt-no-verify.json
|
|
27359
28818
|
var auth_jwt_no_verify_default = {
|
|
@@ -29009,7 +30468,7 @@ var gcp_service_account_default = {
|
|
|
29009
30468
|
severity: "critical",
|
|
29010
30469
|
matcher: {
|
|
29011
30470
|
type: "regex",
|
|
29012
|
-
pattern: "
|
|
30471
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
29013
30472
|
flags: "g"
|
|
29014
30473
|
},
|
|
29015
30474
|
examples: [
|
|
@@ -29388,8 +30847,8 @@ function bundledDetections() {
|
|
|
29388
30847
|
}
|
|
29389
30848
|
|
|
29390
30849
|
// ../packages/plugin-sdk/src/repo.ts
|
|
29391
|
-
import { existsSync as
|
|
29392
|
-
import { basename, dirname as
|
|
30850
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
30851
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join10, sep as sep2 } from "path";
|
|
29393
30852
|
function resolveRepoIdentity(cwd) {
|
|
29394
30853
|
try {
|
|
29395
30854
|
const root = findGitRoot(cwd);
|
|
@@ -29402,7 +30861,7 @@ function resolveRepoIdentity(cwd) {
|
|
|
29402
30861
|
// win32) so the persistence layer's `/`-separated checkout-path patterns
|
|
29403
30862
|
// (the ghost sweep + the read-side worktree filter) match it as written.
|
|
29404
30863
|
url: url2 ?? headRoot.split(sep2).join("/"),
|
|
29405
|
-
name: (url2 ? slugFromUrl(url2) : void 0) ??
|
|
30864
|
+
name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
|
|
29406
30865
|
};
|
|
29407
30866
|
} catch {
|
|
29408
30867
|
return void 0;
|
|
@@ -29427,36 +30886,36 @@ function resolveHeadRoot(cwd) {
|
|
|
29427
30886
|
function findGitRoot(start) {
|
|
29428
30887
|
let dir = start;
|
|
29429
30888
|
for (; ; ) {
|
|
29430
|
-
if (
|
|
29431
|
-
const parent =
|
|
30889
|
+
if (existsSync7(join10(dir, ".git"))) return dir;
|
|
30890
|
+
const parent = dirname3(dir);
|
|
29432
30891
|
if (parent === dir) return void 0;
|
|
29433
30892
|
dir = parent;
|
|
29434
30893
|
}
|
|
29435
30894
|
}
|
|
29436
30895
|
function resolveGitContext(root) {
|
|
29437
|
-
const dotGit =
|
|
30896
|
+
const dotGit = join10(root, ".git");
|
|
29438
30897
|
try {
|
|
29439
|
-
if (
|
|
29440
|
-
return { configPath:
|
|
30898
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
30899
|
+
return { configPath: join10(dotGit, "config"), headRoot: root };
|
|
29441
30900
|
}
|
|
29442
30901
|
} catch {
|
|
29443
30902
|
return void 0;
|
|
29444
30903
|
}
|
|
29445
30904
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
29446
30905
|
if (!target) return void 0;
|
|
29447
|
-
const gitdir = isAbsolute(target) ? target :
|
|
29448
|
-
if (
|
|
29449
|
-
return { configPath:
|
|
30906
|
+
const gitdir = isAbsolute(target) ? target : join10(root, target);
|
|
30907
|
+
if (existsSync7(join10(gitdir, "config"))) {
|
|
30908
|
+
return { configPath: join10(gitdir, "config"), headRoot: root };
|
|
29450
30909
|
}
|
|
29451
|
-
const commonRaw = safeRead(
|
|
30910
|
+
const commonRaw = safeRead(join10(gitdir, "commondir"))?.trim();
|
|
29452
30911
|
if (!commonRaw) return void 0;
|
|
29453
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
29454
|
-
const headRoot =
|
|
29455
|
-
return { configPath:
|
|
30912
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join10(gitdir, commonRaw);
|
|
30913
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname3(commonGitDir) : root;
|
|
30914
|
+
return { configPath: join10(commonGitDir, "config"), headRoot };
|
|
29456
30915
|
}
|
|
29457
30916
|
function safeRead(path) {
|
|
29458
30917
|
try {
|
|
29459
|
-
return
|
|
30918
|
+
return readFileSync6(path, "utf8");
|
|
29460
30919
|
} catch {
|
|
29461
30920
|
return void 0;
|
|
29462
30921
|
}
|
|
@@ -29494,18 +30953,23 @@ function slugFromUrl(url2) {
|
|
|
29494
30953
|
}
|
|
29495
30954
|
|
|
29496
30955
|
// ../packages/plugin-sdk/src/events.ts
|
|
29497
|
-
import { createHash as createHash4, randomUUID as
|
|
30956
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
30957
|
+
|
|
30958
|
+
// ../packages/plugin-sdk/src/isolated-scan.ts
|
|
30959
|
+
import { existsSync as existsSync8 } from "fs";
|
|
30960
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30961
|
+
import { Worker } from "worker_threads";
|
|
29498
30962
|
|
|
29499
30963
|
// ../packages/plugin-sdk/src/inventory-resolver.ts
|
|
29500
|
-
import { arch, hostname as
|
|
30964
|
+
import { arch, hostname as hostname4, platform, release as release2 } from "os";
|
|
29501
30965
|
|
|
29502
30966
|
// ../packages/plugin-sdk/src/nudge.ts
|
|
29503
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
29504
|
-
import { join as
|
|
30967
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
30968
|
+
import { join as join12 } from "path";
|
|
29505
30969
|
|
|
29506
30970
|
// ../packages/plugin-sdk/src/paths.ts
|
|
29507
|
-
import { readdirSync as
|
|
29508
|
-
import { basename as
|
|
30971
|
+
import { readdirSync as readdirSync4, realpathSync as realpathSync2 } from "fs";
|
|
30972
|
+
import { basename as basename4, dirname as dirname4, sep as sep3 } from "path";
|
|
29509
30973
|
function toPosix(path) {
|
|
29510
30974
|
return path.split(sep3).join("/");
|
|
29511
30975
|
}
|
|
@@ -29515,7 +30979,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
29515
30979
|
let root = null;
|
|
29516
30980
|
for (let level = 0; level < MAX_PROJECT_ROOT_LEVELS; level += 1) {
|
|
29517
30981
|
if (directoryHasMarker(dir, recognizeMarker)) root = dir;
|
|
29518
|
-
const parent =
|
|
30982
|
+
const parent = dirname4(dir);
|
|
29519
30983
|
if (parent === dir) break;
|
|
29520
30984
|
dir = parent;
|
|
29521
30985
|
}
|
|
@@ -29523,7 +30987,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
29523
30987
|
}
|
|
29524
30988
|
function directoryHasMarker(dir, recognizeMarker) {
|
|
29525
30989
|
try {
|
|
29526
|
-
for (const entry of
|
|
30990
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
29527
30991
|
if (entry.isFile() && recognizeMarker(entry.name) != null) return true;
|
|
29528
30992
|
}
|
|
29529
30993
|
} catch {
|
|
@@ -29534,13 +30998,13 @@ function directoryHasMarker(dir, recognizeMarker) {
|
|
|
29534
30998
|
function resolveNonGitProject(startDir, recognizeMarker) {
|
|
29535
30999
|
const projectRoot = findProjectRoot(startDir, recognizeMarker);
|
|
29536
31000
|
const realRoot = realpathSync2(projectRoot);
|
|
29537
|
-
return { root: projectRoot, projectKey: `path:${realRoot}`, project:
|
|
31001
|
+
return { root: projectRoot, projectKey: `path:${realRoot}`, project: basename4(realRoot) };
|
|
29538
31002
|
}
|
|
29539
31003
|
|
|
29540
31004
|
// ../packages/plugin-sdk/src/project-files.ts
|
|
29541
31005
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
29542
|
-
import { existsSync as
|
|
29543
|
-
import { basename as
|
|
31006
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
|
|
31007
|
+
import { basename as basename5, join as join13 } from "path";
|
|
29544
31008
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
29545
31009
|
".git",
|
|
29546
31010
|
"node_modules",
|
|
@@ -29558,23 +31022,28 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
29558
31022
|
".cache"
|
|
29559
31023
|
]);
|
|
29560
31024
|
var MAX_FILES = 2e4;
|
|
29561
|
-
function readIgnoreLayer(dir) {
|
|
31025
|
+
function readIgnoreLayer(dir, anchorLen) {
|
|
29562
31026
|
try {
|
|
29563
|
-
const content =
|
|
29564
|
-
return {
|
|
31027
|
+
const content = readFileSync9(join13(dir, ".gitignore"), "utf8");
|
|
31028
|
+
return { matcher: (0, import_ignore.default)().add(content), anchorLen };
|
|
29565
31029
|
} catch {
|
|
29566
31030
|
return void 0;
|
|
29567
31031
|
}
|
|
29568
31032
|
}
|
|
29569
|
-
function
|
|
29570
|
-
|
|
29571
|
-
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
31033
|
+
function relToAnchor(dirRel, anchorLen, name) {
|
|
31034
|
+
if (anchorLen === dirRel.length) return name;
|
|
31035
|
+
return `${dirRel.slice(anchorLen === 0 ? 0 : anchorLen + 1)}/${name}`;
|
|
31036
|
+
}
|
|
31037
|
+
function isIgnored(layers, dirRel, name, isDir) {
|
|
31038
|
+
const suffix = isDir ? "/" : "";
|
|
31039
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
31040
|
+
const layer = layers[i];
|
|
31041
|
+
if (layer === void 0) continue;
|
|
31042
|
+
const verdict = layer.matcher.test(relToAnchor(dirRel, layer.anchorLen, name) + suffix);
|
|
31043
|
+
if (verdict.ignored) return true;
|
|
31044
|
+
if (verdict.unignored) return false;
|
|
29576
31045
|
}
|
|
29577
|
-
return
|
|
31046
|
+
return false;
|
|
29578
31047
|
}
|
|
29579
31048
|
var GENERATED_FILES = /* @__PURE__ */ new Set([
|
|
29580
31049
|
"pnpm-lock.yaml",
|
|
@@ -29627,32 +31096,33 @@ function classifyOrigin(relPath, name) {
|
|
|
29627
31096
|
}
|
|
29628
31097
|
function resolveProjectFiles(cwd) {
|
|
29629
31098
|
try {
|
|
29630
|
-
let visit3 = function(dir, layers) {
|
|
31099
|
+
let visit3 = function(dir, dirRel, layers) {
|
|
29631
31100
|
let dirents;
|
|
29632
31101
|
try {
|
|
29633
|
-
dirents =
|
|
31102
|
+
dirents = readdirSync5(dir, { withFileTypes: true, encoding: "utf8" });
|
|
29634
31103
|
} catch {
|
|
29635
31104
|
walk.lostSubtree = true;
|
|
29636
31105
|
return false;
|
|
29637
31106
|
}
|
|
29638
|
-
const layer = readIgnoreLayer(dir);
|
|
31107
|
+
const layer = readIgnoreLayer(dir, dirRel.length);
|
|
29639
31108
|
const dirLayers = layer ? [...layers, layer] : layers;
|
|
29640
31109
|
for (const entry of dirents) {
|
|
29641
|
-
const fullPath = join12(dir, entry.name);
|
|
29642
31110
|
if (entry.isDirectory()) {
|
|
29643
|
-
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers,
|
|
29644
|
-
|
|
29645
|
-
if (
|
|
31111
|
+
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
|
|
31112
|
+
const fullPath = join13(dir, entry.name);
|
|
31113
|
+
if (existsSync9(join13(fullPath, ".git"))) continue;
|
|
31114
|
+
const childRel = dirRel === "" ? entry.name : `${dirRel}/${entry.name}`;
|
|
31115
|
+
if (visit3(fullPath, childRel, dirLayers)) return true;
|
|
29646
31116
|
continue;
|
|
29647
31117
|
}
|
|
29648
31118
|
if (!entry.isFile()) continue;
|
|
29649
31119
|
if (entry.name === ".git") continue;
|
|
29650
|
-
if (isIgnored(dirLayers,
|
|
31120
|
+
if (isIgnored(dirLayers, dirRel, entry.name, false)) continue;
|
|
29651
31121
|
if (files.length >= MAX_FILES) return true;
|
|
29652
|
-
const relPath =
|
|
31122
|
+
const relPath = dirRel === "" ? entry.name : `${dirRel}/${entry.name}`;
|
|
29653
31123
|
files.push({
|
|
29654
31124
|
path: relPath,
|
|
29655
|
-
name:
|
|
31125
|
+
name: basename5(entry.name),
|
|
29656
31126
|
origin: classifyOrigin(relPath, entry.name),
|
|
29657
31127
|
defaultAccess: "approved"
|
|
29658
31128
|
});
|
|
@@ -29666,7 +31136,7 @@ function resolveProjectFiles(cwd) {
|
|
|
29666
31136
|
const isLinkedCheckout = resolveHeadRoot(cwd) !== worktree;
|
|
29667
31137
|
const files = [];
|
|
29668
31138
|
const walk = { lostSubtree: false };
|
|
29669
|
-
const truncated = visit3(root, []) || walk.lostSubtree || isLinkedCheckout;
|
|
31139
|
+
const truncated = visit3(root, "", []) || walk.lostSubtree || isLinkedCheckout;
|
|
29670
31140
|
if (files.length === 0) return void 0;
|
|
29671
31141
|
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
29672
31142
|
return { files, truncated, scannedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
@@ -29675,22 +31145,48 @@ function resolveProjectFiles(cwd) {
|
|
|
29675
31145
|
}
|
|
29676
31146
|
}
|
|
29677
31147
|
|
|
31148
|
+
// ../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
31149
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
31150
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
31151
|
+
return v;
|
|
31152
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
31153
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
31154
|
+
if (typeof v !== "string") return false;
|
|
31155
|
+
const normalized = v.trim().toLowerCase();
|
|
31156
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
31157
|
+
}, external_exports.boolean()).catch(false);
|
|
31158
|
+
var antigravityProviderEnvShape = {
|
|
31159
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
31160
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
31161
|
+
};
|
|
31162
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
31163
|
+
|
|
31164
|
+
// ../packages/plugin-sdk/src/provider-env-codex.ts
|
|
31165
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
31166
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
31167
|
+
return v;
|
|
31168
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
31169
|
+
var codexProviderEnvShape = {
|
|
31170
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
31171
|
+
};
|
|
31172
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
31173
|
+
|
|
29678
31174
|
// ../packages/plugin-sdk/src/runtime.ts
|
|
29679
|
-
import { randomUUID as
|
|
31175
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
29680
31176
|
|
|
29681
31177
|
// ../packages/plugin-sdk/src/suppressions.ts
|
|
29682
31178
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
29683
31179
|
|
|
29684
31180
|
// ../packages/plugin-sdk/src/throttle.ts
|
|
29685
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
29686
|
-
import { join as
|
|
31181
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
31182
|
+
import { join as join14 } from "path";
|
|
29687
31183
|
|
|
29688
31184
|
// ../packages/local-ops/src/egress-record.ts
|
|
29689
31185
|
function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
29690
31186
|
try {
|
|
29691
31187
|
if (!readWorkspaceSettings(base).dataSharesInPlace) return null;
|
|
29692
31188
|
const abs = resolve(target);
|
|
29693
|
-
const targetDir =
|
|
31189
|
+
const targetDir = statSync7(abs).isDirectory() ? abs : dirname5(abs);
|
|
29694
31190
|
const identity = resolveRepoIdentity(abs);
|
|
29695
31191
|
const worktreeRoot = resolveWorktreeRoot(abs);
|
|
29696
31192
|
let root;
|
|
@@ -29712,7 +31208,7 @@ function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
|
29712
31208
|
}
|
|
29713
31209
|
const files = [];
|
|
29714
31210
|
for (const hit of egress.files) {
|
|
29715
|
-
const file2 = toPosix(
|
|
31211
|
+
const file2 = toPosix(relative(root, hit.file));
|
|
29716
31212
|
if (file2 === "" || file2.startsWith("../")) continue;
|
|
29717
31213
|
if (file2.startsWith(".") || file2.includes("/.")) continue;
|
|
29718
31214
|
files.push({ ...hit, file: file2, vendored: isVendoredPath(file2) });
|
|
@@ -29721,7 +31217,7 @@ function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
|
29721
31217
|
projectKey,
|
|
29722
31218
|
project,
|
|
29723
31219
|
projectId,
|
|
29724
|
-
reconcile: { mode: "walk", walkedPrefix: toPosix(
|
|
31220
|
+
reconcile: { mode: "walk", walkedPrefix: toPosix(relative(root, abs)) },
|
|
29725
31221
|
hits: resolveEgress(files)
|
|
29726
31222
|
});
|
|
29727
31223
|
return { ...summary, project };
|
|
@@ -29731,9 +31227,9 @@ function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
|
|
|
29731
31227
|
}
|
|
29732
31228
|
|
|
29733
31229
|
// ../packages/local-ops/src/fs-scan.ts
|
|
29734
|
-
import { createHash as createHash5, randomUUID as
|
|
29735
|
-
import { readdirSync as
|
|
29736
|
-
import { basename as
|
|
31230
|
+
import { createHash as createHash5, randomUUID as randomUUID15 } from "crypto";
|
|
31231
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync8 } from "fs";
|
|
31232
|
+
import { basename as basename6, extname, join as join15, relative as relative2, resolve as resolve2 } from "path";
|
|
29737
31233
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
29738
31234
|
var AKAIGNORE_FILENAME = ".akaignore";
|
|
29739
31235
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
@@ -29749,7 +31245,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
29749
31245
|
var MAX_BYTES = 1e6;
|
|
29750
31246
|
function readIgnoreLayer2(dir, filename) {
|
|
29751
31247
|
try {
|
|
29752
|
-
const content =
|
|
31248
|
+
const content = readFileSync10(join15(dir, filename), "utf8");
|
|
29753
31249
|
return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
|
|
29754
31250
|
} catch {
|
|
29755
31251
|
return void 0;
|
|
@@ -29758,7 +31254,7 @@ function readIgnoreLayer2(dir, filename) {
|
|
|
29758
31254
|
function evaluate(layers, absPath, isDir) {
|
|
29759
31255
|
let state = "unmatched";
|
|
29760
31256
|
for (const layer of layers) {
|
|
29761
|
-
const rel = toPosix(
|
|
31257
|
+
const rel = toPosix(relative2(layer.base, absPath)) + (isDir ? "/" : "");
|
|
29762
31258
|
const verdict = layer.matcher.test(rel);
|
|
29763
31259
|
if (verdict.ignored) state = "ignored";
|
|
29764
31260
|
else if (verdict.unignored) state = "unignored";
|
|
@@ -29768,7 +31264,7 @@ function evaluate(layers, absPath, isDir) {
|
|
|
29768
31264
|
function* collectFiles(target) {
|
|
29769
31265
|
let st;
|
|
29770
31266
|
try {
|
|
29771
|
-
st =
|
|
31267
|
+
st = statSync8(target);
|
|
29772
31268
|
} catch {
|
|
29773
31269
|
return;
|
|
29774
31270
|
}
|
|
@@ -29784,8 +31280,8 @@ function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
|
29784
31280
|
const dirMarkLayers = markLayer ? [...markLayers, markLayer] : markLayers;
|
|
29785
31281
|
const skipLayer = readIgnoreLayer2(dir, AKAIGNORE_FILENAME);
|
|
29786
31282
|
const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
|
|
29787
|
-
for (const entry of
|
|
29788
|
-
const path =
|
|
31283
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
31284
|
+
const path = join15(dir, entry.name);
|
|
29789
31285
|
if (entry.isDirectory()) {
|
|
29790
31286
|
const skipState = evaluate(dirSkipLayers, path, true);
|
|
29791
31287
|
if (skipState !== "unignored" && (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".") || skipState === "ignored")) {
|
|
@@ -29797,7 +31293,7 @@ function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
|
29797
31293
|
if (evaluate(dirSkipLayers, path, false) === "ignored") continue;
|
|
29798
31294
|
let size;
|
|
29799
31295
|
try {
|
|
29800
|
-
size =
|
|
31296
|
+
size = statSync8(path).size;
|
|
29801
31297
|
} catch {
|
|
29802
31298
|
continue;
|
|
29803
31299
|
}
|
|
@@ -29811,7 +31307,7 @@ function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
|
29811
31307
|
}
|
|
29812
31308
|
function extractFileEgress(file2, text) {
|
|
29813
31309
|
if (text.includes("\0")) return null;
|
|
29814
|
-
const name =
|
|
31310
|
+
const name = basename6(file2);
|
|
29815
31311
|
if (LOCKFILE_BASENAMES.has(name)) return null;
|
|
29816
31312
|
const kind = manifestKindOf(name);
|
|
29817
31313
|
const sdkHits = kind === null ? [] : extractManifestSdks(text, kind);
|
|
@@ -29819,7 +31315,8 @@ function extractFileEgress(file2, text) {
|
|
|
29819
31315
|
if (endpoints.length === 0 && sdkHits.length === 0) return null;
|
|
29820
31316
|
return { file: file2, vendored: isVendoredPath(toPosix(file2)), endpoints, sdkHits };
|
|
29821
31317
|
}
|
|
29822
|
-
function scanPathIntoStore(db, target, opts = {}) {
|
|
31318
|
+
async function scanPathIntoStore(db, target, opts = {}) {
|
|
31319
|
+
const matchText = opts.scanText ?? ((text) => Promise.resolve(scan(text, opts.rules)));
|
|
29823
31320
|
let scanned = 0;
|
|
29824
31321
|
let findingCount = 0;
|
|
29825
31322
|
const files = [];
|
|
@@ -29838,7 +31335,7 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29838
31335
|
for (const { path: file2, gitignored } of collectFiles(resolve2(target))) {
|
|
29839
31336
|
let text;
|
|
29840
31337
|
try {
|
|
29841
|
-
text =
|
|
31338
|
+
text = readFileSync10(file2, "utf8");
|
|
29842
31339
|
} catch {
|
|
29843
31340
|
continue;
|
|
29844
31341
|
}
|
|
@@ -29846,9 +31343,9 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29846
31343
|
const fileEgress = extractFileEgress(file2, text);
|
|
29847
31344
|
if (fileEgress) egressFiles.push(fileEgress);
|
|
29848
31345
|
const shielded = shieldPointers(text);
|
|
29849
|
-
const matches = dropShieldedFindings(
|
|
31346
|
+
const matches = dropShieldedFindings(await matchText(shielded.text), shielded.spans);
|
|
29850
31347
|
if (matches.length === 0) continue;
|
|
29851
|
-
const eventId =
|
|
31348
|
+
const eventId = randomUUID15();
|
|
29852
31349
|
const metadata = { filePath: file2 };
|
|
29853
31350
|
if (gitignored) metadata.gitignored = true;
|
|
29854
31351
|
const event = {
|
|
@@ -29866,7 +31363,7 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29866
31363
|
const key = resolveFingerprintKey();
|
|
29867
31364
|
const valueFingerprint = key ? fingerprintValue(key, m.rawMatch) : maskedMatch;
|
|
29868
31365
|
return {
|
|
29869
|
-
id:
|
|
31366
|
+
id: randomUUID15(),
|
|
29870
31367
|
eventId,
|
|
29871
31368
|
ruleId: m.ruleId,
|
|
29872
31369
|
category: m.category,
|
|
@@ -29891,12 +31388,12 @@ function scanPathIntoStore(db, target, opts = {}) {
|
|
|
29891
31388
|
}
|
|
29892
31389
|
|
|
29893
31390
|
// ../packages/local-ops/src/project-inventory.ts
|
|
29894
|
-
import { statSync as
|
|
31391
|
+
import { statSync as statSync9 } from "fs";
|
|
29895
31392
|
import { resolve as resolve3 } from "path";
|
|
29896
31393
|
function recordProjectInventory(db, target) {
|
|
29897
31394
|
try {
|
|
29898
31395
|
const abs = resolve3(target);
|
|
29899
|
-
|
|
31396
|
+
statSync9(abs);
|
|
29900
31397
|
const identity = resolveRepoIdentity(abs);
|
|
29901
31398
|
if (!identity) return null;
|
|
29902
31399
|
const sourceProjectId2 = db.sourceProject.upsert({
|
|
@@ -29945,17 +31442,17 @@ function reinvokeArgv(subcommand, extraArgs = []) {
|
|
|
29945
31442
|
|
|
29946
31443
|
// ../packages/local-ops/src/update-cache.ts
|
|
29947
31444
|
import { spawn } from "child_process";
|
|
29948
|
-
import { existsSync as
|
|
29949
|
-
import { join as
|
|
31445
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11, rmSync as rmSync5 } from "fs";
|
|
31446
|
+
import { join as join16 } from "path";
|
|
29950
31447
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
29951
31448
|
function cachePath(home) {
|
|
29952
|
-
return
|
|
31449
|
+
return join16(dataDir(home), "update-check.json");
|
|
29953
31450
|
}
|
|
29954
31451
|
function readCache(home) {
|
|
29955
31452
|
const path = cachePath(home);
|
|
29956
|
-
if (!
|
|
31453
|
+
if (!existsSync10(path)) return null;
|
|
29957
31454
|
try {
|
|
29958
|
-
const raw = JSON.parse(
|
|
31455
|
+
const raw = JSON.parse(readFileSync11(path, "utf8"));
|
|
29959
31456
|
if (!isRecord(raw) || typeof raw.checkedAt !== "number" || !isRecord(raw.report)) return null;
|
|
29960
31457
|
return raw;
|
|
29961
31458
|
} catch {
|
|
@@ -29963,9 +31460,9 @@ function readCache(home) {
|
|
|
29963
31460
|
}
|
|
29964
31461
|
}
|
|
29965
31462
|
function writeCache(home, cache) {
|
|
29966
|
-
if (!
|
|
31463
|
+
if (!existsSync10(dataDir(home))) return;
|
|
29967
31464
|
try {
|
|
29968
|
-
|
|
31465
|
+
writeOwnerOnlyFileSync(cachePath(home), `${JSON.stringify(cache, null, 2)}
|
|
29969
31466
|
`);
|
|
29970
31467
|
} catch {
|
|
29971
31468
|
}
|
|
@@ -29975,7 +31472,7 @@ function isStale(cache, ttlMs = CHECK_TTL_MS) {
|
|
|
29975
31472
|
}
|
|
29976
31473
|
function clearCache(home) {
|
|
29977
31474
|
try {
|
|
29978
|
-
|
|
31475
|
+
rmSync5(cachePath(home), { force: true });
|
|
29979
31476
|
} catch {
|
|
29980
31477
|
}
|
|
29981
31478
|
}
|
|
@@ -30082,9 +31579,9 @@ var COMMAND_SPECS = [
|
|
|
30082
31579
|
{ name: "stats", summary: "Print findings / enforcement / detections from the local store" },
|
|
30083
31580
|
{
|
|
30084
31581
|
name: "detections",
|
|
30085
|
-
argHint: "[update]",
|
|
31582
|
+
argHint: "[update|unquarantine]",
|
|
30086
31583
|
summary: "List installed detection packs + available updates (updates are manual)",
|
|
30087
|
-
args: ["update"]
|
|
31584
|
+
args: ["update", "unquarantine"]
|
|
30088
31585
|
},
|
|
30089
31586
|
{
|
|
30090
31587
|
name: "plugins",
|
|
@@ -30092,6 +31589,11 @@ var COMMAND_SPECS = [
|
|
|
30092
31589
|
args: ["list", "install"]
|
|
30093
31590
|
},
|
|
30094
31591
|
{ name: "dashboard", summary: "Launch the local web dashboard and open it in the browser" },
|
|
31592
|
+
{
|
|
31593
|
+
name: "extension",
|
|
31594
|
+
summary: "Install/check the native-messaging host for the browser extension (Chrome)",
|
|
31595
|
+
args: ["install", "status"]
|
|
31596
|
+
},
|
|
30095
31597
|
{
|
|
30096
31598
|
name: "exception",
|
|
30097
31599
|
summary: "Manage detection exceptions (approve, add, list, show, revoke, rotate-key)",
|
|
@@ -30134,13 +31636,14 @@ function commandsHelp() {
|
|
|
30134
31636
|
}
|
|
30135
31637
|
|
|
30136
31638
|
// src/commands/check-updates.ts
|
|
30137
|
-
import { existsSync as
|
|
31639
|
+
import { existsSync as existsSync11 } from "fs";
|
|
30138
31640
|
import { parseArgs } from "util";
|
|
30139
31641
|
|
|
30140
31642
|
// src/lib/args.ts
|
|
31643
|
+
import { resolve as resolve4 } from "path";
|
|
30141
31644
|
var HOME_OPTION = { home: { type: "string" } };
|
|
30142
31645
|
function homeBase(home) {
|
|
30143
|
-
return home
|
|
31646
|
+
return home === void 0 ? defaultDataDir() : resolve4(home);
|
|
30144
31647
|
}
|
|
30145
31648
|
|
|
30146
31649
|
// src/commands/check-updates.ts
|
|
@@ -30165,7 +31668,7 @@ function runCheckUpdates(argv) {
|
|
|
30165
31668
|
out.write(`${String(ups.length)} update(s) available \u2014 run \`aka update\` to apply.
|
|
30166
31669
|
`);
|
|
30167
31670
|
}
|
|
30168
|
-
if (
|
|
31671
|
+
if (existsSync11(dataDir(home))) {
|
|
30169
31672
|
const notifiedPluginIds = readCache(home)?.notifiedPluginIds ?? [];
|
|
30170
31673
|
writeCache(home, { checkedAt: Date.now(), report, notifiedPluginIds });
|
|
30171
31674
|
}
|
|
@@ -30319,41 +31822,48 @@ function runCompletion(argv) {
|
|
|
30319
31822
|
|
|
30320
31823
|
// src/commands/dashboard.ts
|
|
30321
31824
|
import { spawn as spawn3 } from "child_process";
|
|
30322
|
-
import { existsSync as
|
|
31825
|
+
import { existsSync as existsSync12 } from "fs";
|
|
30323
31826
|
import { createRequire as createRequire2 } from "module";
|
|
30324
31827
|
import { createServer } from "net";
|
|
30325
|
-
import { dirname as
|
|
30326
|
-
import { fileURLToPath as
|
|
31828
|
+
import { dirname as dirname6, join as join17 } from "path";
|
|
31829
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
30327
31830
|
import { parseArgs as parseArgs2 } from "util";
|
|
30328
31831
|
|
|
30329
31832
|
// src/lib/open-url.ts
|
|
30330
31833
|
import { spawn as spawn2 } from "child_process";
|
|
30331
|
-
function
|
|
30332
|
-
|
|
30333
|
-
|
|
30334
|
-
|
|
31834
|
+
function openUrlCommand(platform2, url2) {
|
|
31835
|
+
if (platform2 === "darwin") return { command: "open", args: [url2] };
|
|
31836
|
+
if (platform2 === "win32") return { command: "cmd", args: ["/c", "start", "", url2] };
|
|
31837
|
+
return { command: "xdg-open", args: [url2] };
|
|
31838
|
+
}
|
|
31839
|
+
var DETACHED = Object.freeze({ stdio: "ignore", detached: true });
|
|
31840
|
+
function openUrl(url2, deps = {}) {
|
|
31841
|
+
const { command, args } = openUrlCommand(deps.platform ?? process.platform, url2);
|
|
30335
31842
|
try {
|
|
30336
|
-
spawn2(command, args,
|
|
31843
|
+
const child = (deps.spawn ?? spawn2)(command, [...args], DETACHED);
|
|
31844
|
+
child.on("error", () => {
|
|
31845
|
+
});
|
|
31846
|
+
child.unref();
|
|
30337
31847
|
} catch {
|
|
30338
31848
|
}
|
|
30339
31849
|
}
|
|
30340
31850
|
|
|
30341
31851
|
// src/commands/dashboard.ts
|
|
30342
31852
|
var require3 = createRequire2(import.meta.url);
|
|
30343
|
-
var here =
|
|
31853
|
+
var here = dirname6(fileURLToPath3(import.meta.url));
|
|
30344
31854
|
var BIND_HOST = "127.0.0.1";
|
|
30345
31855
|
function pkgBase() {
|
|
30346
|
-
return isSea() ?
|
|
31856
|
+
return isSea() ? dirname6(process.execPath) : join17(here, "..");
|
|
30347
31857
|
}
|
|
30348
31858
|
function isPortFree(port) {
|
|
30349
|
-
return new Promise((
|
|
31859
|
+
return new Promise((resolve6) => {
|
|
30350
31860
|
const probe = createServer();
|
|
30351
31861
|
probe.once("error", () => {
|
|
30352
|
-
|
|
31862
|
+
resolve6(false);
|
|
30353
31863
|
});
|
|
30354
31864
|
probe.once("listening", () => {
|
|
30355
31865
|
probe.close(() => {
|
|
30356
|
-
|
|
31866
|
+
resolve6(true);
|
|
30357
31867
|
});
|
|
30358
31868
|
});
|
|
30359
31869
|
probe.listen(port, BIND_HOST);
|
|
@@ -30397,14 +31907,14 @@ async function runDashboard(argv) {
|
|
|
30397
31907
|
refreshUpdateMirror(dataDir());
|
|
30398
31908
|
const base = pkgBase();
|
|
30399
31909
|
const bundledServer = [
|
|
30400
|
-
|
|
30401
|
-
|
|
30402
|
-
].find((p) =>
|
|
31910
|
+
join17(base, "web-ui", "server.js"),
|
|
31911
|
+
join17(base, "web-ui", "web-ui", "server.js")
|
|
31912
|
+
].find((p) => existsSync12(p));
|
|
30403
31913
|
if (bundledServer) {
|
|
30404
31914
|
launchStandalone(bundledServer, port, shouldOpen, url2);
|
|
30405
31915
|
return;
|
|
30406
31916
|
}
|
|
30407
|
-
const webUiDir =
|
|
31917
|
+
const webUiDir = dirname6(require3.resolve("@akasecurity/web-ui/package.json"));
|
|
30408
31918
|
const nextBin = require3.resolve("next/dist/bin/next", { paths: [webUiDir] });
|
|
30409
31919
|
const startNext = () => {
|
|
30410
31920
|
const child = spawn3(
|
|
@@ -30418,7 +31928,7 @@ async function runDashboard(argv) {
|
|
|
30418
31928
|
onReadyOpen(child, shouldOpen, url2, `Starting the AKA dashboard at ${url2}
|
|
30419
31929
|
`);
|
|
30420
31930
|
};
|
|
30421
|
-
if (
|
|
31931
|
+
if (existsSync12(join17(webUiDir, ".next", "BUILD_ID"))) {
|
|
30422
31932
|
startNext();
|
|
30423
31933
|
return;
|
|
30424
31934
|
}
|
|
@@ -30441,7 +31951,7 @@ function launchStandalone(serverJs, port, shouldOpen, url2) {
|
|
|
30441
31951
|
return;
|
|
30442
31952
|
}
|
|
30443
31953
|
const child = spawn3(reinvoke.command, reinvoke.args, {
|
|
30444
|
-
cwd:
|
|
31954
|
+
cwd: dirname6(serverJs),
|
|
30445
31955
|
stdio: ["inherit", "pipe", "inherit"],
|
|
30446
31956
|
// Inherit the parent env so the child server sees PATH etc.; PORT/HOSTNAME tell
|
|
30447
31957
|
// Next's standalone server where to bind (its only port input).
|
|
@@ -30484,16 +31994,18 @@ function onReadyOpen(child, shouldOpen, url2, banner) {
|
|
|
30484
31994
|
|
|
30485
31995
|
// src/commands/detections.ts
|
|
30486
31996
|
import { parseArgs as parseArgs3 } from "util";
|
|
31997
|
+
var SUBCOMMANDS = ["update", "unquarantine"];
|
|
30487
31998
|
async function runDetections(argv) {
|
|
30488
31999
|
const { values, positionals } = parseArgs3({
|
|
30489
32000
|
args: argv,
|
|
30490
32001
|
options: { ...HOME_OPTION, all: { type: "boolean" } },
|
|
30491
32002
|
allowPositionals: true
|
|
30492
32003
|
});
|
|
30493
|
-
const
|
|
32004
|
+
const named = SUBCOMMANDS.find((s) => s === positionals[0]);
|
|
32005
|
+
const sub = named ?? "list";
|
|
30494
32006
|
if (sub === "list" && positionals.length > 0) {
|
|
30495
32007
|
process.stderr.write(
|
|
30496
|
-
`aka detections: unknown subcommand '${positionals[0] ?? ""}' (
|
|
32008
|
+
`aka detections: unknown subcommand '${positionals[0] ?? ""}' (expected one of: ${SUBCOMMANDS.join(", ")})
|
|
30497
32009
|
`
|
|
30498
32010
|
);
|
|
30499
32011
|
process.exitCode = 1;
|
|
@@ -30505,6 +32017,8 @@ async function runDetections(argv) {
|
|
|
30505
32017
|
db.installedPacks.recordInventory(bundledDetections(), cliRecordedBy());
|
|
30506
32018
|
if (sub === "update") {
|
|
30507
32019
|
await runUpdateSub(db, positionals.slice(1), values.all === true);
|
|
32020
|
+
} else if (sub === "unquarantine") {
|
|
32021
|
+
runUnquarantineSub(db);
|
|
30508
32022
|
} else {
|
|
30509
32023
|
await runListSub(db);
|
|
30510
32024
|
}
|
|
@@ -30527,6 +32041,15 @@ async function runListSub(db) {
|
|
|
30527
32041
|
${String(items.length)} pack(s) \xB7 ${String(items.reduce((n, i) => n + i.ruleCount, 0))} rule(s) \xB7 ${String(active)} enabled
|
|
30528
32042
|
`
|
|
30529
32043
|
);
|
|
32044
|
+
const quarantined = db.ruleProbeCache.countQuarantined();
|
|
32045
|
+
if (quarantined > 0) {
|
|
32046
|
+
out.write(
|
|
32047
|
+
`
|
|
32048
|
+
\u26A0 ${String(quarantined)} rule(s) quarantined for exceeding the ReDoS timing budget \u2014 they are excluded from every scan.
|
|
32049
|
+
aka detections unquarantine # forget the verdicts and measure them again
|
|
32050
|
+
`
|
|
32051
|
+
);
|
|
32052
|
+
}
|
|
30530
32053
|
if (counts.updates > 0) {
|
|
30531
32054
|
out.write(
|
|
30532
32055
|
`
|
|
@@ -30539,6 +32062,20 @@ ${String(items.length)} pack(s) \xB7 ${String(items.reduce((n, i) => n + i.ruleC
|
|
|
30539
32062
|
out.write("\n\u2713 All detection packs are up to date with this CLI.\n");
|
|
30540
32063
|
}
|
|
30541
32064
|
}
|
|
32065
|
+
function runUnquarantineSub(db) {
|
|
32066
|
+
const { refused, cleared } = db.ruleProbeCache.clearQuarantined();
|
|
32067
|
+
if (refused) {
|
|
32068
|
+
process.stderr.write(
|
|
32069
|
+
"aka detections unquarantine: the store refused the write (another process may be holding it). Nothing was cleared \u2014 try again.\n"
|
|
32070
|
+
);
|
|
32071
|
+
process.exitCode = 1;
|
|
32072
|
+
return;
|
|
32073
|
+
}
|
|
32074
|
+
process.stdout.write(
|
|
32075
|
+
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.
|
|
32076
|
+
`
|
|
32077
|
+
);
|
|
32078
|
+
}
|
|
30542
32079
|
async function runUpdateSub(db, ids, all) {
|
|
30543
32080
|
const { items } = await db.detections.listDetections({ filter: "all" });
|
|
30544
32081
|
const byId = new Map(items.map((i) => [i.id, i]));
|
|
@@ -30650,21 +32187,39 @@ function processHiddenChar(state, ch) {
|
|
|
30650
32187
|
return { ...state, value: state.value + ch };
|
|
30651
32188
|
}
|
|
30652
32189
|
var CLEANUP_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
30653
|
-
function
|
|
32190
|
+
function processIo() {
|
|
32191
|
+
return {
|
|
32192
|
+
input: process.stdin,
|
|
32193
|
+
output: process.stdout,
|
|
32194
|
+
errorOutput: process.stderr,
|
|
32195
|
+
signals: {
|
|
32196
|
+
once: (signal, handler) => {
|
|
32197
|
+
process.once(signal, handler);
|
|
32198
|
+
},
|
|
32199
|
+
off: (signal, handler) => {
|
|
32200
|
+
process.removeListener(signal, handler);
|
|
32201
|
+
},
|
|
32202
|
+
raise: (signal) => {
|
|
32203
|
+
process.kill(process.pid, signal);
|
|
32204
|
+
}
|
|
32205
|
+
}
|
|
32206
|
+
};
|
|
32207
|
+
}
|
|
32208
|
+
function terminalPrompter(io = processIo()) {
|
|
30654
32209
|
return {
|
|
30655
32210
|
out: (text) => {
|
|
30656
|
-
|
|
32211
|
+
io.output.write(text);
|
|
30657
32212
|
},
|
|
30658
32213
|
err: (text) => {
|
|
30659
|
-
|
|
32214
|
+
io.errorOutput.write(text);
|
|
30660
32215
|
},
|
|
30661
|
-
isInteractive:
|
|
32216
|
+
isInteractive: io.input.isTTY && io.output.isTTY,
|
|
30662
32217
|
ask(question) {
|
|
30663
|
-
return new Promise((
|
|
30664
|
-
const rl = createInterface({ input:
|
|
32218
|
+
return new Promise((resolve6) => {
|
|
32219
|
+
const rl = createInterface({ input: io.input, output: io.output });
|
|
30665
32220
|
rl.question(question, (answer) => {
|
|
30666
32221
|
rl.close();
|
|
30667
|
-
|
|
32222
|
+
resolve6(answer);
|
|
30668
32223
|
});
|
|
30669
32224
|
});
|
|
30670
32225
|
},
|
|
@@ -30675,9 +32230,9 @@ function terminalPrompter() {
|
|
|
30675
32230
|
// fatal signals: a security tool must never brick the terminal it was
|
|
30676
32231
|
// invoked from.
|
|
30677
32232
|
askHidden(question) {
|
|
30678
|
-
return new Promise((
|
|
30679
|
-
const stdin =
|
|
30680
|
-
|
|
32233
|
+
return new Promise((resolve6, reject) => {
|
|
32234
|
+
const stdin = io.input;
|
|
32235
|
+
io.output.write(question);
|
|
30681
32236
|
stdin.setRawMode(true);
|
|
30682
32237
|
stdin.resume();
|
|
30683
32238
|
let state = { value: "", esc: "none" };
|
|
@@ -30687,7 +32242,7 @@ function terminalPrompter() {
|
|
|
30687
32242
|
restored = true;
|
|
30688
32243
|
stdin.removeListener("data", onData);
|
|
30689
32244
|
stdin.removeListener("error", onError);
|
|
30690
|
-
for (const sig of CLEANUP_SIGNALS)
|
|
32245
|
+
for (const sig of CLEANUP_SIGNALS) io.signals.off(sig, onSignal);
|
|
30691
32246
|
try {
|
|
30692
32247
|
stdin.setRawMode(false);
|
|
30693
32248
|
} catch {
|
|
@@ -30696,16 +32251,16 @@ function terminalPrompter() {
|
|
|
30696
32251
|
};
|
|
30697
32252
|
const finish = (err) => {
|
|
30698
32253
|
restore();
|
|
30699
|
-
|
|
32254
|
+
io.output.write("\n");
|
|
30700
32255
|
if (err) reject(err);
|
|
30701
|
-
else
|
|
32256
|
+
else resolve6(state.value);
|
|
30702
32257
|
};
|
|
30703
32258
|
const onError = (err) => {
|
|
30704
32259
|
finish(err);
|
|
30705
32260
|
};
|
|
30706
32261
|
const onSignal = (sig) => {
|
|
30707
32262
|
restore();
|
|
30708
|
-
|
|
32263
|
+
io.signals.raise(sig);
|
|
30709
32264
|
};
|
|
30710
32265
|
const onData = (chunk) => {
|
|
30711
32266
|
try {
|
|
@@ -30726,12 +32281,12 @@ function terminalPrompter() {
|
|
|
30726
32281
|
};
|
|
30727
32282
|
stdin.on("data", onData);
|
|
30728
32283
|
stdin.once("error", onError);
|
|
30729
|
-
for (const sig of CLEANUP_SIGNALS)
|
|
32284
|
+
for (const sig of CLEANUP_SIGNALS) io.signals.once(sig, onSignal);
|
|
30730
32285
|
});
|
|
30731
32286
|
},
|
|
30732
32287
|
async readAllStdin() {
|
|
30733
32288
|
const chunks = [];
|
|
30734
|
-
for await (const chunk of
|
|
32289
|
+
for await (const chunk of io.input) {
|
|
30735
32290
|
chunks.push(chunk);
|
|
30736
32291
|
}
|
|
30737
32292
|
return Buffer.concat(chunks).toString("utf8");
|
|
@@ -31052,7 +32607,6 @@ async function runApproveReveal(pointer, values, io, base) {
|
|
|
31052
32607
|
const vault = new SecretVault({
|
|
31053
32608
|
repo: db.secretVault,
|
|
31054
32609
|
keys: createKeyProvider(readWorkspaceSettings(base).vaultKeyCustody, keysDir(base)),
|
|
31055
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
31056
32610
|
// Read live so a consent revocation applies to the very next call.
|
|
31057
32611
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent)
|
|
31058
32612
|
});
|
|
@@ -31344,11 +32898,11 @@ async function runShow(argv, io) {
|
|
|
31344
32898
|
ensurePacks();
|
|
31345
32899
|
const rule = getLoadedRules().find((r) => r.id === ex.ruleId);
|
|
31346
32900
|
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
32901
|
const label = (name) => ` ${name.padEnd(10)}`;
|
|
31349
32902
|
const lines = [
|
|
32903
|
+
`${label("Id")}${shortId(ex.id)}`,
|
|
31350
32904
|
`${label("Rule")}${ruleLine}`,
|
|
31351
|
-
`${label("Value")}${ex.maskedValue}
|
|
32905
|
+
`${label("Value")}${ex.maskedValue} (fingerprint key v${String(ex.keyVersion)})`,
|
|
31352
32906
|
`${label("Scope")}${scopeSummary(ex)}${ex.maxUses !== null ? ` \xB7 max ${String(ex.maxUses)} use${ex.maxUses === 1 ? "" : "s"}` : ""}`,
|
|
31353
32907
|
`${label("Reason")}"${ex.justification}"`,
|
|
31354
32908
|
`${label("Created")}${formatTimestamp(ex.createdAt)} by ${ex.createdBy} via ${viaLabel(ex.createdVia)}`,
|
|
@@ -31412,6 +32966,17 @@ async function runRevoke(argv, io) {
|
|
|
31412
32966
|
db.close();
|
|
31413
32967
|
}
|
|
31414
32968
|
}
|
|
32969
|
+
async function countApprovableBlocked(db, dir) {
|
|
32970
|
+
const key = (() => {
|
|
32971
|
+
try {
|
|
32972
|
+
return keyStateOf(readFingerprintKey(dir));
|
|
32973
|
+
} catch {
|
|
32974
|
+
return { status: "unreadable" };
|
|
32975
|
+
}
|
|
32976
|
+
})();
|
|
32977
|
+
const rows = await db.exceptions.recentBlocked(BLOCKED_DETECTIONS_RETENTION_MS);
|
|
32978
|
+
return rows.filter((row) => isMatchableUnder(row.keyVersion, key)).length;
|
|
32979
|
+
}
|
|
31415
32980
|
async function runRotateKey(argv, io) {
|
|
31416
32981
|
const { values } = parseArgs4({
|
|
31417
32982
|
args: argv,
|
|
@@ -31438,6 +33003,10 @@ written under the old key simply stop matching. They remain listed for audit.
|
|
|
31438
33003
|
`);
|
|
31439
33004
|
}
|
|
31440
33005
|
}
|
|
33006
|
+
io.out(`
|
|
33007
|
+
${rotationBlockedLedgerNote(await countApprovableBlocked(db, dir))}
|
|
33008
|
+
|
|
33009
|
+
`);
|
|
31441
33010
|
if (values.yes !== true) {
|
|
31442
33011
|
if (!io.isInteractive) {
|
|
31443
33012
|
throw new Error("pass --yes to rotate non-interactively");
|
|
@@ -31466,7 +33035,6 @@ Existing grants no longer match; re-approve deliberately where still needed.
|
|
|
31466
33035
|
const vault = new SecretVault({
|
|
31467
33036
|
repo: db.secretVault,
|
|
31468
33037
|
keys: createKeyProvider(readWorkspaceSettings(base).vaultKeyCustody, keysDir(base)),
|
|
31469
|
-
fingerprintKey: next,
|
|
31470
33038
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent)
|
|
31471
33039
|
});
|
|
31472
33040
|
const refreshed = await vault.refreshFingerprints(next);
|
|
@@ -31507,14 +33075,208 @@ async function runException(argv, io = terminalPrompter()) {
|
|
|
31507
33075
|
await handler(rest, io);
|
|
31508
33076
|
}
|
|
31509
33077
|
|
|
33078
|
+
// src/commands/extension.ts
|
|
33079
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
33080
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
33081
|
+
import { createRequire as createRequire3 } from "module";
|
|
33082
|
+
import { homedir as homedir5 } from "os";
|
|
33083
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
33084
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
33085
|
+
var require4 = createRequire3(import.meta.url);
|
|
33086
|
+
var here2 = dirname7(fileURLToPath4(import.meta.url));
|
|
33087
|
+
function cliRoot() {
|
|
33088
|
+
return isSea() ? dirname7(process.execPath) : join18(here2, "..");
|
|
33089
|
+
}
|
|
33090
|
+
var EXTENSION_ID = "mdoiaiemcnjnaokmcmgbikcdhgiemdof";
|
|
33091
|
+
var NATIVE_HOST_NAME = "com.akasecurity.aka";
|
|
33092
|
+
function nativeHostManifest(hostPath) {
|
|
33093
|
+
return {
|
|
33094
|
+
name: NATIVE_HOST_NAME,
|
|
33095
|
+
description: "AI Traffic Control native messaging host \u2014 bridges the browser extension to the local ~/.aka SQLite store.",
|
|
33096
|
+
path: hostPath,
|
|
33097
|
+
type: "stdio",
|
|
33098
|
+
allowed_origins: [`chrome-extension://${EXTENSION_ID}/`]
|
|
33099
|
+
};
|
|
33100
|
+
}
|
|
33101
|
+
function resolveHostScript(root = cliRoot()) {
|
|
33102
|
+
const bundled = join18(root, "native-host", "host.js");
|
|
33103
|
+
if (existsSync13(bundled)) return bundled;
|
|
33104
|
+
try {
|
|
33105
|
+
const pkgJson = require4.resolve("@akasecurity/plugin-browser-extension/package.json");
|
|
33106
|
+
const devBuilt = join18(dirname7(pkgJson), "native-host", "host.js");
|
|
33107
|
+
if (existsSync13(devBuilt)) return devBuilt;
|
|
33108
|
+
} catch {
|
|
33109
|
+
}
|
|
33110
|
+
return null;
|
|
33111
|
+
}
|
|
33112
|
+
function resolveExtensionDist(root = cliRoot()) {
|
|
33113
|
+
const bundled = join18(root, "extension");
|
|
33114
|
+
if (existsSync13(bundled)) return bundled;
|
|
33115
|
+
try {
|
|
33116
|
+
const pkgJson = require4.resolve("@akasecurity/plugin-browser-extension/package.json");
|
|
33117
|
+
const devDist = join18(dirname7(pkgJson), "dist");
|
|
33118
|
+
if (existsSync13(devDist)) return devDist;
|
|
33119
|
+
} catch {
|
|
33120
|
+
}
|
|
33121
|
+
return null;
|
|
33122
|
+
}
|
|
33123
|
+
function chromeManifestDir(platform2 = process.platform) {
|
|
33124
|
+
if (platform2 === "darwin") {
|
|
33125
|
+
return join18(
|
|
33126
|
+
homedir5(),
|
|
33127
|
+
"Library",
|
|
33128
|
+
"Application Support",
|
|
33129
|
+
"Google",
|
|
33130
|
+
"Chrome",
|
|
33131
|
+
"NativeMessagingHosts"
|
|
33132
|
+
);
|
|
33133
|
+
}
|
|
33134
|
+
if (platform2 === "win32") {
|
|
33135
|
+
return join18(homedir5(), ".aka", "native-messaging");
|
|
33136
|
+
}
|
|
33137
|
+
return join18(homedir5(), ".config", "google-chrome", "NativeMessagingHosts");
|
|
33138
|
+
}
|
|
33139
|
+
function resolveNodeForLauncher() {
|
|
33140
|
+
if (!isSea()) return process.execPath;
|
|
33141
|
+
try {
|
|
33142
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
33143
|
+
const out = execFileSync3(probe, ["node"], { encoding: "utf8" }).split(/\r?\n/)[0]?.trim();
|
|
33144
|
+
if (out === void 0 || out === "") return null;
|
|
33145
|
+
return out;
|
|
33146
|
+
} catch {
|
|
33147
|
+
return null;
|
|
33148
|
+
}
|
|
33149
|
+
}
|
|
33150
|
+
function launcherPath(manifestDir, platform2) {
|
|
33151
|
+
return join18(
|
|
33152
|
+
manifestDir,
|
|
33153
|
+
platform2 === "win32" ? `${NATIVE_HOST_NAME}.cmd` : `${NATIVE_HOST_NAME}.sh`
|
|
33154
|
+
);
|
|
33155
|
+
}
|
|
33156
|
+
function posixQuote(value) {
|
|
33157
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
33158
|
+
}
|
|
33159
|
+
function cmdQuote(value) {
|
|
33160
|
+
return `"${value.replaceAll("%", "%%")}"`;
|
|
33161
|
+
}
|
|
33162
|
+
function writeLauncher(manifestDir, hostScript, nodePath, platform2) {
|
|
33163
|
+
const path = launcherPath(manifestDir, platform2);
|
|
33164
|
+
const tmp = `${path}.tmp`;
|
|
33165
|
+
if (platform2 === "win32") {
|
|
33166
|
+
writeFileSync7(tmp, `@echo off\r
|
|
33167
|
+
${cmdQuote(nodePath)} ${cmdQuote(hostScript)} %*\r
|
|
33168
|
+
`);
|
|
33169
|
+
} else {
|
|
33170
|
+
writeFileSync7(tmp, `#!/bin/sh
|
|
33171
|
+
exec ${posixQuote(nodePath)} ${posixQuote(hostScript)} "$@"
|
|
33172
|
+
`, {
|
|
33173
|
+
mode: 493
|
|
33174
|
+
});
|
|
33175
|
+
}
|
|
33176
|
+
renameSync5(tmp, path);
|
|
33177
|
+
return path;
|
|
33178
|
+
}
|
|
33179
|
+
function windowsRegistryKey() {
|
|
33180
|
+
return `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${NATIVE_HOST_NAME}`;
|
|
33181
|
+
}
|
|
33182
|
+
function registerWindowsHost(manifestPath) {
|
|
33183
|
+
execFileSync3("reg", [
|
|
33184
|
+
"add",
|
|
33185
|
+
windowsRegistryKey(),
|
|
33186
|
+
"/ve",
|
|
33187
|
+
"/t",
|
|
33188
|
+
"REG_SZ",
|
|
33189
|
+
"/d",
|
|
33190
|
+
manifestPath,
|
|
33191
|
+
"/f"
|
|
33192
|
+
]);
|
|
33193
|
+
}
|
|
33194
|
+
function runExtension(argv) {
|
|
33195
|
+
const sub = argv[0];
|
|
33196
|
+
if (sub === "install") {
|
|
33197
|
+
runInstall(chromeManifestDir());
|
|
33198
|
+
return;
|
|
33199
|
+
}
|
|
33200
|
+
if (sub === "status") {
|
|
33201
|
+
runStatus(chromeManifestDir());
|
|
33202
|
+
return;
|
|
33203
|
+
}
|
|
33204
|
+
process.stderr.write(
|
|
33205
|
+
`aka extension: unknown subcommand '${sub ?? ""}'
|
|
33206
|
+
Usage: aka extension <install|status>
|
|
33207
|
+
`
|
|
33208
|
+
);
|
|
33209
|
+
process.exitCode = 1;
|
|
33210
|
+
}
|
|
33211
|
+
function runInstall(manifestDir, hostScript = resolveHostScript(), extensionDist = resolveExtensionDist(), nodePath = resolveNodeForLauncher()) {
|
|
33212
|
+
if (!hostScript) {
|
|
33213
|
+
process.stderr.write(
|
|
33214
|
+
"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"
|
|
33215
|
+
);
|
|
33216
|
+
process.exitCode = 1;
|
|
33217
|
+
return;
|
|
33218
|
+
}
|
|
33219
|
+
if (!nodePath) {
|
|
33220
|
+
process.stderr.write(
|
|
33221
|
+
"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"
|
|
33222
|
+
);
|
|
33223
|
+
process.exitCode = 1;
|
|
33224
|
+
return;
|
|
33225
|
+
}
|
|
33226
|
+
mkdirSync5(manifestDir, { recursive: true });
|
|
33227
|
+
const launcher = writeLauncher(manifestDir, hostScript, nodePath, process.platform);
|
|
33228
|
+
const manifestPath = join18(manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
33229
|
+
const tmp = `${manifestPath}.tmp`;
|
|
33230
|
+
writeFileSync7(tmp, `${JSON.stringify(nativeHostManifest(launcher), null, 2)}
|
|
33231
|
+
`, {
|
|
33232
|
+
mode: DATA_FILE_MODE
|
|
33233
|
+
});
|
|
33234
|
+
renameSync5(tmp, manifestPath);
|
|
33235
|
+
if (process.platform === "win32") {
|
|
33236
|
+
try {
|
|
33237
|
+
registerWindowsHost(manifestPath);
|
|
33238
|
+
} catch (err) {
|
|
33239
|
+
process.stderr.write(
|
|
33240
|
+
`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.
|
|
33241
|
+
`
|
|
33242
|
+
);
|
|
33243
|
+
process.exitCode = 1;
|
|
33244
|
+
return;
|
|
33245
|
+
}
|
|
33246
|
+
}
|
|
33247
|
+
process.stdout.write(
|
|
33248
|
+
`\u2713 Installed the AKA native-messaging host
|
|
33249
|
+
host script: ${hostScript}
|
|
33250
|
+
launcher: ${launcher}
|
|
33251
|
+
manifest: ${manifestPath}
|
|
33252
|
+
` + (process.platform === "win32" ? ` registry: ${windowsRegistryKey()}
|
|
33253
|
+
` : "") + `
|
|
33254
|
+
Next: load the extension in Chrome \u2014
|
|
33255
|
+
1. Open chrome://extensions
|
|
33256
|
+
2. Enable "Developer mode" (top right)
|
|
33257
|
+
3. Click "Load unpacked" and select:
|
|
33258
|
+
${extensionDist ?? "<run: pnpm --filter @akasecurity/plugin-browser-extension build>"}
|
|
33259
|
+
`
|
|
33260
|
+
);
|
|
33261
|
+
}
|
|
33262
|
+
function runStatus(manifestDir) {
|
|
33263
|
+
const manifestPath = join18(manifestDir, `${NATIVE_HOST_NAME}.json`);
|
|
33264
|
+
const installed = existsSync13(manifestPath);
|
|
33265
|
+
process.stdout.write(
|
|
33266
|
+
`native-messaging host: ${installed ? "installed" : "not installed"}
|
|
33267
|
+
manifest: ${manifestPath}
|
|
33268
|
+
` + (installed ? "" : " run `aka extension install` to set it up\n")
|
|
33269
|
+
);
|
|
33270
|
+
}
|
|
33271
|
+
|
|
31510
33272
|
// src/commands/init.ts
|
|
31511
|
-
import { existsSync as
|
|
31512
|
-
import { join as
|
|
33273
|
+
import { existsSync as existsSync15, lstatSync as lstatSync2, readdirSync as readdirSync7, readlinkSync, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
33274
|
+
import { dirname as dirname8, join as join19, resolve as resolve5 } from "path";
|
|
31513
33275
|
import * as readline from "readline/promises";
|
|
31514
33276
|
import { parseArgs as parseArgs6 } from "util";
|
|
31515
33277
|
|
|
31516
33278
|
// src/commands/plugins.ts
|
|
31517
|
-
import { existsSync as
|
|
33279
|
+
import { existsSync as existsSync14 } from "fs";
|
|
31518
33280
|
import { parseArgs as parseArgs5 } from "util";
|
|
31519
33281
|
function runPlugins(argv) {
|
|
31520
33282
|
const [sub, ...rest] = argv;
|
|
@@ -31531,7 +33293,7 @@ async function listPlugins(argv) {
|
|
|
31531
33293
|
const { values } = parseArgs5({ args: argv, options: HOME_OPTION });
|
|
31532
33294
|
const home = homeBase(values.home);
|
|
31533
33295
|
const active = /* @__PURE__ */ new Set();
|
|
31534
|
-
if (
|
|
33296
|
+
if (existsSync14(dbPath(home))) {
|
|
31535
33297
|
const db = openLocalDatabase(dataDir(home));
|
|
31536
33298
|
try {
|
|
31537
33299
|
const recent = await db.findings.recentFindings({ limit: 1e3 });
|
|
@@ -31540,7 +33302,7 @@ async function listPlugins(argv) {
|
|
|
31540
33302
|
db.close();
|
|
31541
33303
|
}
|
|
31542
33304
|
}
|
|
31543
|
-
const installed =
|
|
33305
|
+
const installed = installedAgentPluginVersions();
|
|
31544
33306
|
const out = process.stdout;
|
|
31545
33307
|
out.write("Agent plugins (the CLI is an optional hub \u2014 plugins also self-install):\n\n");
|
|
31546
33308
|
for (const a of AGENT_PLUGINS) {
|
|
@@ -31571,42 +33333,44 @@ function installPlugin(argv) {
|
|
|
31571
33333
|
return;
|
|
31572
33334
|
}
|
|
31573
33335
|
const ref = pluginRef(agent);
|
|
31574
|
-
|
|
33336
|
+
const cliBin = agent.cliBin;
|
|
33337
|
+
if (!ref || !cliBin) {
|
|
31575
33338
|
process.stdout.write(
|
|
31576
|
-
`${agent.
|
|
33339
|
+
agent.installHint ? `${agent.installHint}
|
|
33340
|
+
` : `${agent.name} has no automated install path yet \u2014 install it from the AKA marketplace in ${agent.name}, then run \`aka init\`.
|
|
31577
33341
|
`
|
|
31578
33342
|
);
|
|
31579
33343
|
return;
|
|
31580
33344
|
}
|
|
31581
|
-
if (!
|
|
33345
|
+
if (!createCliPluginManager(cliBin).available()) {
|
|
31582
33346
|
process.stdout.write(
|
|
31583
33347
|
`Installing ${agent.name}\u2026
|
|
31584
33348
|
|
|
31585
|
-
The \`
|
|
31586
|
-
Install it from inside
|
|
31587
|
-
|
|
31588
|
-
|
|
33349
|
+
The \`${cliBin}\` CLI isn't on your PATH, so I can't install it automatically.
|
|
33350
|
+
Install it from inside ${agent.name}:
|
|
33351
|
+
${cliBin} plugin marketplace add ${agent.marketplaceSource ?? ""}
|
|
33352
|
+
${cliBin} plugin install ${ref}
|
|
31589
33353
|
|
|
31590
33354
|
Then run \`aka init\` to set up the local store.
|
|
31591
33355
|
`
|
|
31592
33356
|
);
|
|
31593
33357
|
return;
|
|
31594
33358
|
}
|
|
31595
|
-
process.stdout.write(`Installing ${agent.name} via
|
|
33359
|
+
process.stdout.write(`Installing ${agent.name} via ${cliBin}\u2026
|
|
31596
33360
|
`);
|
|
31597
33361
|
const { ok } = installAgentPlugin(agent.id, "inherit");
|
|
31598
33362
|
if (ok) {
|
|
31599
33363
|
process.stdout.write(
|
|
31600
33364
|
`
|
|
31601
33365
|
\u2713 Installed ${agent.name}.
|
|
31602
|
-
\u21BB Restart
|
|
33366
|
+
\u21BB Restart ${agent.name} to load it.
|
|
31603
33367
|
Run \`aka init\` to scaffold your local store (if you haven't already).
|
|
31604
33368
|
`
|
|
31605
33369
|
);
|
|
31606
33370
|
} else {
|
|
31607
33371
|
process.stderr.write(
|
|
31608
33372
|
`
|
|
31609
|
-
\u2717 Install failed \u2014 see the output above, or add it in
|
|
33373
|
+
\u2717 Install failed \u2014 see the output above, or add it in ${agent.name} with \`${cliBin} plugin install ${ref}\`.
|
|
31610
33374
|
`
|
|
31611
33375
|
);
|
|
31612
33376
|
process.exitCode = 1;
|
|
@@ -31615,40 +33379,159 @@ Then run \`aka init\` to set up the local store.
|
|
|
31615
33379
|
|
|
31616
33380
|
// src/commands/init.ts
|
|
31617
33381
|
var PLUGIN_OFFER_IDENTITY = `${PRODUCT_NAME} \u2014 ${PRODUCT_TAGLINE}`;
|
|
33382
|
+
var STORE_DB = "the store database (including the prompt corpus)";
|
|
33383
|
+
var STORE_SETTINGS = "your settings file";
|
|
33384
|
+
function storeContents(home) {
|
|
33385
|
+
return /* @__PURE__ */ new Map([
|
|
33386
|
+
[home, "the store (including the prompt corpus in aka.db)"],
|
|
33387
|
+
[settingsDir(home), STORE_SETTINGS],
|
|
33388
|
+
[dataDir(home), STORE_DB],
|
|
33389
|
+
[keysDir(home), "the vault key"],
|
|
33390
|
+
[join19(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
33391
|
+
[dbPath(home), STORE_DB]
|
|
33392
|
+
]);
|
|
33393
|
+
}
|
|
33394
|
+
function storeTargets(home) {
|
|
33395
|
+
return [...storeContents(home).keys()];
|
|
33396
|
+
}
|
|
33397
|
+
function storeModeTargets(home) {
|
|
33398
|
+
const targets = new Set(storeTargets(home));
|
|
33399
|
+
const data = dataDir(home);
|
|
33400
|
+
try {
|
|
33401
|
+
for (const name of readdirSync7(data)) {
|
|
33402
|
+
if (name.endsWith(".partial")) continue;
|
|
33403
|
+
targets.add(join19(data, name));
|
|
33404
|
+
}
|
|
33405
|
+
} catch {
|
|
33406
|
+
}
|
|
33407
|
+
return [...targets];
|
|
33408
|
+
}
|
|
31618
33409
|
function looseStorePaths(home) {
|
|
31619
33410
|
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) => {
|
|
33411
|
+
return storeModeTargets(home).filter((p) => {
|
|
31628
33412
|
try {
|
|
31629
|
-
|
|
33413
|
+
if (lstatSync2(p).isSymbolicLink()) return false;
|
|
33414
|
+
return (statSync10(p).mode & 63) !== 0;
|
|
31630
33415
|
} catch {
|
|
31631
33416
|
return false;
|
|
31632
33417
|
}
|
|
31633
33418
|
});
|
|
31634
33419
|
}
|
|
33420
|
+
function symlinkedStorePaths(home, platform2 = process.platform) {
|
|
33421
|
+
return [...storeContents(home)].flatMap(([path, holds]) => {
|
|
33422
|
+
try {
|
|
33423
|
+
if (!lstatSync2(path).isSymbolicLink()) return [];
|
|
33424
|
+
return [
|
|
33425
|
+
{
|
|
33426
|
+
path,
|
|
33427
|
+
target: linkTarget(path),
|
|
33428
|
+
holds,
|
|
33429
|
+
// existsSync follows the link, so a target that is gone reads as
|
|
33430
|
+
// absent here while lstat above still sees the link itself.
|
|
33431
|
+
missing: !existsSync15(path),
|
|
33432
|
+
mode: targetMode(path, platform2)
|
|
33433
|
+
}
|
|
33434
|
+
];
|
|
33435
|
+
} catch {
|
|
33436
|
+
return [];
|
|
33437
|
+
}
|
|
33438
|
+
});
|
|
33439
|
+
}
|
|
33440
|
+
function linkTarget(path) {
|
|
33441
|
+
try {
|
|
33442
|
+
return realpathSync3(path);
|
|
33443
|
+
} catch {
|
|
33444
|
+
return resolve5(dirname8(path), readlinkSync(path));
|
|
33445
|
+
}
|
|
33446
|
+
}
|
|
33447
|
+
function targetMode(path, platform2) {
|
|
33448
|
+
if (platform2 === "win32") return void 0;
|
|
33449
|
+
try {
|
|
33450
|
+
return statSync10(path).mode & 511;
|
|
33451
|
+
} catch {
|
|
33452
|
+
return void 0;
|
|
33453
|
+
}
|
|
33454
|
+
}
|
|
33455
|
+
function symlinkWarnings(paths, platform2 = process.platform) {
|
|
33456
|
+
const seeAlso = '(see the "Data at rest" note in SECURITY.md)';
|
|
33457
|
+
return paths.map(({ path, target, holds, missing, mode }) => {
|
|
33458
|
+
if (missing) {
|
|
33459
|
+
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}
|
|
33460
|
+
`;
|
|
33461
|
+
}
|
|
33462
|
+
const inherited = mode === void 0 ? "" : ` (currently ${formatMode(mode)}${(mode & 63) !== 0 ? ", NOT owner-only" : ""})`;
|
|
33463
|
+
const kept = platform2 === "win32" ? "" : "permissions are never changed through a symlink, so ";
|
|
33464
|
+
const under = platform2 === "win32" ? "" : " under that target's own permissions";
|
|
33465
|
+
return ` \u26A0 ${path} is a symlink to ${target}${inherited} \u2014 ${kept}${holds} is written there${under} ${seeAlso}
|
|
33466
|
+
`;
|
|
33467
|
+
}).join("");
|
|
33468
|
+
}
|
|
33469
|
+
function formatMode(mode) {
|
|
33470
|
+
return `0${mode.toString(8).padStart(3, "0")}`;
|
|
33471
|
+
}
|
|
33472
|
+
function assertStoreLinksResolve(home) {
|
|
33473
|
+
for (const dir of [home, settingsDir(home), dataDir(home)]) {
|
|
33474
|
+
if (!isBrokenLink(dir)) continue;
|
|
33475
|
+
throw new Error(
|
|
33476
|
+
`${dir} is a symlink to ${linkTarget(dir)}, which does not exist \u2014 create that target or remove the link, then re-run \`aka init\``
|
|
33477
|
+
);
|
|
33478
|
+
}
|
|
33479
|
+
}
|
|
33480
|
+
function isBrokenLink(path) {
|
|
33481
|
+
try {
|
|
33482
|
+
return lstatSync2(path).isSymbolicLink() && !existsSync15(path);
|
|
33483
|
+
} catch {
|
|
33484
|
+
return false;
|
|
33485
|
+
}
|
|
33486
|
+
}
|
|
33487
|
+
function assertStorePathsAreDirectories(home) {
|
|
33488
|
+
for (const dir of [home, settingsDir(home), dataDir(home), keysDir(home)]) {
|
|
33489
|
+
if (!existsAsNonDirectory(dir)) continue;
|
|
33490
|
+
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";
|
|
33491
|
+
throw new Error(`${dir} ${occupant}. AKA keeps its store there; re-run \`aka init\` after.`);
|
|
33492
|
+
}
|
|
33493
|
+
}
|
|
33494
|
+
function isSymlink2(path) {
|
|
33495
|
+
try {
|
|
33496
|
+
return lstatSync2(path).isSymbolicLink();
|
|
33497
|
+
} catch {
|
|
33498
|
+
return false;
|
|
33499
|
+
}
|
|
33500
|
+
}
|
|
33501
|
+
function existsAsNonDirectory(path) {
|
|
33502
|
+
try {
|
|
33503
|
+
return !statSync10(path).isDirectory();
|
|
33504
|
+
} catch {
|
|
33505
|
+
return false;
|
|
33506
|
+
}
|
|
33507
|
+
}
|
|
31635
33508
|
async function runInit(argv) {
|
|
31636
33509
|
const { values } = parseArgs6({
|
|
31637
33510
|
args: argv,
|
|
31638
33511
|
options: { ...HOME_OPTION, yes: { type: "boolean", short: "y" } }
|
|
31639
33512
|
});
|
|
31640
33513
|
const home = homeBase(values.home);
|
|
33514
|
+
assertStoreLinksResolve(home);
|
|
33515
|
+
assertStorePathsAreDirectories(home);
|
|
31641
33516
|
ensureLayoutDirSync(home);
|
|
31642
33517
|
const settings = settingsDir(home);
|
|
31643
33518
|
ensureLayoutDirSync(settings);
|
|
31644
|
-
const settingsFile =
|
|
31645
|
-
|
|
31646
|
-
if (
|
|
31647
|
-
|
|
31648
|
-
settingsFile,
|
|
31649
|
-
|
|
33519
|
+
const settingsFile = join19(settings, "settings.json");
|
|
33520
|
+
let settingsCreated = false;
|
|
33521
|
+
if (!existsSync15(settingsFile)) {
|
|
33522
|
+
try {
|
|
33523
|
+
settingsCreated = withFileLock(settingsFile, () => {
|
|
33524
|
+
if (existsSync15(settingsFile)) return false;
|
|
33525
|
+
writeOwnerOnlyFileSync(
|
|
33526
|
+
settingsFile,
|
|
33527
|
+
`${JSON.stringify(defaultWorkspaceSettings(), null, 2)}
|
|
31650
33528
|
`
|
|
31651
|
-
|
|
33529
|
+
);
|
|
33530
|
+
return true;
|
|
33531
|
+
});
|
|
33532
|
+
} catch (err) {
|
|
33533
|
+
if (!(err instanceof FileLockError) || err.reason !== "timeout") throw err;
|
|
33534
|
+
}
|
|
31652
33535
|
}
|
|
31653
33536
|
tightenFile(settingsFile);
|
|
31654
33537
|
const db = openLocalDatabase(dataDir(home));
|
|
@@ -31664,14 +33547,15 @@ async function runInit(argv) {
|
|
|
31664
33547
|
db.close();
|
|
31665
33548
|
}
|
|
31666
33549
|
const loose = looseStorePaths(home);
|
|
33550
|
+
const symlinked = symlinkedStorePaths(home);
|
|
31667
33551
|
process.stdout.write(
|
|
31668
33552
|
`\u2713 Initialized AKA at ${home}
|
|
31669
|
-
settings: ${settingsFile}${settingsCreated ? "" : " (kept existing)"}
|
|
33553
|
+
settings: ${settingsFile}${settingsCreated ? "" : existsSync15(settingsFile) ? " (kept existing)" : " (not written \u2014 another process was writing it)"}
|
|
31670
33554
|
database: ${dbPath(home)}
|
|
31671
33555
|
seeded ${String(policyCount)} default policies, ${String(packCount)} detection pack(s)
|
|
31672
33556
|
` + (updatesAvailable > 0 ? ` \u2B06 ${String(updatesAvailable)} detection pack update(s) available \u2014 review with \`aka detections\`, apply with \`aka detections update --all\`
|
|
31673
33557
|
` : "") + (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
|
-
` : "")
|
|
33558
|
+
` : "") + symlinkWarnings(symlinked)
|
|
31675
33559
|
);
|
|
31676
33560
|
await offerPluginInstall(values.yes === true);
|
|
31677
33561
|
}
|
|
@@ -31716,7 +33600,7 @@ async function confirm(question) {
|
|
|
31716
33600
|
}
|
|
31717
33601
|
|
|
31718
33602
|
// src/commands/scan.ts
|
|
31719
|
-
import { statSync as
|
|
33603
|
+
import { statSync as statSync11 } from "fs";
|
|
31720
33604
|
import { parseArgs as parseArgs7 } from "util";
|
|
31721
33605
|
function severityRank(s) {
|
|
31722
33606
|
return Severity.options.indexOf(s);
|
|
@@ -31730,7 +33614,7 @@ function renderEgressLine(egress) {
|
|
|
31730
33614
|
const cap = egress.truncated ? ` (capped at ${String(MAX_EGRESS_CALL_SITES_PER_PROJECT)} call sites)` : "";
|
|
31731
33615
|
return `Data shares: ${String(egress.destinations)} destination(s) \xB7 ${String(egress.endpoints)} endpoint(s) \xB7 ${String(egress.callSites)} call site(s)${cap}`;
|
|
31732
33616
|
}
|
|
31733
|
-
function runScan(argv) {
|
|
33617
|
+
async function runScan(argv) {
|
|
31734
33618
|
const { values, positionals } = parseArgs7({
|
|
31735
33619
|
args: argv,
|
|
31736
33620
|
options: {
|
|
@@ -31763,7 +33647,7 @@ function runScan(argv) {
|
|
|
31763
33647
|
failOn = parsed.data;
|
|
31764
33648
|
}
|
|
31765
33649
|
try {
|
|
31766
|
-
|
|
33650
|
+
statSync11(target);
|
|
31767
33651
|
} catch {
|
|
31768
33652
|
process.stderr.write(`aka scan: no such file or directory: ${target}
|
|
31769
33653
|
`);
|
|
@@ -31778,7 +33662,11 @@ function runScan(argv) {
|
|
|
31778
33662
|
let egress;
|
|
31779
33663
|
try {
|
|
31780
33664
|
const { ruleActions } = db.installedPacks.installedRuleset();
|
|
31781
|
-
result = scanPathIntoStore(db, target, {
|
|
33665
|
+
result = await scanPathIntoStore(db, target, {
|
|
33666
|
+
ruleActions,
|
|
33667
|
+
sourceTool: "cli",
|
|
33668
|
+
dataDir: storeDir
|
|
33669
|
+
});
|
|
31782
33670
|
inventory = recordProjectInventory(db, target);
|
|
31783
33671
|
egress = recordProjectEgress(db, target, result.egress, home);
|
|
31784
33672
|
} finally {
|
|
@@ -32649,19 +34537,20 @@ function applyCliUpdate2() {
|
|
|
32649
34537
|
function applyPluginUpdate2(status) {
|
|
32650
34538
|
const agent = findAgent(status.id);
|
|
32651
34539
|
const ref = agent ? pluginRef(agent) : void 0;
|
|
32652
|
-
|
|
34540
|
+
const cliBin = agent?.cliBin;
|
|
34541
|
+
if (!agent || !ref || !cliBin) {
|
|
32653
34542
|
process.stderr.write(`\u2717 ${status.name}: no update coordinates in the registry.
|
|
32654
34543
|
`);
|
|
32655
34544
|
return false;
|
|
32656
34545
|
}
|
|
32657
|
-
if (!
|
|
34546
|
+
if (!createCliPluginManager(cliBin).available()) {
|
|
32658
34547
|
process.stderr.write(
|
|
32659
|
-
`\u2717 ${status.name}: the \`
|
|
34548
|
+
`\u2717 ${status.name}: the \`${cliBin}\` CLI isn't on your PATH \u2014 install ${agent.name}, then run \`${cliBin} plugin update ${ref}\`.
|
|
32660
34549
|
`
|
|
32661
34550
|
);
|
|
32662
34551
|
return false;
|
|
32663
34552
|
}
|
|
32664
|
-
process.stdout.write(`Updating ${status.name} (
|
|
34553
|
+
process.stdout.write(`Updating ${status.name} (${cliBin} plugin update ${ref})\u2026
|
|
32665
34554
|
`);
|
|
32666
34555
|
const { ok } = applyPluginUpdate(status.id, "inherit");
|
|
32667
34556
|
process.stdout.write(ok ? `\u2713 ${status.name} updated.
|
|
@@ -32719,7 +34608,6 @@ async function runShow2(argv, io) {
|
|
|
32719
34608
|
const vault = new SecretVault({
|
|
32720
34609
|
repo: db.secretVault,
|
|
32721
34610
|
keys: createKeyProvider(readWorkspaceSettings(base).vaultKeyCustody, keysDir(base)),
|
|
32722
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
32723
34611
|
// Read live so a consent revocation applies to the very next call.
|
|
32724
34612
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent)
|
|
32725
34613
|
});
|
|
@@ -32807,6 +34695,7 @@ var COMMANDS = {
|
|
|
32807
34695
|
detections: runDetections,
|
|
32808
34696
|
plugins: runPlugins,
|
|
32809
34697
|
dashboard: runDashboard,
|
|
34698
|
+
extension: runExtension,
|
|
32810
34699
|
exception: (argv) => runException(argv),
|
|
32811
34700
|
vault: (argv) => runVault(argv),
|
|
32812
34701
|
tui: runTui,
|