@akasecurity/ai-tc-claude-code 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -0
- package/package.json +4 -3
- package/scripts/apply-suppressions.js +1607 -534
- package/scripts/backfill.js +1969 -390
- package/scripts/filescan.js +1863 -346
- package/scripts/firstrun.js +1307 -223
- package/scripts/intro.js +370 -54
- package/scripts/message-display.js +1406 -297
- package/scripts/onboard.js +1532 -245
- package/scripts/post-tool-use.js +1885 -356
- package/scripts/pre-tool-use.js +1900 -365
- package/scripts/query.js +1306 -222
- package/scripts/reconcile.js +1435 -326
- package/scripts/remediate.js +2121 -546
- package/scripts/scan-worker.js +18006 -0
- package/scripts/session-start.js +1350 -312
- package/scripts/start-light.js +390 -74
- package/scripts/statusline.js +1306 -222
- package/scripts/stop.js +352 -79
- package/scripts/user-prompt-submit.js +1897 -368
package/scripts/query.js
CHANGED
|
@@ -492,9 +492,8 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/persistence/src/database.ts
|
|
495
|
-
import { randomUUID as
|
|
496
|
-
import {
|
|
497
|
-
import { join, sep } from "path";
|
|
495
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
496
|
+
import { join as join2, sep } from "path";
|
|
498
497
|
import { DatabaseSync } from "node:sqlite";
|
|
499
498
|
|
|
500
499
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -574,6 +573,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
574
573
|
{
|
|
575
574
|
tag: "0018_serious_tana_nile",
|
|
576
575
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
tag: "0019_audit_started_at_index",
|
|
579
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
583
|
+
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');"
|
|
577
584
|
}
|
|
578
585
|
];
|
|
579
586
|
|
|
@@ -15371,7 +15378,17 @@ var Finding = external_exports.object({
|
|
|
15371
15378
|
}).meta({ id: "Finding" });
|
|
15372
15379
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15373
15380
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15374
|
-
var FindingProvider = external_exports.enum([
|
|
15381
|
+
var FindingProvider = external_exports.enum([
|
|
15382
|
+
"claudecode",
|
|
15383
|
+
"claudedesktop",
|
|
15384
|
+
"cursor",
|
|
15385
|
+
"copilot",
|
|
15386
|
+
"chatgpt",
|
|
15387
|
+
"claudeai",
|
|
15388
|
+
"codex",
|
|
15389
|
+
"antigravity",
|
|
15390
|
+
"api"
|
|
15391
|
+
]).meta({ id: "FindingProvider" });
|
|
15375
15392
|
var FindingCategory = external_exports.enum([
|
|
15376
15393
|
"secret",
|
|
15377
15394
|
"pii",
|
|
@@ -15425,7 +15442,16 @@ var FindingInstance = external_exports.object({
|
|
|
15425
15442
|
confidence: external_exports.number().min(0).max(1),
|
|
15426
15443
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15427
15444
|
// that predate the resolution feature stay valid.
|
|
15428
|
-
status: FindingStatus.optional()
|
|
15445
|
+
status: FindingStatus.optional(),
|
|
15446
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15447
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15448
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15449
|
+
// MOST RECENT detection event, not the first.
|
|
15450
|
+
eventId: external_exports.string().optional(),
|
|
15451
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15452
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15453
|
+
// outside a session.
|
|
15454
|
+
sessionId: external_exports.string().optional()
|
|
15429
15455
|
}).meta({ id: "FindingInstance" });
|
|
15430
15456
|
var FindingGroup = external_exports.object({
|
|
15431
15457
|
id: external_exports.string(),
|
|
@@ -15469,7 +15495,11 @@ var FindingFacets = external_exports.object({
|
|
|
15469
15495
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15470
15496
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15471
15497
|
// counted under no value.
|
|
15472
|
-
status: external_exports.array(FindingFacetItem)
|
|
15498
|
+
status: external_exports.array(FindingFacetItem),
|
|
15499
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15500
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15501
|
+
// because a group spans tools.
|
|
15502
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15473
15503
|
}).meta({ id: "FindingFacets" });
|
|
15474
15504
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15475
15505
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15487,6 +15517,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15487
15517
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15488
15518
|
// session → findings drilldown). Findings without a session never match.
|
|
15489
15519
|
sessionId: external_exports.string().optional(),
|
|
15520
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15521
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15522
|
+
// means all time — this list has no default window.
|
|
15523
|
+
from: external_exports.iso.datetime().optional(),
|
|
15524
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15525
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15526
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15527
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15528
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15529
|
+
includeId: external_exports.string().optional(),
|
|
15490
15530
|
groupBy: external_exports.literal("type").optional(),
|
|
15491
15531
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15492
15532
|
cursor: external_exports.string().optional()
|
|
@@ -15531,15 +15571,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15531
15571
|
detection: FindingDetectionRef,
|
|
15532
15572
|
policy: FindingPolicyRef
|
|
15533
15573
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15574
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15575
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15576
|
+
severity: external_exports.array(Severity).optional(),
|
|
15577
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15578
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15579
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15580
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15581
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15582
|
+
// the grouped query's group-level fold.
|
|
15583
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15584
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15585
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15586
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15587
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15588
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15589
|
+
repo: external_exports.string().optional(),
|
|
15590
|
+
file: external_exports.string().optional(),
|
|
15591
|
+
q: external_exports.string().optional(),
|
|
15592
|
+
sessionId: external_exports.string().optional(),
|
|
15593
|
+
from: external_exports.iso.datetime().optional(),
|
|
15594
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15595
|
+
cursor: external_exports.string().optional()
|
|
15596
|
+
});
|
|
15597
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15598
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15599
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15600
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15601
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15602
|
+
// dimension still excludes its own filter.
|
|
15603
|
+
facets: FindingFacets,
|
|
15604
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15605
|
+
nextCursor: external_exports.string().nullable()
|
|
15606
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15607
|
+
var FindingLocationFile = external_exports.object({
|
|
15608
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15609
|
+
// with no file attribution).
|
|
15610
|
+
file: external_exports.string(),
|
|
15611
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15612
|
+
maxSeverity: Severity,
|
|
15613
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15614
|
+
// Folded from the instances' derived statuses with the same
|
|
15615
|
+
// open-dominates precedence a group uses.
|
|
15616
|
+
status: FindingStatus.optional(),
|
|
15617
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15618
|
+
// chips, and the count is what conveys scale.
|
|
15619
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15620
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15621
|
+
var FindingLocationRepo = external_exports.object({
|
|
15622
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15623
|
+
repo: external_exports.string(),
|
|
15624
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15625
|
+
maxSeverity: Severity,
|
|
15626
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15627
|
+
status: FindingStatus.optional(),
|
|
15628
|
+
files: external_exports.array(FindingLocationFile)
|
|
15629
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15630
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15631
|
+
severity: external_exports.array(Severity).optional(),
|
|
15632
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15633
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15634
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15635
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15636
|
+
// instances that match, and folds its status from those.
|
|
15637
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15638
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15639
|
+
q: external_exports.string().optional(),
|
|
15640
|
+
sessionId: external_exports.string().optional(),
|
|
15641
|
+
from: external_exports.iso.datetime().optional(),
|
|
15642
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15643
|
+
});
|
|
15644
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15645
|
+
totals: external_exports.object({
|
|
15646
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15647
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15648
|
+
files: external_exports.number().int().nonnegative()
|
|
15649
|
+
}),
|
|
15650
|
+
/** Sorted by max severity, then most recent. */
|
|
15651
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15652
|
+
/** Whether `limit` truncated the repo list. */
|
|
15653
|
+
hasMore: external_exports.boolean()
|
|
15654
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15534
15655
|
|
|
15535
15656
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15536
|
-
var Harness = external_exports.enum([
|
|
15657
|
+
var Harness = external_exports.enum([
|
|
15658
|
+
"claudecode",
|
|
15659
|
+
"cursor",
|
|
15660
|
+
"copilot",
|
|
15661
|
+
"codex",
|
|
15662
|
+
"antigravity",
|
|
15663
|
+
"windsurf",
|
|
15664
|
+
"claudedesktop",
|
|
15665
|
+
"chatgpt",
|
|
15666
|
+
"claudeai",
|
|
15667
|
+
"api"
|
|
15668
|
+
]).meta({ id: "Harness" });
|
|
15537
15669
|
var TOOL_TO_HARNESS = {
|
|
15538
15670
|
"claude-code": "claudecode",
|
|
15539
15671
|
"claude-desktop": "claudedesktop",
|
|
15540
15672
|
"github-copilot": "copilot",
|
|
15541
15673
|
cursor: "cursor",
|
|
15542
|
-
chatgpt: "chatgpt"
|
|
15674
|
+
chatgpt: "chatgpt",
|
|
15675
|
+
codex: "codex",
|
|
15676
|
+
antigravity: "antigravity",
|
|
15677
|
+
"claude-ai": "claudeai"
|
|
15543
15678
|
};
|
|
15544
15679
|
|
|
15545
15680
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15997,7 +16132,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15997
16132
|
// ../../packages/schema/src/zod/event.ts
|
|
15998
16133
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15999
16134
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
16000
|
-
var SourceTool = external_exports.enum([
|
|
16135
|
+
var SourceTool = external_exports.enum([
|
|
16136
|
+
"claude-code",
|
|
16137
|
+
"claude-desktop",
|
|
16138
|
+
"cursor",
|
|
16139
|
+
"chatgpt",
|
|
16140
|
+
"claude-ai",
|
|
16141
|
+
"github-copilot",
|
|
16142
|
+
"codex",
|
|
16143
|
+
"antigravity",
|
|
16144
|
+
"cli",
|
|
16145
|
+
"unknown"
|
|
16146
|
+
]).meta({ id: "SourceTool" });
|
|
16001
16147
|
var EventMetadata = external_exports.object({
|
|
16002
16148
|
sessionId: external_exports.string().optional(),
|
|
16003
16149
|
repo: external_exports.string().optional(),
|
|
@@ -16068,7 +16214,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16068
16214
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16069
16215
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16070
16216
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16071
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16217
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16072
16218
|
var AccessCounts = external_exports.object({
|
|
16073
16219
|
open: external_exports.number().int().nonnegative(),
|
|
16074
16220
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16337,6 +16483,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16337
16483
|
useCount: true,
|
|
16338
16484
|
conditions: true
|
|
16339
16485
|
});
|
|
16486
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16340
16487
|
|
|
16341
16488
|
// ../../packages/schema/src/zod/rule.ts
|
|
16342
16489
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17147,6 +17294,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17147
17294
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17148
17295
|
}).meta({ id: "EgressWriteSummary" });
|
|
17149
17296
|
|
|
17297
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17298
|
+
var confirmation = external_exports.string().optional();
|
|
17299
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17300
|
+
reference: external_exports.string(),
|
|
17301
|
+
scope: external_exports.string(),
|
|
17302
|
+
reason: external_exports.string(),
|
|
17303
|
+
confirmation
|
|
17304
|
+
});
|
|
17305
|
+
var AddExceptionInput = external_exports.object({
|
|
17306
|
+
ruleId: external_exports.string(),
|
|
17307
|
+
value: external_exports.string(),
|
|
17308
|
+
scope: external_exports.string(),
|
|
17309
|
+
reason: external_exports.string(),
|
|
17310
|
+
confirmation
|
|
17311
|
+
});
|
|
17312
|
+
var GrantRevealInput = external_exports.object({
|
|
17313
|
+
pointer: external_exports.string(),
|
|
17314
|
+
scope: external_exports.string(),
|
|
17315
|
+
justification: external_exports.string(),
|
|
17316
|
+
confirmation
|
|
17317
|
+
});
|
|
17318
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17319
|
+
id: external_exports.string(),
|
|
17320
|
+
reason: external_exports.string()
|
|
17321
|
+
});
|
|
17322
|
+
var RotateKeyInput = external_exports.object({
|
|
17323
|
+
confirmation: external_exports.string()
|
|
17324
|
+
});
|
|
17325
|
+
|
|
17150
17326
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17151
17327
|
function toApiAction(dbVal) {
|
|
17152
17328
|
const map2 = {
|
|
@@ -17202,6 +17378,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17202
17378
|
repo: r.repo,
|
|
17203
17379
|
file: r.file,
|
|
17204
17380
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17381
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17382
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17205
17383
|
action: toApiAction(effectiveDbAction),
|
|
17206
17384
|
detectedAt: r.occurredAt,
|
|
17207
17385
|
confidence: r.confidence,
|
|
@@ -17333,14 +17511,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17333
17511
|
}
|
|
17334
17512
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17335
17513
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17514
|
+
function compareFindingGroupOrder(a, b) {
|
|
17515
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17516
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17517
|
+
const severityDiff = rankA - rankB;
|
|
17518
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17519
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17520
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17521
|
+
return a.id.localeCompare(b.id);
|
|
17522
|
+
}
|
|
17336
17523
|
function sortFindingGroups(groups) {
|
|
17337
|
-
return [...groups].sort(
|
|
17338
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17339
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17340
|
-
const severityDiff = rankA - rankB;
|
|
17341
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17342
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17343
|
-
});
|
|
17524
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17344
17525
|
}
|
|
17345
17526
|
function computeFindingFacets(allGroups, opts) {
|
|
17346
17527
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17396,15 +17577,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17396
17577
|
for (const g of forStatus) {
|
|
17397
17578
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17398
17579
|
}
|
|
17399
|
-
const
|
|
17580
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17581
|
+
return {
|
|
17582
|
+
severity: toItems2(severityMap),
|
|
17583
|
+
provider: toItems2(providerMap),
|
|
17584
|
+
action: toItems2(actionMap),
|
|
17585
|
+
subtype: toItems2(subtypeMap),
|
|
17586
|
+
status: toItems2(statusMap)
|
|
17587
|
+
};
|
|
17588
|
+
}
|
|
17589
|
+
|
|
17590
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17591
|
+
function rowHaystack(row) {
|
|
17592
|
+
return [
|
|
17593
|
+
row.ruleId,
|
|
17594
|
+
row.category,
|
|
17595
|
+
row.maskedMatch,
|
|
17596
|
+
row.repo,
|
|
17597
|
+
row.file,
|
|
17598
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17599
|
+
row.id
|
|
17600
|
+
].join(" ").toLowerCase();
|
|
17601
|
+
}
|
|
17602
|
+
function matchesDimension(row, opts, dimension) {
|
|
17603
|
+
switch (dimension) {
|
|
17604
|
+
case "severity":
|
|
17605
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17606
|
+
case "subtype":
|
|
17607
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17608
|
+
case "providers":
|
|
17609
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17610
|
+
case "actions":
|
|
17611
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17612
|
+
case "statuses":
|
|
17613
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17614
|
+
case "tools":
|
|
17615
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17616
|
+
case "repo":
|
|
17617
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17618
|
+
case "file":
|
|
17619
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17620
|
+
case "q":
|
|
17621
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17622
|
+
}
|
|
17623
|
+
}
|
|
17624
|
+
var DIMENSIONS = [
|
|
17625
|
+
"severity",
|
|
17626
|
+
"subtype",
|
|
17627
|
+
"providers",
|
|
17628
|
+
"actions",
|
|
17629
|
+
"statuses",
|
|
17630
|
+
"tools",
|
|
17631
|
+
"repo",
|
|
17632
|
+
"file",
|
|
17633
|
+
"q"
|
|
17634
|
+
];
|
|
17635
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17636
|
+
for (const dimension of DIMENSIONS) {
|
|
17637
|
+
if (dimension === except) continue;
|
|
17638
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17639
|
+
}
|
|
17640
|
+
return true;
|
|
17641
|
+
}
|
|
17642
|
+
function toItems(counts) {
|
|
17643
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17644
|
+
}
|
|
17645
|
+
function bump(counts, value) {
|
|
17646
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17647
|
+
}
|
|
17648
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17649
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17650
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17651
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17652
|
+
const action = /* @__PURE__ */ new Map();
|
|
17653
|
+
const status = /* @__PURE__ */ new Map();
|
|
17654
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17655
|
+
return {
|
|
17656
|
+
add(row) {
|
|
17657
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17658
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17659
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17660
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17661
|
+
}
|
|
17662
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17663
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17664
|
+
bump(status, row.status);
|
|
17665
|
+
}
|
|
17666
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17667
|
+
bump(tool, row.toolName);
|
|
17668
|
+
}
|
|
17669
|
+
},
|
|
17670
|
+
facets: () => ({
|
|
17671
|
+
severity: toItems(severity),
|
|
17672
|
+
subtype: toItems(subtype),
|
|
17673
|
+
provider: toItems(provider),
|
|
17674
|
+
action: toItems(action),
|
|
17675
|
+
status: toItems(status),
|
|
17676
|
+
tool: toItems(tool)
|
|
17677
|
+
})
|
|
17678
|
+
};
|
|
17679
|
+
}
|
|
17680
|
+
function toInstanceDetail(row) {
|
|
17681
|
+
const category = toApiCategory(row.category);
|
|
17682
|
+
return {
|
|
17683
|
+
id: row.id,
|
|
17684
|
+
provider: toApiProvider(row.sourceTool),
|
|
17685
|
+
repo: row.repo,
|
|
17686
|
+
file: row.file,
|
|
17687
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17688
|
+
eventId: row.eventId,
|
|
17689
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17690
|
+
action: toApiAction(row.actionTaken),
|
|
17691
|
+
detectedAt: row.occurredAt,
|
|
17692
|
+
confidence: row.confidence,
|
|
17693
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17694
|
+
groupId: row.ruleId,
|
|
17695
|
+
category,
|
|
17696
|
+
subtype: row.ruleId,
|
|
17697
|
+
severity: row.severity,
|
|
17698
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17699
|
+
detection: { id: row.ruleId, name: null },
|
|
17700
|
+
policy: { id: `category:${category}`, name: category }
|
|
17701
|
+
};
|
|
17702
|
+
}
|
|
17703
|
+
var SEVERITY_ORDER2 = {
|
|
17704
|
+
critical: 0,
|
|
17705
|
+
high: 1,
|
|
17706
|
+
medium: 2,
|
|
17707
|
+
low: 3
|
|
17708
|
+
};
|
|
17709
|
+
function newLocationAccumulator() {
|
|
17400
17710
|
return {
|
|
17401
|
-
|
|
17402
|
-
|
|
17403
|
-
|
|
17404
|
-
|
|
17405
|
-
|
|
17711
|
+
instanceCount: 0,
|
|
17712
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17713
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17714
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17715
|
+
maxSeverity: "low",
|
|
17716
|
+
latestDetectedAt: "",
|
|
17717
|
+
statuses: [],
|
|
17718
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17406
17719
|
};
|
|
17407
17720
|
}
|
|
17721
|
+
function addToLocation(acc, row) {
|
|
17722
|
+
acc.instanceCount += 1;
|
|
17723
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17724
|
+
if (rank < acc.maxSeverityRank) {
|
|
17725
|
+
acc.maxSeverityRank = rank;
|
|
17726
|
+
acc.maxSeverity = row.severity;
|
|
17727
|
+
}
|
|
17728
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17729
|
+
acc.statuses.push(row.status);
|
|
17730
|
+
acc.ruleIds.add(row.ruleId);
|
|
17731
|
+
}
|
|
17408
17732
|
|
|
17409
17733
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17410
17734
|
var InstalledPack = external_exports.object({
|
|
@@ -17538,6 +17862,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17538
17862
|
revealGrantId: external_exports.string().nullable(),
|
|
17539
17863
|
sightings: external_exports.array(VaultSighting)
|
|
17540
17864
|
});
|
|
17865
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17866
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17867
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17868
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17869
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17870
|
+
// Opaque; names the last row of the page just served.
|
|
17871
|
+
cursor: external_exports.string().optional()
|
|
17872
|
+
});
|
|
17873
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17874
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17875
|
+
// independent, so paging never changes what the count claims.
|
|
17876
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17877
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17878
|
+
// `null` once the last page is reached.
|
|
17879
|
+
nextCursor: external_exports.string().nullable()
|
|
17880
|
+
});
|
|
17881
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17882
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17883
|
+
cursor: external_exports.string().optional()
|
|
17884
|
+
});
|
|
17885
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17886
|
+
// Reused values across the whole store — the number the section's claim
|
|
17887
|
+
// ("values detected in more than one place") is about.
|
|
17888
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17889
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17890
|
+
nextCursor: external_exports.string().nullable()
|
|
17891
|
+
});
|
|
17892
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17893
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17894
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17895
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17896
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17897
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17898
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17899
|
+
cursor: external_exports.string().optional()
|
|
17900
|
+
});
|
|
17901
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17902
|
+
items: external_exports.array(VaultDeref),
|
|
17903
|
+
nextCursor: external_exports.string().nullable(),
|
|
17904
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17905
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17906
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17907
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17908
|
+
});
|
|
17541
17909
|
var VaultKeyCustody = external_exports.string();
|
|
17542
17910
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17543
17911
|
var VaultConsent = external_exports.object({
|
|
@@ -17910,7 +18278,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17910
18278
|
// Omit for both kinds.
|
|
17911
18279
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17912
18280
|
});
|
|
17913
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18281
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17914
18282
|
var ScanCoverageProvider = external_exports.object({
|
|
17915
18283
|
provider: Provider,
|
|
17916
18284
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18163,6 +18531,138 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18163
18531
|
);
|
|
18164
18532
|
}
|
|
18165
18533
|
|
|
18534
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18535
|
+
import { randomUUID } from "crypto";
|
|
18536
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18537
|
+
import { basename, dirname, join } from "path";
|
|
18538
|
+
|
|
18539
|
+
// ../../packages/persistence/src/paths.ts
|
|
18540
|
+
import {
|
|
18541
|
+
chmodSync,
|
|
18542
|
+
linkSync,
|
|
18543
|
+
lstatSync,
|
|
18544
|
+
mkdirSync,
|
|
18545
|
+
renameSync,
|
|
18546
|
+
rmSync,
|
|
18547
|
+
writeFileSync
|
|
18548
|
+
} from "fs";
|
|
18549
|
+
import { threadId } from "worker_threads";
|
|
18550
|
+
var DATA_DIR_MODE = 448;
|
|
18551
|
+
var DATA_FILE_MODE = 384;
|
|
18552
|
+
var DB_FILENAME = "aka.db";
|
|
18553
|
+
function isSymlink(path) {
|
|
18554
|
+
try {
|
|
18555
|
+
return lstatSync(path).isSymbolicLink();
|
|
18556
|
+
} catch {
|
|
18557
|
+
return false;
|
|
18558
|
+
}
|
|
18559
|
+
}
|
|
18560
|
+
function chmodBestEffort(path, mode) {
|
|
18561
|
+
if (isSymlink(path)) return;
|
|
18562
|
+
try {
|
|
18563
|
+
chmodSync(path, mode);
|
|
18564
|
+
} catch {
|
|
18565
|
+
}
|
|
18566
|
+
}
|
|
18567
|
+
function tightenDir(dir) {
|
|
18568
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18569
|
+
}
|
|
18570
|
+
function ensureDataDirSync(dir) {
|
|
18571
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18572
|
+
tightenDir(dir);
|
|
18573
|
+
}
|
|
18574
|
+
function dbSidecars(file2) {
|
|
18575
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18576
|
+
}
|
|
18577
|
+
function tightenFile(file2) {
|
|
18578
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18579
|
+
}
|
|
18580
|
+
function tightenPerms(file2) {
|
|
18581
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18582
|
+
}
|
|
18583
|
+
|
|
18584
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18585
|
+
function backupPath(file2, tag) {
|
|
18586
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18587
|
+
}
|
|
18588
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18589
|
+
function reapStalePartials(file2) {
|
|
18590
|
+
const dir = dirname(file2);
|
|
18591
|
+
const prefix = `${basename(file2)}.`;
|
|
18592
|
+
let entries;
|
|
18593
|
+
try {
|
|
18594
|
+
entries = readdirSync(dir);
|
|
18595
|
+
} catch {
|
|
18596
|
+
return;
|
|
18597
|
+
}
|
|
18598
|
+
for (const name of entries) {
|
|
18599
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18600
|
+
const partial2 = join(dir, name);
|
|
18601
|
+
try {
|
|
18602
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18603
|
+
rmSync2(partial2, { force: true });
|
|
18604
|
+
}
|
|
18605
|
+
} catch {
|
|
18606
|
+
}
|
|
18607
|
+
}
|
|
18608
|
+
}
|
|
18609
|
+
function snapshotStore(db, backup) {
|
|
18610
|
+
const partial2 = `${backup}.partial`;
|
|
18611
|
+
try {
|
|
18612
|
+
rmSync2(partial2, { force: true });
|
|
18613
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18614
|
+
tightenFile(partial2);
|
|
18615
|
+
renameSync2(partial2, backup);
|
|
18616
|
+
} catch (error51) {
|
|
18617
|
+
try {
|
|
18618
|
+
rmSync2(partial2, { force: true });
|
|
18619
|
+
} catch {
|
|
18620
|
+
}
|
|
18621
|
+
throw error51;
|
|
18622
|
+
}
|
|
18623
|
+
}
|
|
18624
|
+
function moveStoreAside(file2, backup) {
|
|
18625
|
+
const undo = [];
|
|
18626
|
+
renameSync2(file2, backup);
|
|
18627
|
+
undo.push([backup, file2]);
|
|
18628
|
+
try {
|
|
18629
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18630
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18631
|
+
try {
|
|
18632
|
+
renameSync2(sidecar, moved);
|
|
18633
|
+
undo.push([moved, sidecar]);
|
|
18634
|
+
} catch {
|
|
18635
|
+
rmSync2(sidecar, { force: true });
|
|
18636
|
+
}
|
|
18637
|
+
}
|
|
18638
|
+
} catch (error51) {
|
|
18639
|
+
for (const [from, to] of undo.reverse()) {
|
|
18640
|
+
try {
|
|
18641
|
+
renameSync2(from, to);
|
|
18642
|
+
} catch {
|
|
18643
|
+
}
|
|
18644
|
+
}
|
|
18645
|
+
throw error51;
|
|
18646
|
+
}
|
|
18647
|
+
tightenPerms(backup);
|
|
18648
|
+
}
|
|
18649
|
+
function discardStore(file2, backup) {
|
|
18650
|
+
try {
|
|
18651
|
+
rmSync2(file2, { force: true });
|
|
18652
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18653
|
+
rmSync2(sidecar, { force: true });
|
|
18654
|
+
}
|
|
18655
|
+
} catch (error51) {
|
|
18656
|
+
if (existsSync(file2)) {
|
|
18657
|
+
try {
|
|
18658
|
+
rmSync2(backup, { force: true });
|
|
18659
|
+
} catch {
|
|
18660
|
+
}
|
|
18661
|
+
}
|
|
18662
|
+
throw error51;
|
|
18663
|
+
}
|
|
18664
|
+
}
|
|
18665
|
+
|
|
18166
18666
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18167
18667
|
function escapeLikePattern(s) {
|
|
18168
18668
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18304,38 +18804,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18304
18804
|
return out;
|
|
18305
18805
|
}
|
|
18306
18806
|
|
|
18307
|
-
// ../../packages/persistence/src/paths.ts
|
|
18308
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18309
|
-
var DATA_DIR_MODE = 448;
|
|
18310
|
-
var DATA_FILE_MODE = 384;
|
|
18311
|
-
var DB_FILENAME = "aka.db";
|
|
18312
|
-
function chmodBestEffort(path, mode) {
|
|
18313
|
-
try {
|
|
18314
|
-
chmodSync(path, mode);
|
|
18315
|
-
} catch {
|
|
18316
|
-
}
|
|
18317
|
-
}
|
|
18318
|
-
function tightenDir(dir) {
|
|
18319
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18320
|
-
}
|
|
18321
|
-
function ensureDataDirSync(dir) {
|
|
18322
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18323
|
-
tightenDir(dir);
|
|
18324
|
-
}
|
|
18325
|
-
function dbSidecars(file2) {
|
|
18326
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18327
|
-
}
|
|
18328
|
-
function tightenFile(file2) {
|
|
18329
|
-
try {
|
|
18330
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18331
|
-
} catch {
|
|
18332
|
-
}
|
|
18333
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18334
|
-
}
|
|
18335
|
-
function tightenPerms(file2) {
|
|
18336
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18337
|
-
}
|
|
18338
|
-
|
|
18339
18807
|
// ../../packages/persistence/src/migrations.ts
|
|
18340
18808
|
function describeObject(object2) {
|
|
18341
18809
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18451,9 +18919,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18451
18919
|
}
|
|
18452
18920
|
}
|
|
18453
18921
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
|
|
18922
|
+
reapStalePartials(file2);
|
|
18923
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18924
|
+
snapshotStore(db, backup);
|
|
18457
18925
|
return backup;
|
|
18458
18926
|
}
|
|
18459
18927
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18797,6 +19265,25 @@ function parseJsonObject(s) {
|
|
|
18797
19265
|
return void 0;
|
|
18798
19266
|
}
|
|
18799
19267
|
|
|
19268
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19269
|
+
function encodeKeysetCursor(payload) {
|
|
19270
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19271
|
+
}
|
|
19272
|
+
function decodeKeysetCursor(cursor) {
|
|
19273
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19274
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19275
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19276
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19277
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19278
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19279
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19280
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19281
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19282
|
+
return parsed;
|
|
19283
|
+
}
|
|
19284
|
+
return null;
|
|
19285
|
+
}
|
|
19286
|
+
|
|
18800
19287
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18801
19288
|
var DAY_MS = 864e5;
|
|
18802
19289
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18842,16 +19329,6 @@ function utcWindow(nowMs) {
|
|
|
18842
19329
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18843
19330
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18844
19331
|
}
|
|
18845
|
-
function encodeCursor(payload) {
|
|
18846
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18847
|
-
}
|
|
18848
|
-
function decodeCursor(cursor) {
|
|
18849
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18850
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18851
|
-
return parsed;
|
|
18852
|
-
}
|
|
18853
|
-
return null;
|
|
18854
|
-
}
|
|
18855
19332
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18856
19333
|
session: "session",
|
|
18857
19334
|
prompt: "prompt",
|
|
@@ -18996,7 +19473,7 @@ var SqliteActivityRepository = class {
|
|
|
18996
19473
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18997
19474
|
}
|
|
18998
19475
|
listSessions(query) {
|
|
18999
|
-
const cursor = query.cursor ?
|
|
19476
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
19000
19477
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
19001
19478
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
19002
19479
|
const conditions = [SESSION_ROOT];
|
|
@@ -19070,7 +19547,7 @@ var SqliteActivityRepository = class {
|
|
|
19070
19547
|
)
|
|
19071
19548
|
);
|
|
19072
19549
|
const last = page[page.length - 1];
|
|
19073
|
-
const nextCursor = hasMore && last ?
|
|
19550
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19074
19551
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19075
19552
|
}
|
|
19076
19553
|
getSession(sessionId) {
|
|
@@ -19943,7 +20420,7 @@ var SqliteEventsRepository = class {
|
|
|
19943
20420
|
};
|
|
19944
20421
|
|
|
19945
20422
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19946
|
-
import { randomUUID } from "crypto";
|
|
20423
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19947
20424
|
|
|
19948
20425
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19949
20426
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19979,8 +20456,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19979
20456
|
AND conditions IS NULL
|
|
19980
20457
|
AND ${ACTIVE_PREDICATE}`;
|
|
19981
20458
|
var SqliteExceptionsRepository = class {
|
|
19982
|
-
constructor(db) {
|
|
20459
|
+
constructor(db, now = () => Date.now()) {
|
|
19983
20460
|
this.db = db;
|
|
20461
|
+
this.now = now;
|
|
19984
20462
|
this.consumeStmt = db.prepare(
|
|
19985
20463
|
`UPDATE exceptions
|
|
19986
20464
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19998,6 +20476,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19998
20476
|
);
|
|
19999
20477
|
}
|
|
20000
20478
|
db;
|
|
20479
|
+
now;
|
|
20001
20480
|
consumeStmt;
|
|
20002
20481
|
insertBlockedStmt;
|
|
20003
20482
|
sweepBlockedStmt;
|
|
@@ -20024,8 +20503,8 @@ var SqliteExceptionsRepository = class {
|
|
|
20024
20503
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
20025
20504
|
);
|
|
20026
20505
|
}
|
|
20027
|
-
const id =
|
|
20028
|
-
const now =
|
|
20506
|
+
const id = randomUUID2();
|
|
20507
|
+
const now = this.now();
|
|
20029
20508
|
try {
|
|
20030
20509
|
this.insertExceptionRow(id, input, now);
|
|
20031
20510
|
} catch (err) {
|
|
@@ -20103,7 +20582,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20103
20582
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20104
20583
|
const rows = allRows(
|
|
20105
20584
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20106
|
-
opts?.includeTerminal ? {} : { now:
|
|
20585
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20107
20586
|
);
|
|
20108
20587
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20109
20588
|
return Promise.resolve(exceptions);
|
|
@@ -20138,7 +20617,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20138
20617
|
* already revoked.
|
|
20139
20618
|
*/
|
|
20140
20619
|
revoke(id, revokedBy, reason) {
|
|
20141
|
-
const now =
|
|
20620
|
+
const now = this.now();
|
|
20142
20621
|
const result = this.db.prepare(
|
|
20143
20622
|
`UPDATE exceptions
|
|
20144
20623
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20152,7 +20631,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20152
20631
|
* callers must treat identically — means it does not and the detection is
|
|
20153
20632
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20154
20633
|
*/
|
|
20155
|
-
consume(id, now =
|
|
20634
|
+
consume(id, now = this.now()) {
|
|
20156
20635
|
const result = this.consumeStmt.run({ id, now });
|
|
20157
20636
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20158
20637
|
}
|
|
@@ -20161,7 +20640,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20161
20640
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20162
20641
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20163
20642
|
*/
|
|
20164
|
-
activeBundleEntries(keyVersion, now =
|
|
20643
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20165
20644
|
const rows = allRows(
|
|
20166
20645
|
this.db.prepare(
|
|
20167
20646
|
`SELECT * FROM exceptions
|
|
@@ -20193,7 +20672,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20193
20672
|
* than the retention window on every write, so the ledger self-limits.
|
|
20194
20673
|
*/
|
|
20195
20674
|
recordBlocked(entry) {
|
|
20196
|
-
const now =
|
|
20675
|
+
const now = this.now();
|
|
20197
20676
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20198
20677
|
this.insertBlockedStmt.run({
|
|
20199
20678
|
reference: entry.reference,
|
|
@@ -20216,7 +20695,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20216
20695
|
WHERE blocked_at > :cutoff
|
|
20217
20696
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20218
20697
|
),
|
|
20219
|
-
{ cutoff:
|
|
20698
|
+
{ cutoff: this.now() - windowMs }
|
|
20220
20699
|
);
|
|
20221
20700
|
return Promise.resolve(
|
|
20222
20701
|
rows.map((row) => ({
|
|
@@ -20244,8 +20723,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20244
20723
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20245
20724
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20246
20725
|
*/
|
|
20247
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20726
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20248
20727
|
try {
|
|
20728
|
+
const at = now ?? this.now();
|
|
20249
20729
|
const row = getRow(
|
|
20250
20730
|
this.db.prepare(
|
|
20251
20731
|
`SELECT id FROM exceptions
|
|
@@ -20254,7 +20734,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20254
20734
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20255
20735
|
LIMIT 1`
|
|
20256
20736
|
),
|
|
20257
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20737
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20258
20738
|
);
|
|
20259
20739
|
return Promise.resolve(row ?? null);
|
|
20260
20740
|
} catch (err) {
|
|
@@ -20268,7 +20748,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20268
20748
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20269
20749
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20270
20750
|
*/
|
|
20271
|
-
sweepTerminal(retentionMs, now =
|
|
20751
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20272
20752
|
const result = this.db.prepare(
|
|
20273
20753
|
`DELETE FROM exceptions
|
|
20274
20754
|
WHERE updated_at < :cutoff
|
|
@@ -20331,6 +20811,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20331
20811
|
|
|
20332
20812
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20333
20813
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20814
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20815
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20816
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20817
|
+
function compareLocationOrder(a, b) {
|
|
20818
|
+
return compareFindingGroupOrder(
|
|
20819
|
+
{
|
|
20820
|
+
severity: a.maxSeverity,
|
|
20821
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20822
|
+
id: ""
|
|
20823
|
+
},
|
|
20824
|
+
{
|
|
20825
|
+
severity: b.maxSeverity,
|
|
20826
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20827
|
+
id: ""
|
|
20828
|
+
}
|
|
20829
|
+
);
|
|
20830
|
+
}
|
|
20334
20831
|
var CONCAT_SEP = ",";
|
|
20335
20832
|
var TUPLE_SEP = "|";
|
|
20336
20833
|
function splitConcat(value) {
|
|
@@ -20343,6 +20840,33 @@ function deriveInstanceStatus(row) {
|
|
|
20343
20840
|
latestResolutionStatus: row.latest_status
|
|
20344
20841
|
});
|
|
20345
20842
|
}
|
|
20843
|
+
function encodeGroupCursor(group) {
|
|
20844
|
+
const payload = {
|
|
20845
|
+
sev: group.severity,
|
|
20846
|
+
t: group.latestDetectedAt,
|
|
20847
|
+
id: group.id
|
|
20848
|
+
};
|
|
20849
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20850
|
+
}
|
|
20851
|
+
function decodeGroupCursor(cursor) {
|
|
20852
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20853
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20854
|
+
return {
|
|
20855
|
+
severity: parsed.sev,
|
|
20856
|
+
latestDetectedAt: parsed.t,
|
|
20857
|
+
id: parsed.id
|
|
20858
|
+
};
|
|
20859
|
+
}
|
|
20860
|
+
return null;
|
|
20861
|
+
}
|
|
20862
|
+
function firstAfter(sorted, cursor) {
|
|
20863
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20864
|
+
return index === -1 ? sorted.length : index;
|
|
20865
|
+
}
|
|
20866
|
+
function findDeepLinked(sorted, page, id) {
|
|
20867
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20868
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20869
|
+
}
|
|
20346
20870
|
var DAY_MS3 = 864e5;
|
|
20347
20871
|
var SqliteFindingsRepository = class {
|
|
20348
20872
|
constructor(db) {
|
|
@@ -20452,8 +20976,13 @@ var SqliteFindingsRepository = class {
|
|
|
20452
20976
|
*/
|
|
20453
20977
|
listGroupedFindings(query) {
|
|
20454
20978
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20455
|
-
const
|
|
20456
|
-
const
|
|
20979
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20980
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20981
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20982
|
+
const sessionParams = {
|
|
20983
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20984
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20985
|
+
};
|
|
20457
20986
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20458
20987
|
predicate,
|
|
20459
20988
|
params: sessionParams
|
|
@@ -20461,7 +20990,8 @@ var SqliteFindingsRepository = class {
|
|
|
20461
20990
|
const rows = allRows(
|
|
20462
20991
|
this.db.prepare(
|
|
20463
20992
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20464
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20993
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20994
|
+
kind, finding_key, latest_status
|
|
20465
20995
|
FROM (
|
|
20466
20996
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20467
20997
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20471,6 +21001,7 @@ var SqliteFindingsRepository = class {
|
|
|
20471
21001
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20472
21002
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20473
21003
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21004
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20474
21005
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20475
21006
|
latest.status AS latest_status,
|
|
20476
21007
|
ROW_NUMBER() OVER (
|
|
@@ -20502,6 +21033,8 @@ var SqliteFindingsRepository = class {
|
|
|
20502
21033
|
repo: r.repo ?? "",
|
|
20503
21034
|
file: r.file ?? "",
|
|
20504
21035
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21036
|
+
eventId: r.event_id,
|
|
21037
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20505
21038
|
status: deriveInstanceStatus(r)
|
|
20506
21039
|
}));
|
|
20507
21040
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20525,18 +21058,23 @@ var SqliteFindingsRepository = class {
|
|
|
20525
21058
|
groups: sorted.length
|
|
20526
21059
|
};
|
|
20527
21060
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21061
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21062
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21063
|
+
const page = sorted.slice(start, start + limit);
|
|
21064
|
+
const lastOnPage = page.at(-1);
|
|
21065
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21066
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20528
21067
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20529
|
-
const
|
|
20530
|
-
|
|
20531
|
-
|
|
20532
|
-
|
|
20533
|
-
|
|
20534
|
-
);
|
|
21068
|
+
const narrow = (g) => statusSet ? {
|
|
21069
|
+
...g,
|
|
21070
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21071
|
+
} : g;
|
|
21072
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20535
21073
|
return Promise.resolve({
|
|
20536
21074
|
totals,
|
|
20537
21075
|
facets,
|
|
20538
21076
|
items,
|
|
20539
|
-
nextCursor
|
|
21077
|
+
nextCursor,
|
|
20540
21078
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20541
21079
|
});
|
|
20542
21080
|
}
|
|
@@ -20568,6 +21106,266 @@ var SqliteFindingsRepository = class {
|
|
|
20568
21106
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20569
21107
|
* path repeating across tuples.)
|
|
20570
21108
|
*/
|
|
21109
|
+
/**
|
|
21110
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21111
|
+
* paged by keyset.
|
|
21112
|
+
*
|
|
21113
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21114
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21115
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21116
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21117
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21118
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21119
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21120
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21121
|
+
* none of the mappers names", which no IN-list can say.
|
|
21122
|
+
*
|
|
21123
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21124
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21125
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21126
|
+
* while the counting runs, and only the page itself is retained.
|
|
21127
|
+
*/
|
|
21128
|
+
listFindingInstances(query) {
|
|
21129
|
+
const opts = {
|
|
21130
|
+
severity: query.severity,
|
|
21131
|
+
subtype: query.subtype,
|
|
21132
|
+
providers: query.provider,
|
|
21133
|
+
actions: query.action,
|
|
21134
|
+
statuses: query.status,
|
|
21135
|
+
tools: query.tool,
|
|
21136
|
+
repo: query.repo,
|
|
21137
|
+
file: query.file,
|
|
21138
|
+
q: query.q
|
|
21139
|
+
};
|
|
21140
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21141
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21142
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21143
|
+
const items = [];
|
|
21144
|
+
let total = 0;
|
|
21145
|
+
let last;
|
|
21146
|
+
let hasMore = false;
|
|
21147
|
+
for (const row of this.scanFindingRows({
|
|
21148
|
+
sessionId: query.sessionId,
|
|
21149
|
+
from: query.from
|
|
21150
|
+
})) {
|
|
21151
|
+
accumulator.add(row);
|
|
21152
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21153
|
+
total += 1;
|
|
21154
|
+
if (items.length < limit) {
|
|
21155
|
+
items.push(toInstanceDetail(row));
|
|
21156
|
+
last = row;
|
|
21157
|
+
} else {
|
|
21158
|
+
hasMore = true;
|
|
21159
|
+
}
|
|
21160
|
+
}
|
|
21161
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21162
|
+
if (cursor !== null) {
|
|
21163
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21164
|
+
return Promise.resolve({
|
|
21165
|
+
totals: { findings: total },
|
|
21166
|
+
facets: accumulator.facets(),
|
|
21167
|
+
items: resumed.items,
|
|
21168
|
+
nextCursor: resumed.nextCursor
|
|
21169
|
+
});
|
|
21170
|
+
}
|
|
21171
|
+
return Promise.resolve({
|
|
21172
|
+
totals: { findings: total },
|
|
21173
|
+
facets: accumulator.facets(),
|
|
21174
|
+
items,
|
|
21175
|
+
nextCursor
|
|
21176
|
+
});
|
|
21177
|
+
}
|
|
21178
|
+
/**
|
|
21179
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21180
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21181
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21182
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21183
|
+
*/
|
|
21184
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21185
|
+
const items = [];
|
|
21186
|
+
let last;
|
|
21187
|
+
let hasMore = false;
|
|
21188
|
+
for (const row of this.scanFindingRows({
|
|
21189
|
+
sessionId: query.sessionId,
|
|
21190
|
+
from: query.from,
|
|
21191
|
+
after: cursor
|
|
21192
|
+
})) {
|
|
21193
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21194
|
+
if (items.length < limit) {
|
|
21195
|
+
items.push(toInstanceDetail(row));
|
|
21196
|
+
last = row;
|
|
21197
|
+
} else {
|
|
21198
|
+
hasMore = true;
|
|
21199
|
+
break;
|
|
21200
|
+
}
|
|
21201
|
+
}
|
|
21202
|
+
return {
|
|
21203
|
+
items,
|
|
21204
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21205
|
+
};
|
|
21206
|
+
}
|
|
21207
|
+
/**
|
|
21208
|
+
* The same findings folded by location: repository, then file within it.
|
|
21209
|
+
*
|
|
21210
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21211
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21212
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21213
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21214
|
+
* filter can name it.
|
|
21215
|
+
*/
|
|
21216
|
+
listFindingLocations(query) {
|
|
21217
|
+
const opts = {
|
|
21218
|
+
severity: query.severity,
|
|
21219
|
+
subtype: query.subtype,
|
|
21220
|
+
providers: query.provider,
|
|
21221
|
+
actions: query.action,
|
|
21222
|
+
statuses: query.status,
|
|
21223
|
+
tools: query.tool,
|
|
21224
|
+
q: query.q
|
|
21225
|
+
};
|
|
21226
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21227
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21228
|
+
let total = 0;
|
|
21229
|
+
for (const row of this.scanFindingRows({
|
|
21230
|
+
sessionId: query.sessionId,
|
|
21231
|
+
from: query.from
|
|
21232
|
+
})) {
|
|
21233
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21234
|
+
total += 1;
|
|
21235
|
+
let files = byRepo.get(row.repo);
|
|
21236
|
+
if (files === void 0) {
|
|
21237
|
+
files = /* @__PURE__ */ new Map();
|
|
21238
|
+
byRepo.set(row.repo, files);
|
|
21239
|
+
}
|
|
21240
|
+
let acc = files.get(row.file);
|
|
21241
|
+
if (acc === void 0) {
|
|
21242
|
+
acc = newLocationAccumulator();
|
|
21243
|
+
files.set(row.file, acc);
|
|
21244
|
+
}
|
|
21245
|
+
addToLocation(acc, row);
|
|
21246
|
+
}
|
|
21247
|
+
let fileCount = 0;
|
|
21248
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21249
|
+
fileCount += files.size;
|
|
21250
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21251
|
+
file: file2,
|
|
21252
|
+
instanceCount: acc.instanceCount,
|
|
21253
|
+
maxSeverity: acc.maxSeverity,
|
|
21254
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21255
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21256
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21257
|
+
})).sort(compareLocationOrder);
|
|
21258
|
+
const rollup = fileRows.reduce(
|
|
21259
|
+
(a, f) => ({
|
|
21260
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21261
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21262
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21263
|
+
}),
|
|
21264
|
+
{
|
|
21265
|
+
instanceCount: 0,
|
|
21266
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21267
|
+
latestDetectedAt: ""
|
|
21268
|
+
}
|
|
21269
|
+
);
|
|
21270
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21271
|
+
const folded = foldGroupStatus(statuses);
|
|
21272
|
+
return {
|
|
21273
|
+
repo,
|
|
21274
|
+
instanceCount: rollup.instanceCount,
|
|
21275
|
+
maxSeverity: rollup.maxSeverity,
|
|
21276
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21277
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21278
|
+
files: fileRows
|
|
21279
|
+
};
|
|
21280
|
+
});
|
|
21281
|
+
repos.sort(compareLocationOrder);
|
|
21282
|
+
return Promise.resolve({
|
|
21283
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21284
|
+
items: repos.slice(0, limit),
|
|
21285
|
+
hasMore: repos.length > limit
|
|
21286
|
+
});
|
|
21287
|
+
}
|
|
21288
|
+
/**
|
|
21289
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21290
|
+
*
|
|
21291
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21292
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21293
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21294
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21295
|
+
* rather than one unbounded result set.
|
|
21296
|
+
*
|
|
21297
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21298
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21299
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21300
|
+
* a window over the whole resolution table once per batch.
|
|
21301
|
+
*
|
|
21302
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21303
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21304
|
+
* dimension — see listFindingInstances.
|
|
21305
|
+
*/
|
|
21306
|
+
*scanFindingRows(scope) {
|
|
21307
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21308
|
+
const params = [];
|
|
21309
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21310
|
+
conditions.push("e.root_session_id = ?");
|
|
21311
|
+
params.push(scope.sessionId);
|
|
21312
|
+
}
|
|
21313
|
+
if (scope.from !== void 0) {
|
|
21314
|
+
conditions.push("e.started_at >= ?");
|
|
21315
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21316
|
+
}
|
|
21317
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21318
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21319
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21320
|
+
e.started_at AS occurred_at,
|
|
21321
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21322
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21323
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21324
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21325
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21326
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21327
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21328
|
+
FROM inspection_findings f
|
|
21329
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21330
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21331
|
+
WHERE ${conditions.join(" AND ")}
|
|
21332
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21333
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21334
|
+
LIMIT ?`;
|
|
21335
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21336
|
+
for (; ; ) {
|
|
21337
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21338
|
+
...params,
|
|
21339
|
+
after.startedAtMs,
|
|
21340
|
+
after.startedAtMs,
|
|
21341
|
+
after.id,
|
|
21342
|
+
SCAN_BATCH_ROWS
|
|
21343
|
+
]);
|
|
21344
|
+
for (const r of rows) {
|
|
21345
|
+
yield {
|
|
21346
|
+
id: r.id,
|
|
21347
|
+
ruleId: r.rule_id,
|
|
21348
|
+
category: r.category,
|
|
21349
|
+
severity: r.severity,
|
|
21350
|
+
maskedMatch: r.masked_match,
|
|
21351
|
+
actionTaken: r.action_taken,
|
|
21352
|
+
confidence: r.confidence,
|
|
21353
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21354
|
+
sourceTool: r.source_tool,
|
|
21355
|
+
repo: r.repo ?? "",
|
|
21356
|
+
file: r.file ?? "",
|
|
21357
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21358
|
+
eventId: r.event_id,
|
|
21359
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21360
|
+
status: deriveInstanceStatus(r)
|
|
21361
|
+
};
|
|
21362
|
+
}
|
|
21363
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21364
|
+
const lastRow = rows[rows.length - 1];
|
|
21365
|
+
if (lastRow === void 0) return;
|
|
21366
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21367
|
+
}
|
|
21368
|
+
}
|
|
20571
21369
|
groupAggregates(withSearchText, scope) {
|
|
20572
21370
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20573
21371
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20828,7 +21626,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20828
21626
|
};
|
|
20829
21627
|
|
|
20830
21628
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20831
|
-
import { createHash as createHash2, randomUUID as
|
|
21629
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20832
21630
|
|
|
20833
21631
|
// ../../packages/persistence/src/semver.ts
|
|
20834
21632
|
function parse3(version2) {
|
|
@@ -20979,7 +21777,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20979
21777
|
let behind = false;
|
|
20980
21778
|
for (const row of rows) {
|
|
20981
21779
|
const params = {
|
|
20982
|
-
id:
|
|
21780
|
+
id: randomUUID3(),
|
|
20983
21781
|
namespace: row.namespace,
|
|
20984
21782
|
packId: row.packId,
|
|
20985
21783
|
version: row.version,
|
|
@@ -20991,7 +21789,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20991
21789
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20992
21790
|
this.upsertAvailableStmt.run({
|
|
20993
21791
|
...params,
|
|
20994
|
-
id:
|
|
21792
|
+
id: randomUUID3(),
|
|
20995
21793
|
recordedBy: meta3?.recordedBy ?? null
|
|
20996
21794
|
});
|
|
20997
21795
|
} else {
|
|
@@ -21314,14 +22112,15 @@ var SqliteInventoryRepository = class {
|
|
|
21314
22112
|
};
|
|
21315
22113
|
|
|
21316
22114
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21317
|
-
import { randomUUID as
|
|
22115
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21318
22116
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21319
22117
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21320
22118
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21321
22119
|
var HARNESS_LABELS = {
|
|
21322
22120
|
claudecode: "Claude Code",
|
|
21323
22121
|
cursor: "Cursor",
|
|
21324
|
-
codex: "Codex"
|
|
22122
|
+
codex: "Codex",
|
|
22123
|
+
antigravity: "Antigravity"
|
|
21325
22124
|
};
|
|
21326
22125
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21327
22126
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21336,6 +22135,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21336
22135
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21337
22136
|
if (t.includes("cursor")) return "cursor";
|
|
21338
22137
|
if (t.includes("codex")) return "codex";
|
|
22138
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21339
22139
|
return null;
|
|
21340
22140
|
}
|
|
21341
22141
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21794,7 +22594,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21794
22594
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21795
22595
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21796
22596
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21797
|
-
).run({ id:
|
|
22597
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21798
22598
|
}
|
|
21799
22599
|
return true;
|
|
21800
22600
|
}
|
|
@@ -21815,7 +22615,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21815
22615
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21816
22616
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21817
22617
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21818
|
-
).run({ id:
|
|
22618
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21819
22619
|
}
|
|
21820
22620
|
this.configRowsCache = void 0;
|
|
21821
22621
|
return "ok";
|
|
@@ -22112,7 +22912,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22112
22912
|
};
|
|
22113
22913
|
|
|
22114
22914
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22115
|
-
import { randomUUID as
|
|
22915
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22116
22916
|
var SqlitePoliciesRepository = class {
|
|
22117
22917
|
constructor(db) {
|
|
22118
22918
|
this.db = db;
|
|
@@ -22147,7 +22947,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22147
22947
|
failOpenTransaction(this.db, () => {
|
|
22148
22948
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22149
22949
|
stmt.run({
|
|
22150
|
-
id:
|
|
22950
|
+
id: randomUUID5(),
|
|
22151
22951
|
target: JSON.stringify({ category }),
|
|
22152
22952
|
action,
|
|
22153
22953
|
now: Date.now()
|
|
@@ -22167,7 +22967,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22167
22967
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22168
22968
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22169
22969
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22170
|
-
).run({ id:
|
|
22970
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22171
22971
|
}
|
|
22172
22972
|
// Caps every global per-category policy currently set to block/redact down
|
|
22173
22973
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22235,7 +23035,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22235
23035
|
};
|
|
22236
23036
|
|
|
22237
23037
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22238
|
-
import { randomUUID as
|
|
23038
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22239
23039
|
var SqliteProjectFilesRepository = class {
|
|
22240
23040
|
constructor(db) {
|
|
22241
23041
|
this.db = db;
|
|
@@ -22267,7 +23067,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22267
23067
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22268
23068
|
for (const file2 of scan2.files) {
|
|
22269
23069
|
this.upsertStmt.run({
|
|
22270
|
-
id:
|
|
23070
|
+
id: randomUUID6(),
|
|
22271
23071
|
projectId,
|
|
22272
23072
|
path: file2.path,
|
|
22273
23073
|
name: file2.name,
|
|
@@ -22281,7 +23081,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22281
23081
|
};
|
|
22282
23082
|
|
|
22283
23083
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22284
|
-
import { randomUUID as
|
|
23084
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22285
23085
|
var SqliteResolutionsRepository = class {
|
|
22286
23086
|
constructor(db, now = () => Date.now()) {
|
|
22287
23087
|
this.db = db;
|
|
@@ -22335,7 +23135,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22335
23135
|
*/
|
|
22336
23136
|
insertResolution(r) {
|
|
22337
23137
|
this.insertStmt.run({
|
|
22338
|
-
id:
|
|
23138
|
+
id: randomUUID7(),
|
|
22339
23139
|
findingKey: r.findingKey,
|
|
22340
23140
|
status: FindingStatus.parse(r.status),
|
|
22341
23141
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22394,13 +23194,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22394
23194
|
this.readStmt = db.prepare(
|
|
22395
23195
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22396
23196
|
);
|
|
23197
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23198
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23199
|
+
);
|
|
23200
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23201
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23202
|
+
);
|
|
22397
23203
|
}
|
|
22398
23204
|
db;
|
|
22399
23205
|
upsertStmt;
|
|
22400
23206
|
readStmt;
|
|
23207
|
+
countQuarantinedStmt;
|
|
23208
|
+
clearQuarantinedStmt;
|
|
22401
23209
|
getVerdict(ruleKey) {
|
|
22402
23210
|
return getRow(this.readStmt, { ruleKey });
|
|
22403
23211
|
}
|
|
23212
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23213
|
+
countQuarantined() {
|
|
23214
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23215
|
+
}
|
|
23216
|
+
/**
|
|
23217
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23218
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23219
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23220
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23221
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23222
|
+
*
|
|
23223
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23224
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23225
|
+
*
|
|
23226
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23227
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23228
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23229
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23230
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23231
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23232
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23233
|
+
* to restore are silently still disabled.
|
|
23234
|
+
*/
|
|
23235
|
+
clearQuarantined() {
|
|
23236
|
+
const before = this.countQuarantined();
|
|
23237
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23238
|
+
this.clearQuarantinedStmt.run();
|
|
23239
|
+
});
|
|
23240
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23241
|
+
}
|
|
22404
23242
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22405
23243
|
failOpenTransaction(this.db, () => {
|
|
22406
23244
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22455,7 +23293,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22455
23293
|
};
|
|
22456
23294
|
|
|
22457
23295
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22458
|
-
import { randomUUID as
|
|
23296
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23297
|
+
function pageLimit(requested, fallback) {
|
|
23298
|
+
if (requested === void 0) return fallback;
|
|
23299
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23300
|
+
}
|
|
23301
|
+
function encodeReuseCursor(payload) {
|
|
23302
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23303
|
+
}
|
|
23304
|
+
function decodeReuseCursor(cursor) {
|
|
23305
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23306
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23307
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23308
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23309
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23310
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23311
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23312
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23313
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23314
|
+
}
|
|
23315
|
+
return null;
|
|
23316
|
+
}
|
|
23317
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23318
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23319
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23320
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23321
|
+
function toSighting(row) {
|
|
23322
|
+
return {
|
|
23323
|
+
location: row.location,
|
|
23324
|
+
kind: row.kind,
|
|
23325
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23326
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23327
|
+
};
|
|
23328
|
+
}
|
|
22459
23329
|
var SELECT_COLUMNS = `
|
|
22460
23330
|
pointer_id AS pointerId,
|
|
22461
23331
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22639,39 +23509,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22639
23509
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22640
23510
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22641
23511
|
).run({
|
|
22642
|
-
id:
|
|
23512
|
+
id: randomUUID8(),
|
|
22643
23513
|
pointerId: entry.pointerId,
|
|
22644
23514
|
location: entry.location,
|
|
22645
23515
|
kind: entry.kind,
|
|
22646
23516
|
now
|
|
22647
23517
|
});
|
|
22648
23518
|
}
|
|
22649
|
-
|
|
23519
|
+
/**
|
|
23520
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23521
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23522
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23523
|
+
*
|
|
23524
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23525
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23526
|
+
*/
|
|
23527
|
+
sightingsFor(pointerIds) {
|
|
23528
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23529
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22650
23530
|
const rows = allRows(
|
|
22651
23531
|
this.db.prepare(
|
|
22652
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22653
|
-
|
|
23532
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23533
|
+
FROM secret_vault_sighting
|
|
23534
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23535
|
+
ORDER BY last_seen DESC`
|
|
22654
23536
|
),
|
|
22655
|
-
|
|
23537
|
+
pointerIds
|
|
22656
23538
|
);
|
|
23539
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23540
|
+
return byPointer;
|
|
23541
|
+
}
|
|
23542
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23543
|
+
toInventoryEntries(rows) {
|
|
23544
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22657
23545
|
return rows.map((r) => ({
|
|
22658
|
-
|
|
22659
|
-
|
|
23546
|
+
pointerId: r.pointer_id,
|
|
23547
|
+
category: r.category,
|
|
23548
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23549
|
+
maskedMatch: r.masked_match,
|
|
23550
|
+
occurrences: r.occurrence_count,
|
|
22660
23551
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22661
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23552
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23553
|
+
revealGrantId: r.grant_id,
|
|
23554
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22662
23555
|
}));
|
|
22663
23556
|
}
|
|
22664
23557
|
/**
|
|
22665
|
-
* The dashboard inventory
|
|
22666
|
-
* its sightings and the active
|
|
22667
|
-
* Raw-free by construction — neither
|
|
22668
|
-
* columns are selected.
|
|
23558
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23559
|
+
* value's descriptor data joined with its sightings and the active
|
|
23560
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23561
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23562
|
+
*
|
|
23563
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23564
|
+
* sees never depends on how far they have paged.
|
|
22669
23565
|
*/
|
|
22670
|
-
listInventory(now = Date.now()) {
|
|
23566
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23567
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23568
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23569
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22671
23570
|
const rows = allRows(
|
|
22672
23571
|
this.db.prepare(
|
|
22673
|
-
`SELECT
|
|
22674
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23572
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22675
23573
|
(SELECT e.id FROM exceptions e
|
|
22676
23574
|
WHERE e.rule_id = v.rule_id
|
|
22677
23575
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22679,45 +23577,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22679
23577
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22680
23578
|
LIMIT 1) AS grant_id
|
|
22681
23579
|
FROM secret_vault v
|
|
22682
|
-
|
|
23580
|
+
${where}
|
|
23581
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23582
|
+
LIMIT :limit`
|
|
22683
23583
|
),
|
|
22684
|
-
{
|
|
23584
|
+
bindParams({
|
|
23585
|
+
now,
|
|
23586
|
+
limit: limit + 1,
|
|
23587
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23588
|
+
})
|
|
22685
23589
|
);
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
sightings: this.listSightings(r.pointer_id)
|
|
22696
|
-
}));
|
|
23590
|
+
const hasMore = rows.length > limit;
|
|
23591
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23592
|
+
const last = page[page.length - 1];
|
|
23593
|
+
return {
|
|
23594
|
+
totals: { values: this.countEntries() },
|
|
23595
|
+
items: this.toInventoryEntries(page),
|
|
23596
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23597
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23598
|
+
};
|
|
22697
23599
|
}
|
|
22698
23600
|
/**
|
|
22699
|
-
*
|
|
22700
|
-
*
|
|
22701
|
-
*
|
|
22702
|
-
*
|
|
23601
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23602
|
+
* than one location — most-reused first, one page at a time.
|
|
23603
|
+
*
|
|
23604
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23605
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23606
|
+
* under-report exactly the values a reader most needs to see.
|
|
22703
23607
|
*/
|
|
22704
|
-
|
|
22705
|
-
const limit =
|
|
22706
|
-
const
|
|
23608
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23609
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23610
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23611
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23612
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23613
|
+
const rows = allRows(
|
|
23614
|
+
this.db.prepare(
|
|
23615
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23616
|
+
(SELECT e.id FROM exceptions e
|
|
23617
|
+
WHERE e.rule_id = v.rule_id
|
|
23618
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23619
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23620
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23621
|
+
LIMIT 1) AS grant_id
|
|
23622
|
+
FROM secret_vault v
|
|
23623
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23624
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23625
|
+
LIMIT :limit`
|
|
23626
|
+
),
|
|
23627
|
+
bindParams({
|
|
23628
|
+
now,
|
|
23629
|
+
limit: limit + 1,
|
|
23630
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23631
|
+
})
|
|
23632
|
+
);
|
|
23633
|
+
const hasMore = rows.length > limit;
|
|
23634
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23635
|
+
const last = page[page.length - 1];
|
|
23636
|
+
return {
|
|
23637
|
+
totals: { reused: this.countReused() },
|
|
23638
|
+
items: this.toInventoryEntries(page),
|
|
23639
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23640
|
+
};
|
|
23641
|
+
}
|
|
23642
|
+
/**
|
|
23643
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23644
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23645
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23646
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23647
|
+
*
|
|
23648
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23649
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23650
|
+
* the reader pages.
|
|
23651
|
+
*/
|
|
23652
|
+
listDerefs(query = {}) {
|
|
23653
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23654
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23655
|
+
const conditions = [];
|
|
23656
|
+
if (query.includeBatched !== true) {
|
|
23657
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23658
|
+
}
|
|
23659
|
+
if (cursor !== null) {
|
|
23660
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23661
|
+
}
|
|
23662
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22707
23663
|
const rows = allRows(
|
|
22708
23664
|
this.db.prepare(
|
|
22709
23665
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22710
23666
|
FROM secret_vault_deref ${where}
|
|
22711
|
-
ORDER BY at DESC,
|
|
23667
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22712
23668
|
),
|
|
22713
|
-
{
|
|
23669
|
+
bindParams({
|
|
23670
|
+
limit: limit + 1,
|
|
23671
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23672
|
+
})
|
|
22714
23673
|
);
|
|
22715
|
-
const
|
|
23674
|
+
const hasMore = rows.length > limit;
|
|
23675
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23676
|
+
const last = page[page.length - 1];
|
|
23677
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22716
23678
|
this.db,
|
|
22717
23679
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22718
23680
|
);
|
|
22719
23681
|
return {
|
|
22720
|
-
|
|
23682
|
+
items: page.map((r) => ({
|
|
22721
23683
|
id: r.id,
|
|
22722
23684
|
pointerId: r.pointer_id,
|
|
22723
23685
|
at: new Date(r.at).toISOString(),
|
|
@@ -22727,12 +23689,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22727
23689
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22728
23690
|
pointerCount: r.pointer_count
|
|
22729
23691
|
})),
|
|
23692
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22730
23693
|
hiddenBatched
|
|
22731
23694
|
};
|
|
22732
23695
|
}
|
|
22733
23696
|
countEntries() {
|
|
22734
23697
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22735
23698
|
}
|
|
23699
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23700
|
+
countReused() {
|
|
23701
|
+
return countScalar(
|
|
23702
|
+
this.db,
|
|
23703
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23704
|
+
);
|
|
23705
|
+
}
|
|
22736
23706
|
};
|
|
22737
23707
|
|
|
22738
23708
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22747,7 +23717,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22747
23717
|
var SCAN_COVERAGE = [
|
|
22748
23718
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22749
23719
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22750
|
-
{ provider: "codex", coverage:
|
|
23720
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23721
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23722
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22751
23723
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22752
23724
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22753
23725
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23080,7 +24052,7 @@ var SqliteSecurityRepository = class {
|
|
|
23080
24052
|
};
|
|
23081
24053
|
|
|
23082
24054
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23083
|
-
import { randomUUID as
|
|
24055
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23084
24056
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23085
24057
|
var IN_CHUNK = 500;
|
|
23086
24058
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23336,7 +24308,7 @@ var SqliteSharesRepository = class {
|
|
|
23336
24308
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23337
24309
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23338
24310
|
).run({
|
|
23339
|
-
id:
|
|
24311
|
+
id: randomUUID9(),
|
|
23340
24312
|
destinationId,
|
|
23341
24313
|
host: dest.host,
|
|
23342
24314
|
decision,
|
|
@@ -23485,7 +24457,7 @@ var SqliteSharesRepository = class {
|
|
|
23485
24457
|
let destinationId = destIds.get(hit.host);
|
|
23486
24458
|
if (destinationId === void 0) {
|
|
23487
24459
|
destStmt.run({
|
|
23488
|
-
id:
|
|
24460
|
+
id: randomUUID9(),
|
|
23489
24461
|
kind: hit.kind,
|
|
23490
24462
|
name: hit.name,
|
|
23491
24463
|
host: hit.host,
|
|
@@ -23501,7 +24473,7 @@ var SqliteSharesRepository = class {
|
|
|
23501
24473
|
let endpointId = endpointIds.get(endpointKey);
|
|
23502
24474
|
if (endpointId === void 0) {
|
|
23503
24475
|
endpointStmt.run({
|
|
23504
|
-
id:
|
|
24476
|
+
id: randomUUID9(),
|
|
23505
24477
|
destinationId,
|
|
23506
24478
|
method: hit.method,
|
|
23507
24479
|
transport: hit.transport,
|
|
@@ -23514,7 +24486,7 @@ var SqliteSharesRepository = class {
|
|
|
23514
24486
|
endpointIds.set(endpointKey, endpointId);
|
|
23515
24487
|
}
|
|
23516
24488
|
siteStmt.run({
|
|
23517
|
-
id:
|
|
24489
|
+
id: randomUUID9(),
|
|
23518
24490
|
endpointId,
|
|
23519
24491
|
project: input.project,
|
|
23520
24492
|
projectKey: input.projectKey,
|
|
@@ -23879,6 +24851,9 @@ function purgeSampleData(db) {
|
|
|
23879
24851
|
}
|
|
23880
24852
|
|
|
23881
24853
|
// ../../packages/persistence/src/database.ts
|
|
24854
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24855
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24856
|
+
);
|
|
23882
24857
|
function linkHost(input, hostId) {
|
|
23883
24858
|
return hostId ? { ...input, hostId } : input;
|
|
23884
24859
|
}
|
|
@@ -23900,21 +24875,34 @@ function openWithPragmas(file2) {
|
|
|
23900
24875
|
}
|
|
23901
24876
|
return db;
|
|
23902
24877
|
}
|
|
23903
|
-
function backupLegacyStore(file2) {
|
|
23904
|
-
|
|
23905
|
-
|
|
23906
|
-
|
|
23907
|
-
|
|
23908
|
-
|
|
24878
|
+
function backupLegacyStore(db, file2) {
|
|
24879
|
+
reapStalePartials(file2);
|
|
24880
|
+
const backup = backupPath(file2, "legacy");
|
|
24881
|
+
let snapshotted = false;
|
|
24882
|
+
let snapshotError;
|
|
24883
|
+
try {
|
|
24884
|
+
snapshotStore(db, backup);
|
|
24885
|
+
snapshotted = true;
|
|
24886
|
+
} catch (error51) {
|
|
24887
|
+
snapshotError = error51;
|
|
24888
|
+
} finally {
|
|
24889
|
+
db.close();
|
|
23909
24890
|
}
|
|
24891
|
+
if (!snapshotted) {
|
|
24892
|
+
akaWarn(
|
|
24893
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24894
|
+
);
|
|
24895
|
+
moveStoreAside(file2, backup);
|
|
24896
|
+
return backup;
|
|
24897
|
+
}
|
|
24898
|
+
discardStore(file2, backup);
|
|
23910
24899
|
return backup;
|
|
23911
24900
|
}
|
|
23912
24901
|
function openAndInitialize(file2) {
|
|
23913
24902
|
let db = openWithPragmas(file2);
|
|
23914
24903
|
try {
|
|
23915
24904
|
if (isForeignSqliteLineage(db)) {
|
|
23916
|
-
db
|
|
23917
|
-
const backup = backupLegacyStore(file2);
|
|
24905
|
+
const backup = backupLegacyStore(db, file2);
|
|
23918
24906
|
db = openWithPragmas(file2);
|
|
23919
24907
|
akaWarn(
|
|
23920
24908
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23958,7 +24946,7 @@ function openAndInitialize(file2) {
|
|
|
23958
24946
|
}
|
|
23959
24947
|
function openLocalDatabase(dir) {
|
|
23960
24948
|
ensureDataDirSync(dir);
|
|
23961
|
-
const file2 =
|
|
24949
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23962
24950
|
const {
|
|
23963
24951
|
db,
|
|
23964
24952
|
events,
|
|
@@ -24075,7 +25063,7 @@ function openLocalDatabase(dir) {
|
|
|
24075
25063
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24076
25064
|
if (!definitionId) continue;
|
|
24077
25065
|
inspectionFindings.insertFinding({
|
|
24078
|
-
id:
|
|
25066
|
+
id: randomUUID10(),
|
|
24079
25067
|
auditEventId: record2.scanEvent.id,
|
|
24080
25068
|
inspectionDefinitionId: definitionId,
|
|
24081
25069
|
span: finding.span,
|
|
@@ -24181,22 +25169,38 @@ function openLocalDatabase(dir) {
|
|
|
24181
25169
|
transaction,
|
|
24182
25170
|
close: () => {
|
|
24183
25171
|
db.close();
|
|
24184
|
-
}
|
|
25172
|
+
},
|
|
25173
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25174
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24185
25175
|
};
|
|
24186
25176
|
}
|
|
24187
25177
|
|
|
25178
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25179
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25180
|
+
import {
|
|
25181
|
+
closeSync,
|
|
25182
|
+
existsSync as existsSync2,
|
|
25183
|
+
openSync,
|
|
25184
|
+
readFileSync,
|
|
25185
|
+
rmSync as rmSync3,
|
|
25186
|
+
statSync as statSync2,
|
|
25187
|
+
writeFileSync as writeFileSync2
|
|
25188
|
+
} from "fs";
|
|
25189
|
+
import { hostname as hostname3 } from "os";
|
|
25190
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25191
|
+
|
|
24188
25192
|
// ../../packages/persistence/src/finding-key.ts
|
|
24189
25193
|
import { createHash as createHash3 } from "crypto";
|
|
24190
25194
|
|
|
24191
25195
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24192
25196
|
import { createHmac, randomBytes } from "crypto";
|
|
24193
|
-
import { existsSync as
|
|
24194
|
-
import { join as
|
|
25197
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25198
|
+
import { join as join3 } from "path";
|
|
24195
25199
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24196
|
-
var
|
|
25200
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24197
25201
|
var KEY_MATERIAL_BYTES = 32;
|
|
24198
25202
|
function keyFilePath(dataDir2) {
|
|
24199
|
-
return
|
|
25203
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24200
25204
|
}
|
|
24201
25205
|
function parseKeyFile(raw) {
|
|
24202
25206
|
const parsed = JSON.parse(raw);
|
|
@@ -24219,7 +25223,7 @@ function parseKeyFile(raw) {
|
|
|
24219
25223
|
function readFingerprintKey(dataDir2) {
|
|
24220
25224
|
let raw;
|
|
24221
25225
|
try {
|
|
24222
|
-
raw =
|
|
25226
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24223
25227
|
} catch (err) {
|
|
24224
25228
|
if (err.code === "ENOENT") return null;
|
|
24225
25229
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24231,18 +25235,18 @@ function readFingerprintKey(dataDir2) {
|
|
|
24231
25235
|
import { renameSync as renameSync3 } from "fs";
|
|
24232
25236
|
import { mkdir } from "fs/promises";
|
|
24233
25237
|
import { homedir } from "os";
|
|
24234
|
-
import { join as
|
|
25238
|
+
import { join as join4 } from "path";
|
|
24235
25239
|
function defaultDataDir() {
|
|
24236
|
-
return
|
|
25240
|
+
return join4(homedir(), ".aka");
|
|
24237
25241
|
}
|
|
24238
25242
|
function settingsDir(base = defaultDataDir()) {
|
|
24239
|
-
return
|
|
25243
|
+
return join4(base, "settings");
|
|
24240
25244
|
}
|
|
24241
25245
|
function dataDir(base = defaultDataDir()) {
|
|
24242
|
-
return
|
|
25246
|
+
return join4(base, "data");
|
|
24243
25247
|
}
|
|
24244
25248
|
function dbPath(base = defaultDataDir()) {
|
|
24245
|
-
return
|
|
25249
|
+
return join4(dataDir(base), "aka.db");
|
|
24246
25250
|
}
|
|
24247
25251
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24248
25252
|
ensureDataDirSync(dir);
|
|
@@ -24255,8 +25259,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24255
25259
|
for (const { name, dest } of moves) {
|
|
24256
25260
|
try {
|
|
24257
25261
|
ensureDataDirSync(dest);
|
|
24258
|
-
const moved =
|
|
24259
|
-
renameSync3(
|
|
25262
|
+
const moved = join4(dest, name);
|
|
25263
|
+
renameSync3(join4(base, name), moved);
|
|
24260
25264
|
tightenFile(moved);
|
|
24261
25265
|
} catch {
|
|
24262
25266
|
}
|
|
@@ -24264,10 +25268,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24264
25268
|
}
|
|
24265
25269
|
|
|
24266
25270
|
// ../../packages/persistence/src/settings.ts
|
|
24267
|
-
import { readFileSync as
|
|
24268
|
-
import { join as
|
|
25271
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25272
|
+
import { join as join5 } from "path";
|
|
25273
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24269
25274
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24270
|
-
const record2 = readJson(
|
|
25275
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24271
25276
|
if (!record2) return defaultWorkspaceSettings();
|
|
24272
25277
|
try {
|
|
24273
25278
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24278,7 +25283,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24278
25283
|
function readJson(file2) {
|
|
24279
25284
|
let text;
|
|
24280
25285
|
try {
|
|
24281
|
-
text =
|
|
25286
|
+
text = readFileSync3(file2, "utf8");
|
|
24282
25287
|
} catch {
|
|
24283
25288
|
return null;
|
|
24284
25289
|
}
|
|
@@ -24300,34 +25305,34 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24300
25305
|
import {
|
|
24301
25306
|
chmodSync as chmodSync2,
|
|
24302
25307
|
mkdirSync as mkdirSync2,
|
|
24303
|
-
readFileSync as
|
|
25308
|
+
readFileSync as readFileSync4,
|
|
24304
25309
|
renameSync as renameSync4,
|
|
24305
|
-
rmSync as
|
|
24306
|
-
statSync,
|
|
24307
|
-
writeFileSync as
|
|
25310
|
+
rmSync as rmSync4,
|
|
25311
|
+
statSync as statSync3,
|
|
25312
|
+
writeFileSync as writeFileSync3
|
|
24308
25313
|
} from "fs";
|
|
24309
|
-
import { join as
|
|
25314
|
+
import { join as join6 } from "path";
|
|
24310
25315
|
|
|
24311
25316
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24312
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25317
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24313
25318
|
|
|
24314
25319
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
24315
|
-
import { existsSync as
|
|
24316
|
-
import { join as
|
|
25320
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25321
|
+
import { join as join7 } from "path";
|
|
24317
25322
|
var MARKER = "warn-era-capped";
|
|
24318
25323
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
24319
25324
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
24320
|
-
const marker =
|
|
24321
|
-
if (
|
|
25325
|
+
const marker = join7(dataDir2, MARKER);
|
|
25326
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
24322
25327
|
const capped = db.policies.capCategoryActions();
|
|
24323
|
-
|
|
25328
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
24324
25329
|
`, { mode: DATA_FILE_MODE });
|
|
24325
25330
|
return { capped };
|
|
24326
25331
|
}
|
|
24327
25332
|
|
|
24328
25333
|
// ../../packages/plugin-sdk/src/config.ts
|
|
24329
|
-
import { existsSync as
|
|
24330
|
-
import { join as
|
|
25334
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25335
|
+
import { join as join8 } from "path";
|
|
24331
25336
|
|
|
24332
25337
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
24333
25338
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -24378,11 +25383,11 @@ function resolveProvider() {
|
|
|
24378
25383
|
}
|
|
24379
25384
|
|
|
24380
25385
|
// ../../packages/plugin-sdk/src/config.ts
|
|
24381
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25386
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
24382
25387
|
try {
|
|
24383
25388
|
ensureLayoutDirSync(base);
|
|
24384
|
-
const settingsFile =
|
|
24385
|
-
if (
|
|
25389
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25390
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
24386
25391
|
} catch {
|
|
24387
25392
|
}
|
|
24388
25393
|
migrateLegacyLayout(base);
|
|
@@ -24393,21 +25398,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
24393
25398
|
dbPath: dbPath(base),
|
|
24394
25399
|
settingsDir: settingsDir(base),
|
|
24395
25400
|
onboarded: settings.onboardedAt != null,
|
|
24396
|
-
provider: resolveProviderSafe()
|
|
25401
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
24397
25402
|
};
|
|
24398
25403
|
}
|
|
24399
|
-
function resolveProviderSafe() {
|
|
25404
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
24400
25405
|
try {
|
|
24401
|
-
return
|
|
25406
|
+
return resolveProviderFn();
|
|
24402
25407
|
} catch {
|
|
24403
25408
|
return { provider: "anthropic" };
|
|
24404
25409
|
}
|
|
24405
25410
|
}
|
|
24406
25411
|
|
|
24407
25412
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
24408
|
-
import { readdirSync, readFileSync as
|
|
25413
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
24409
25414
|
import { homedir as homedir2 } from "os";
|
|
24410
|
-
import { basename as
|
|
25415
|
+
import { basename as basename3, join as join10 } from "path";
|
|
24411
25416
|
|
|
24412
25417
|
// ../../packages/detections/src/egress/registry.ts
|
|
24413
25418
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -26771,7 +27776,7 @@ var gcp_service_account_default = {
|
|
|
26771
27776
|
severity: "critical",
|
|
26772
27777
|
matcher: {
|
|
26773
27778
|
type: "regex",
|
|
26774
|
-
pattern: "
|
|
27779
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
26775
27780
|
flags: "g"
|
|
26776
27781
|
},
|
|
26777
27782
|
examples: [
|
|
@@ -27143,40 +28148,71 @@ function bundledDetections() {
|
|
|
27143
28148
|
}
|
|
27144
28149
|
|
|
27145
28150
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
27146
|
-
import { existsSync as
|
|
27147
|
-
import { basename, dirname, isAbsolute, join as
|
|
28151
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
28152
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
27148
28153
|
|
|
27149
28154
|
// ../../packages/plugin-sdk/src/events.ts
|
|
27150
|
-
import { createHash as createHash4, randomUUID as
|
|
28155
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28156
|
+
|
|
28157
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
28158
|
+
import { existsSync as existsSync7 } from "fs";
|
|
28159
|
+
import { fileURLToPath } from "url";
|
|
28160
|
+
import { Worker } from "worker_threads";
|
|
27151
28161
|
|
|
27152
28162
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
27153
|
-
import { arch, hostname as
|
|
28163
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
27154
28164
|
|
|
27155
28165
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
27156
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
27157
|
-
import { join as
|
|
28166
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
28167
|
+
import { join as join11 } from "path";
|
|
27158
28168
|
|
|
27159
28169
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
27160
|
-
import { readdirSync as
|
|
27161
|
-
import { basename as
|
|
28170
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
28171
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
27162
28172
|
|
|
27163
28173
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
27164
28174
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
27165
|
-
import { existsSync as
|
|
27166
|
-
import { basename as
|
|
28175
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
28176
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
28177
|
+
|
|
28178
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
28179
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
28180
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28181
|
+
return v;
|
|
28182
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28183
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
28184
|
+
if (typeof v !== "string") return false;
|
|
28185
|
+
const normalized = v.trim().toLowerCase();
|
|
28186
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
28187
|
+
}, external_exports.boolean()).catch(false);
|
|
28188
|
+
var antigravityProviderEnvShape = {
|
|
28189
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
28190
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
28191
|
+
};
|
|
28192
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
28193
|
+
|
|
28194
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
28195
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
28196
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28197
|
+
return v;
|
|
28198
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28199
|
+
var codexProviderEnvShape = {
|
|
28200
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
28201
|
+
};
|
|
28202
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
27167
28203
|
|
|
27168
28204
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
27169
|
-
import { randomUUID as
|
|
28205
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
27170
28206
|
|
|
27171
28207
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
27172
28208
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
27173
28209
|
|
|
27174
28210
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
27175
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
27176
|
-
import { join as
|
|
28211
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
28212
|
+
import { join as join13 } from "path";
|
|
27177
28213
|
|
|
27178
28214
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
27179
|
-
import { randomUUID as
|
|
28215
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
27180
28216
|
|
|
27181
28217
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
27182
28218
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -27338,7 +28374,7 @@ var StandaloneDataGateway = class {
|
|
|
27338
28374
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
27339
28375
|
const installed = this.installedScanRules();
|
|
27340
28376
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
27341
|
-
id:
|
|
28377
|
+
id: randomUUID15(),
|
|
27342
28378
|
scope: "global",
|
|
27343
28379
|
target: { ruleId },
|
|
27344
28380
|
action,
|
|
@@ -27491,7 +28527,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
27491
28527
|
}
|
|
27492
28528
|
|
|
27493
28529
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
27494
|
-
import { randomUUID as
|
|
28530
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
27495
28531
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
27496
28532
|
|
|
27497
28533
|
// src/present.ts
|
|
@@ -27610,10 +28646,58 @@ function fenced(body) {
|
|
|
27610
28646
|
return [fence, body, fence].join("\n");
|
|
27611
28647
|
}
|
|
27612
28648
|
|
|
28649
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
28650
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
28651
|
+
import { join as join14 } from "path";
|
|
28652
|
+
|
|
28653
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
28654
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
28655
|
+
import { tmpdir } from "os";
|
|
28656
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
28657
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
28658
|
+
ruleId: external_exports.string(),
|
|
28659
|
+
category: DetectionCategory,
|
|
28660
|
+
valueFingerprint: external_exports.string(),
|
|
28661
|
+
keyVersion: external_exports.number(),
|
|
28662
|
+
maskedValue: external_exports.string(),
|
|
28663
|
+
justification: external_exports.string()
|
|
28664
|
+
});
|
|
28665
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
28666
|
+
category: DetectionCategory,
|
|
28667
|
+
action: BuiltinPolicyId,
|
|
28668
|
+
genuineCount: external_exports.number(),
|
|
28669
|
+
fpCount: external_exports.number(),
|
|
28670
|
+
reasoning: external_exports.string()
|
|
28671
|
+
});
|
|
28672
|
+
var JoinEntrySchema = external_exports.object({
|
|
28673
|
+
id: external_exports.string(),
|
|
28674
|
+
ruleId: external_exports.string(),
|
|
28675
|
+
category: DetectionCategory,
|
|
28676
|
+
valueFingerprint: external_exports.string().optional(),
|
|
28677
|
+
keyVersion: external_exports.number().optional(),
|
|
28678
|
+
maskedMatch: external_exports.string(),
|
|
28679
|
+
maskedContext: external_exports.string()
|
|
28680
|
+
});
|
|
28681
|
+
var PLAN_FILE_VERSION = 3;
|
|
28682
|
+
var PersistedPlanSchema = external_exports.object({
|
|
28683
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
28684
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
28685
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
28686
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
28687
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
28688
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
28689
|
+
join: external_exports.array(JoinEntrySchema),
|
|
28690
|
+
notes: external_exports.string(),
|
|
28691
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
28692
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
28693
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
28694
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
28695
|
+
});
|
|
28696
|
+
|
|
27613
28697
|
// src/command-registry.ts
|
|
27614
|
-
import { readdirSync as
|
|
27615
|
-
import { fileURLToPath } from "url";
|
|
27616
|
-
var COMMANDS_DIR =
|
|
28698
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
28699
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
28700
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
27617
28701
|
|
|
27618
28702
|
// src/render.ts
|
|
27619
28703
|
var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
|