@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
|
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
495
|
+
import { existsSync as existsSync5 } from "fs";
|
|
496
|
+
import { join as join8 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/database.ts
|
|
499
|
-
import { randomUUID as
|
|
500
|
-
import {
|
|
501
|
-
import { join, sep } from "path";
|
|
499
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
500
|
+
import { join as join2, sep } from "path";
|
|
502
501
|
import { DatabaseSync } from "node:sqlite";
|
|
503
502
|
|
|
504
503
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -578,6 +577,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
578
577
|
{
|
|
579
578
|
tag: "0018_serious_tana_nile",
|
|
580
579
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0019_audit_started_at_index",
|
|
583
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
587
|
+
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');"
|
|
581
588
|
}
|
|
582
589
|
];
|
|
583
590
|
|
|
@@ -15315,7 +15322,17 @@ var Finding = external_exports.object({
|
|
|
15315
15322
|
}).meta({ id: "Finding" });
|
|
15316
15323
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15317
15324
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15318
|
-
var FindingProvider = external_exports.enum([
|
|
15325
|
+
var FindingProvider = external_exports.enum([
|
|
15326
|
+
"claudecode",
|
|
15327
|
+
"claudedesktop",
|
|
15328
|
+
"cursor",
|
|
15329
|
+
"copilot",
|
|
15330
|
+
"chatgpt",
|
|
15331
|
+
"claudeai",
|
|
15332
|
+
"codex",
|
|
15333
|
+
"antigravity",
|
|
15334
|
+
"api"
|
|
15335
|
+
]).meta({ id: "FindingProvider" });
|
|
15319
15336
|
var FindingCategory = external_exports.enum([
|
|
15320
15337
|
"secret",
|
|
15321
15338
|
"pii",
|
|
@@ -15369,7 +15386,16 @@ var FindingInstance = external_exports.object({
|
|
|
15369
15386
|
confidence: external_exports.number().min(0).max(1),
|
|
15370
15387
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15371
15388
|
// that predate the resolution feature stay valid.
|
|
15372
|
-
status: FindingStatus.optional()
|
|
15389
|
+
status: FindingStatus.optional(),
|
|
15390
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15391
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15392
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15393
|
+
// MOST RECENT detection event, not the first.
|
|
15394
|
+
eventId: external_exports.string().optional(),
|
|
15395
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15396
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15397
|
+
// outside a session.
|
|
15398
|
+
sessionId: external_exports.string().optional()
|
|
15373
15399
|
}).meta({ id: "FindingInstance" });
|
|
15374
15400
|
var FindingGroup = external_exports.object({
|
|
15375
15401
|
id: external_exports.string(),
|
|
@@ -15413,7 +15439,11 @@ var FindingFacets = external_exports.object({
|
|
|
15413
15439
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15414
15440
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15415
15441
|
// counted under no value.
|
|
15416
|
-
status: external_exports.array(FindingFacetItem)
|
|
15442
|
+
status: external_exports.array(FindingFacetItem),
|
|
15443
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15444
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15445
|
+
// because a group spans tools.
|
|
15446
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15417
15447
|
}).meta({ id: "FindingFacets" });
|
|
15418
15448
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15419
15449
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15431,6 +15461,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15431
15461
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15432
15462
|
// session → findings drilldown). Findings without a session never match.
|
|
15433
15463
|
sessionId: external_exports.string().optional(),
|
|
15464
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15465
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15466
|
+
// means all time — this list has no default window.
|
|
15467
|
+
from: external_exports.iso.datetime().optional(),
|
|
15468
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15469
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15470
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15471
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15472
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15473
|
+
includeId: external_exports.string().optional(),
|
|
15434
15474
|
groupBy: external_exports.literal("type").optional(),
|
|
15435
15475
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15436
15476
|
cursor: external_exports.string().optional()
|
|
@@ -15475,15 +15515,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15475
15515
|
detection: FindingDetectionRef,
|
|
15476
15516
|
policy: FindingPolicyRef
|
|
15477
15517
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15518
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15519
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15520
|
+
severity: external_exports.array(Severity).optional(),
|
|
15521
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15522
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15523
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15524
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15525
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15526
|
+
// the grouped query's group-level fold.
|
|
15527
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15528
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15529
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15530
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15531
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15532
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15533
|
+
repo: external_exports.string().optional(),
|
|
15534
|
+
file: external_exports.string().optional(),
|
|
15535
|
+
q: external_exports.string().optional(),
|
|
15536
|
+
sessionId: external_exports.string().optional(),
|
|
15537
|
+
from: external_exports.iso.datetime().optional(),
|
|
15538
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15539
|
+
cursor: external_exports.string().optional()
|
|
15540
|
+
});
|
|
15541
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15542
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15543
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15544
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15545
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15546
|
+
// dimension still excludes its own filter.
|
|
15547
|
+
facets: FindingFacets,
|
|
15548
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15549
|
+
nextCursor: external_exports.string().nullable()
|
|
15550
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15551
|
+
var FindingLocationFile = external_exports.object({
|
|
15552
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15553
|
+
// with no file attribution).
|
|
15554
|
+
file: external_exports.string(),
|
|
15555
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15556
|
+
maxSeverity: Severity,
|
|
15557
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15558
|
+
// Folded from the instances' derived statuses with the same
|
|
15559
|
+
// open-dominates precedence a group uses.
|
|
15560
|
+
status: FindingStatus.optional(),
|
|
15561
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15562
|
+
// chips, and the count is what conveys scale.
|
|
15563
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15564
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15565
|
+
var FindingLocationRepo = external_exports.object({
|
|
15566
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15567
|
+
repo: external_exports.string(),
|
|
15568
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15569
|
+
maxSeverity: Severity,
|
|
15570
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15571
|
+
status: FindingStatus.optional(),
|
|
15572
|
+
files: external_exports.array(FindingLocationFile)
|
|
15573
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15574
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15575
|
+
severity: external_exports.array(Severity).optional(),
|
|
15576
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15577
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15578
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15579
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15580
|
+
// instances that match, and folds its status from those.
|
|
15581
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15582
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15583
|
+
q: external_exports.string().optional(),
|
|
15584
|
+
sessionId: external_exports.string().optional(),
|
|
15585
|
+
from: external_exports.iso.datetime().optional(),
|
|
15586
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15587
|
+
});
|
|
15588
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15589
|
+
totals: external_exports.object({
|
|
15590
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15591
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15592
|
+
files: external_exports.number().int().nonnegative()
|
|
15593
|
+
}),
|
|
15594
|
+
/** Sorted by max severity, then most recent. */
|
|
15595
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15596
|
+
/** Whether `limit` truncated the repo list. */
|
|
15597
|
+
hasMore: external_exports.boolean()
|
|
15598
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15478
15599
|
|
|
15479
15600
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15480
|
-
var Harness = external_exports.enum([
|
|
15601
|
+
var Harness = external_exports.enum([
|
|
15602
|
+
"claudecode",
|
|
15603
|
+
"cursor",
|
|
15604
|
+
"copilot",
|
|
15605
|
+
"codex",
|
|
15606
|
+
"antigravity",
|
|
15607
|
+
"windsurf",
|
|
15608
|
+
"claudedesktop",
|
|
15609
|
+
"chatgpt",
|
|
15610
|
+
"claudeai",
|
|
15611
|
+
"api"
|
|
15612
|
+
]).meta({ id: "Harness" });
|
|
15481
15613
|
var TOOL_TO_HARNESS = {
|
|
15482
15614
|
"claude-code": "claudecode",
|
|
15483
15615
|
"claude-desktop": "claudedesktop",
|
|
15484
15616
|
"github-copilot": "copilot",
|
|
15485
15617
|
cursor: "cursor",
|
|
15486
|
-
chatgpt: "chatgpt"
|
|
15618
|
+
chatgpt: "chatgpt",
|
|
15619
|
+
codex: "codex",
|
|
15620
|
+
antigravity: "antigravity",
|
|
15621
|
+
"claude-ai": "claudeai"
|
|
15487
15622
|
};
|
|
15488
15623
|
|
|
15489
15624
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15941,7 +16076,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15941
16076
|
// ../../packages/schema/src/zod/event.ts
|
|
15942
16077
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15943
16078
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15944
|
-
var SourceTool = external_exports.enum([
|
|
16079
|
+
var SourceTool = external_exports.enum([
|
|
16080
|
+
"claude-code",
|
|
16081
|
+
"claude-desktop",
|
|
16082
|
+
"cursor",
|
|
16083
|
+
"chatgpt",
|
|
16084
|
+
"claude-ai",
|
|
16085
|
+
"github-copilot",
|
|
16086
|
+
"codex",
|
|
16087
|
+
"antigravity",
|
|
16088
|
+
"cli",
|
|
16089
|
+
"unknown"
|
|
16090
|
+
]).meta({ id: "SourceTool" });
|
|
15945
16091
|
var EventMetadata = external_exports.object({
|
|
15946
16092
|
sessionId: external_exports.string().optional(),
|
|
15947
16093
|
repo: external_exports.string().optional(),
|
|
@@ -16012,7 +16158,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16012
16158
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16013
16159
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16014
16160
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16015
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16161
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16016
16162
|
var AccessCounts = external_exports.object({
|
|
16017
16163
|
open: external_exports.number().int().nonnegative(),
|
|
16018
16164
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16281,6 +16427,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16281
16427
|
useCount: true,
|
|
16282
16428
|
conditions: true
|
|
16283
16429
|
});
|
|
16430
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16284
16431
|
|
|
16285
16432
|
// ../../packages/schema/src/zod/rule.ts
|
|
16286
16433
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17091,6 +17238,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17091
17238
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17092
17239
|
}).meta({ id: "EgressWriteSummary" });
|
|
17093
17240
|
|
|
17241
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17242
|
+
var confirmation = external_exports.string().optional();
|
|
17243
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17244
|
+
reference: external_exports.string(),
|
|
17245
|
+
scope: external_exports.string(),
|
|
17246
|
+
reason: external_exports.string(),
|
|
17247
|
+
confirmation
|
|
17248
|
+
});
|
|
17249
|
+
var AddExceptionInput = external_exports.object({
|
|
17250
|
+
ruleId: external_exports.string(),
|
|
17251
|
+
value: external_exports.string(),
|
|
17252
|
+
scope: external_exports.string(),
|
|
17253
|
+
reason: external_exports.string(),
|
|
17254
|
+
confirmation
|
|
17255
|
+
});
|
|
17256
|
+
var GrantRevealInput = external_exports.object({
|
|
17257
|
+
pointer: external_exports.string(),
|
|
17258
|
+
scope: external_exports.string(),
|
|
17259
|
+
justification: external_exports.string(),
|
|
17260
|
+
confirmation
|
|
17261
|
+
});
|
|
17262
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17263
|
+
id: external_exports.string(),
|
|
17264
|
+
reason: external_exports.string()
|
|
17265
|
+
});
|
|
17266
|
+
var RotateKeyInput = external_exports.object({
|
|
17267
|
+
confirmation: external_exports.string()
|
|
17268
|
+
});
|
|
17269
|
+
|
|
17094
17270
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17095
17271
|
function toApiAction(dbVal) {
|
|
17096
17272
|
const map2 = {
|
|
@@ -17146,6 +17322,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17146
17322
|
repo: r.repo,
|
|
17147
17323
|
file: r.file,
|
|
17148
17324
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17325
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17326
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17149
17327
|
action: toApiAction(effectiveDbAction),
|
|
17150
17328
|
detectedAt: r.occurredAt,
|
|
17151
17329
|
confidence: r.confidence,
|
|
@@ -17277,14 +17455,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17277
17455
|
}
|
|
17278
17456
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17279
17457
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17458
|
+
function compareFindingGroupOrder(a, b) {
|
|
17459
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17460
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17461
|
+
const severityDiff = rankA - rankB;
|
|
17462
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17463
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17464
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17465
|
+
return a.id.localeCompare(b.id);
|
|
17466
|
+
}
|
|
17280
17467
|
function sortFindingGroups(groups) {
|
|
17281
|
-
return [...groups].sort(
|
|
17282
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17283
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17284
|
-
const severityDiff = rankA - rankB;
|
|
17285
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17286
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17287
|
-
});
|
|
17468
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17288
17469
|
}
|
|
17289
17470
|
function computeFindingFacets(allGroups, opts) {
|
|
17290
17471
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17340,15 +17521,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17340
17521
|
for (const g of forStatus) {
|
|
17341
17522
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17342
17523
|
}
|
|
17343
|
-
const
|
|
17524
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17525
|
+
return {
|
|
17526
|
+
severity: toItems2(severityMap),
|
|
17527
|
+
provider: toItems2(providerMap),
|
|
17528
|
+
action: toItems2(actionMap),
|
|
17529
|
+
subtype: toItems2(subtypeMap),
|
|
17530
|
+
status: toItems2(statusMap)
|
|
17531
|
+
};
|
|
17532
|
+
}
|
|
17533
|
+
|
|
17534
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17535
|
+
function rowHaystack(row) {
|
|
17536
|
+
return [
|
|
17537
|
+
row.ruleId,
|
|
17538
|
+
row.category,
|
|
17539
|
+
row.maskedMatch,
|
|
17540
|
+
row.repo,
|
|
17541
|
+
row.file,
|
|
17542
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17543
|
+
row.id
|
|
17544
|
+
].join(" ").toLowerCase();
|
|
17545
|
+
}
|
|
17546
|
+
function matchesDimension(row, opts, dimension) {
|
|
17547
|
+
switch (dimension) {
|
|
17548
|
+
case "severity":
|
|
17549
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17550
|
+
case "subtype":
|
|
17551
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17552
|
+
case "providers":
|
|
17553
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17554
|
+
case "actions":
|
|
17555
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17556
|
+
case "statuses":
|
|
17557
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17558
|
+
case "tools":
|
|
17559
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17560
|
+
case "repo":
|
|
17561
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17562
|
+
case "file":
|
|
17563
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17564
|
+
case "q":
|
|
17565
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17566
|
+
}
|
|
17567
|
+
}
|
|
17568
|
+
var DIMENSIONS = [
|
|
17569
|
+
"severity",
|
|
17570
|
+
"subtype",
|
|
17571
|
+
"providers",
|
|
17572
|
+
"actions",
|
|
17573
|
+
"statuses",
|
|
17574
|
+
"tools",
|
|
17575
|
+
"repo",
|
|
17576
|
+
"file",
|
|
17577
|
+
"q"
|
|
17578
|
+
];
|
|
17579
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17580
|
+
for (const dimension of DIMENSIONS) {
|
|
17581
|
+
if (dimension === except) continue;
|
|
17582
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17583
|
+
}
|
|
17584
|
+
return true;
|
|
17585
|
+
}
|
|
17586
|
+
function toItems(counts) {
|
|
17587
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17588
|
+
}
|
|
17589
|
+
function bump(counts, value) {
|
|
17590
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17591
|
+
}
|
|
17592
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17593
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17594
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17595
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17596
|
+
const action = /* @__PURE__ */ new Map();
|
|
17597
|
+
const status = /* @__PURE__ */ new Map();
|
|
17598
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17344
17599
|
return {
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17600
|
+
add(row) {
|
|
17601
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17603
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17604
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17605
|
+
}
|
|
17606
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17607
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17608
|
+
bump(status, row.status);
|
|
17609
|
+
}
|
|
17610
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17611
|
+
bump(tool, row.toolName);
|
|
17612
|
+
}
|
|
17613
|
+
},
|
|
17614
|
+
facets: () => ({
|
|
17615
|
+
severity: toItems(severity),
|
|
17616
|
+
subtype: toItems(subtype),
|
|
17617
|
+
provider: toItems(provider),
|
|
17618
|
+
action: toItems(action),
|
|
17619
|
+
status: toItems(status),
|
|
17620
|
+
tool: toItems(tool)
|
|
17621
|
+
})
|
|
17622
|
+
};
|
|
17623
|
+
}
|
|
17624
|
+
function toInstanceDetail(row) {
|
|
17625
|
+
const category = toApiCategory(row.category);
|
|
17626
|
+
return {
|
|
17627
|
+
id: row.id,
|
|
17628
|
+
provider: toApiProvider(row.sourceTool),
|
|
17629
|
+
repo: row.repo,
|
|
17630
|
+
file: row.file,
|
|
17631
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17632
|
+
eventId: row.eventId,
|
|
17633
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17634
|
+
action: toApiAction(row.actionTaken),
|
|
17635
|
+
detectedAt: row.occurredAt,
|
|
17636
|
+
confidence: row.confidence,
|
|
17637
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17638
|
+
groupId: row.ruleId,
|
|
17639
|
+
category,
|
|
17640
|
+
subtype: row.ruleId,
|
|
17641
|
+
severity: row.severity,
|
|
17642
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17643
|
+
detection: { id: row.ruleId, name: null },
|
|
17644
|
+
policy: { id: `category:${category}`, name: category }
|
|
17645
|
+
};
|
|
17646
|
+
}
|
|
17647
|
+
var SEVERITY_ORDER2 = {
|
|
17648
|
+
critical: 0,
|
|
17649
|
+
high: 1,
|
|
17650
|
+
medium: 2,
|
|
17651
|
+
low: 3
|
|
17652
|
+
};
|
|
17653
|
+
function newLocationAccumulator() {
|
|
17654
|
+
return {
|
|
17655
|
+
instanceCount: 0,
|
|
17656
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17657
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17658
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17659
|
+
maxSeverity: "low",
|
|
17660
|
+
latestDetectedAt: "",
|
|
17661
|
+
statuses: [],
|
|
17662
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17350
17663
|
};
|
|
17351
17664
|
}
|
|
17665
|
+
function addToLocation(acc, row) {
|
|
17666
|
+
acc.instanceCount += 1;
|
|
17667
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17668
|
+
if (rank < acc.maxSeverityRank) {
|
|
17669
|
+
acc.maxSeverityRank = rank;
|
|
17670
|
+
acc.maxSeverity = row.severity;
|
|
17671
|
+
}
|
|
17672
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17673
|
+
acc.statuses.push(row.status);
|
|
17674
|
+
acc.ruleIds.add(row.ruleId);
|
|
17675
|
+
}
|
|
17352
17676
|
|
|
17353
17677
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17354
17678
|
var InstalledPack = external_exports.object({
|
|
@@ -17489,6 +17813,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17489
17813
|
revealGrantId: external_exports.string().nullable(),
|
|
17490
17814
|
sightings: external_exports.array(VaultSighting)
|
|
17491
17815
|
});
|
|
17816
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17817
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17818
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17819
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17820
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17821
|
+
// Opaque; names the last row of the page just served.
|
|
17822
|
+
cursor: external_exports.string().optional()
|
|
17823
|
+
});
|
|
17824
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17825
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17826
|
+
// independent, so paging never changes what the count claims.
|
|
17827
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17828
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17829
|
+
// `null` once the last page is reached.
|
|
17830
|
+
nextCursor: external_exports.string().nullable()
|
|
17831
|
+
});
|
|
17832
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17833
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17834
|
+
cursor: external_exports.string().optional()
|
|
17835
|
+
});
|
|
17836
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17837
|
+
// Reused values across the whole store — the number the section's claim
|
|
17838
|
+
// ("values detected in more than one place") is about.
|
|
17839
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17840
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17841
|
+
nextCursor: external_exports.string().nullable()
|
|
17842
|
+
});
|
|
17843
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17844
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17845
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17846
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17847
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17848
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17849
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17850
|
+
cursor: external_exports.string().optional()
|
|
17851
|
+
});
|
|
17852
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17853
|
+
items: external_exports.array(VaultDeref),
|
|
17854
|
+
nextCursor: external_exports.string().nullable(),
|
|
17855
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17856
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17857
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17858
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17859
|
+
});
|
|
17492
17860
|
var VaultKeyCustody = external_exports.string();
|
|
17493
17861
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17494
17862
|
var VAULT_INLINE_REVEAL_MAX_PER_MESSAGE = 2;
|
|
@@ -17866,7 +18234,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17866
18234
|
// Omit for both kinds.
|
|
17867
18235
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17868
18236
|
});
|
|
17869
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18237
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17870
18238
|
var ScanCoverageProvider = external_exports.object({
|
|
17871
18239
|
provider: Provider,
|
|
17872
18240
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18108,6 +18476,195 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18108
18476
|
);
|
|
18109
18477
|
}
|
|
18110
18478
|
|
|
18479
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18480
|
+
import { randomUUID } from "crypto";
|
|
18481
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18482
|
+
import { basename, dirname, join } from "path";
|
|
18483
|
+
|
|
18484
|
+
// ../../packages/persistence/src/paths.ts
|
|
18485
|
+
import {
|
|
18486
|
+
chmodSync,
|
|
18487
|
+
linkSync,
|
|
18488
|
+
lstatSync,
|
|
18489
|
+
mkdirSync,
|
|
18490
|
+
renameSync,
|
|
18491
|
+
rmSync,
|
|
18492
|
+
writeFileSync
|
|
18493
|
+
} from "fs";
|
|
18494
|
+
import { threadId } from "worker_threads";
|
|
18495
|
+
var DATA_DIR_MODE = 448;
|
|
18496
|
+
var DATA_FILE_MODE = 384;
|
|
18497
|
+
var DB_FILENAME = "aka.db";
|
|
18498
|
+
function isSymlink(path) {
|
|
18499
|
+
try {
|
|
18500
|
+
return lstatSync(path).isSymbolicLink();
|
|
18501
|
+
} catch {
|
|
18502
|
+
return false;
|
|
18503
|
+
}
|
|
18504
|
+
}
|
|
18505
|
+
function chmodBestEffort(path, mode) {
|
|
18506
|
+
if (isSymlink(path)) return;
|
|
18507
|
+
try {
|
|
18508
|
+
chmodSync(path, mode);
|
|
18509
|
+
} catch {
|
|
18510
|
+
}
|
|
18511
|
+
}
|
|
18512
|
+
function tightenDir(dir) {
|
|
18513
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18514
|
+
}
|
|
18515
|
+
function ensureDataDirSync(dir) {
|
|
18516
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18517
|
+
tightenDir(dir);
|
|
18518
|
+
}
|
|
18519
|
+
function dbSidecars(file2) {
|
|
18520
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18521
|
+
}
|
|
18522
|
+
function tightenFile(file2) {
|
|
18523
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18524
|
+
}
|
|
18525
|
+
function tightenPerms(file2) {
|
|
18526
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18527
|
+
}
|
|
18528
|
+
function classifyOccupant(file2) {
|
|
18529
|
+
try {
|
|
18530
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
18531
|
+
return { kind: "gone" };
|
|
18532
|
+
} catch (err) {
|
|
18533
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
18534
|
+
return { kind: "unknown", cause: err };
|
|
18535
|
+
}
|
|
18536
|
+
}
|
|
18537
|
+
var KeyUnclaimableError = class extends Error {
|
|
18538
|
+
code = "key-unclaimable";
|
|
18539
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
18540
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
18541
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
18542
|
+
// captured and then lost, which is worse than its plain absence.
|
|
18543
|
+
constructor(message, cause) {
|
|
18544
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
18545
|
+
this.name = "KeyUnclaimableError";
|
|
18546
|
+
}
|
|
18547
|
+
};
|
|
18548
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
18549
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
18550
|
+
try {
|
|
18551
|
+
rmSync(tmp, { force: true });
|
|
18552
|
+
} catch {
|
|
18553
|
+
}
|
|
18554
|
+
let created;
|
|
18555
|
+
try {
|
|
18556
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18557
|
+
created = publishByLink(tmp, file2, data);
|
|
18558
|
+
} finally {
|
|
18559
|
+
try {
|
|
18560
|
+
rmSync(tmp, { force: true });
|
|
18561
|
+
} catch {
|
|
18562
|
+
}
|
|
18563
|
+
}
|
|
18564
|
+
if (created) tightenFile(file2);
|
|
18565
|
+
return created;
|
|
18566
|
+
}
|
|
18567
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
18568
|
+
function publishByLink(tmp, file2, data) {
|
|
18569
|
+
try {
|
|
18570
|
+
linkSync(tmp, file2);
|
|
18571
|
+
return true;
|
|
18572
|
+
} catch (err) {
|
|
18573
|
+
const code = err.code;
|
|
18574
|
+
if (code === "EEXIST") return false;
|
|
18575
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18576
|
+
}
|
|
18577
|
+
try {
|
|
18578
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18579
|
+
return true;
|
|
18580
|
+
} catch (err) {
|
|
18581
|
+
if (err.code === "EEXIST") return false;
|
|
18582
|
+
throw err;
|
|
18583
|
+
}
|
|
18584
|
+
}
|
|
18585
|
+
|
|
18586
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18587
|
+
function backupPath(file2, tag) {
|
|
18588
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18589
|
+
}
|
|
18590
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18591
|
+
function reapStalePartials(file2) {
|
|
18592
|
+
const dir = dirname(file2);
|
|
18593
|
+
const prefix = `${basename(file2)}.`;
|
|
18594
|
+
let entries;
|
|
18595
|
+
try {
|
|
18596
|
+
entries = readdirSync(dir);
|
|
18597
|
+
} catch {
|
|
18598
|
+
return;
|
|
18599
|
+
}
|
|
18600
|
+
for (const name of entries) {
|
|
18601
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18602
|
+
const partial2 = join(dir, name);
|
|
18603
|
+
try {
|
|
18604
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18605
|
+
rmSync2(partial2, { force: true });
|
|
18606
|
+
}
|
|
18607
|
+
} catch {
|
|
18608
|
+
}
|
|
18609
|
+
}
|
|
18610
|
+
}
|
|
18611
|
+
function snapshotStore(db, backup) {
|
|
18612
|
+
const partial2 = `${backup}.partial`;
|
|
18613
|
+
try {
|
|
18614
|
+
rmSync2(partial2, { force: true });
|
|
18615
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18616
|
+
tightenFile(partial2);
|
|
18617
|
+
renameSync2(partial2, backup);
|
|
18618
|
+
} catch (error51) {
|
|
18619
|
+
try {
|
|
18620
|
+
rmSync2(partial2, { force: true });
|
|
18621
|
+
} catch {
|
|
18622
|
+
}
|
|
18623
|
+
throw error51;
|
|
18624
|
+
}
|
|
18625
|
+
}
|
|
18626
|
+
function moveStoreAside(file2, backup) {
|
|
18627
|
+
const undo = [];
|
|
18628
|
+
renameSync2(file2, backup);
|
|
18629
|
+
undo.push([backup, file2]);
|
|
18630
|
+
try {
|
|
18631
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18632
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18633
|
+
try {
|
|
18634
|
+
renameSync2(sidecar, moved);
|
|
18635
|
+
undo.push([moved, sidecar]);
|
|
18636
|
+
} catch {
|
|
18637
|
+
rmSync2(sidecar, { force: true });
|
|
18638
|
+
}
|
|
18639
|
+
}
|
|
18640
|
+
} catch (error51) {
|
|
18641
|
+
for (const [from, to] of undo.reverse()) {
|
|
18642
|
+
try {
|
|
18643
|
+
renameSync2(from, to);
|
|
18644
|
+
} catch {
|
|
18645
|
+
}
|
|
18646
|
+
}
|
|
18647
|
+
throw error51;
|
|
18648
|
+
}
|
|
18649
|
+
tightenPerms(backup);
|
|
18650
|
+
}
|
|
18651
|
+
function discardStore(file2, backup) {
|
|
18652
|
+
try {
|
|
18653
|
+
rmSync2(file2, { force: true });
|
|
18654
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18655
|
+
rmSync2(sidecar, { force: true });
|
|
18656
|
+
}
|
|
18657
|
+
} catch (error51) {
|
|
18658
|
+
if (existsSync(file2)) {
|
|
18659
|
+
try {
|
|
18660
|
+
rmSync2(backup, { force: true });
|
|
18661
|
+
} catch {
|
|
18662
|
+
}
|
|
18663
|
+
}
|
|
18664
|
+
throw error51;
|
|
18665
|
+
}
|
|
18666
|
+
}
|
|
18667
|
+
|
|
18111
18668
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18112
18669
|
function escapeLikePattern(s) {
|
|
18113
18670
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18249,55 +18806,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18249
18806
|
return out;
|
|
18250
18807
|
}
|
|
18251
18808
|
|
|
18252
|
-
// ../../packages/persistence/src/paths.ts
|
|
18253
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18254
|
-
var DATA_DIR_MODE = 448;
|
|
18255
|
-
var DATA_FILE_MODE = 384;
|
|
18256
|
-
var DB_FILENAME = "aka.db";
|
|
18257
|
-
function chmodBestEffort(path, mode) {
|
|
18258
|
-
try {
|
|
18259
|
-
chmodSync(path, mode);
|
|
18260
|
-
} catch {
|
|
18261
|
-
}
|
|
18262
|
-
}
|
|
18263
|
-
function tightenDir(dir) {
|
|
18264
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18265
|
-
}
|
|
18266
|
-
function ensureDataDirSync(dir) {
|
|
18267
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18268
|
-
tightenDir(dir);
|
|
18269
|
-
}
|
|
18270
|
-
function dbSidecars(file2) {
|
|
18271
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18272
|
-
}
|
|
18273
|
-
function tightenFile(file2) {
|
|
18274
|
-
try {
|
|
18275
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18276
|
-
} catch {
|
|
18277
|
-
}
|
|
18278
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18279
|
-
}
|
|
18280
|
-
function tightenPerms(file2) {
|
|
18281
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18282
|
-
}
|
|
18283
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18284
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18285
|
-
try {
|
|
18286
|
-
rmSync(tmp, { force: true });
|
|
18287
|
-
} catch {
|
|
18288
|
-
}
|
|
18289
|
-
try {
|
|
18290
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18291
|
-
renameSync(tmp, file2);
|
|
18292
|
-
} finally {
|
|
18293
|
-
try {
|
|
18294
|
-
rmSync(tmp, { force: true });
|
|
18295
|
-
} catch {
|
|
18296
|
-
}
|
|
18297
|
-
}
|
|
18298
|
-
tightenFile(file2);
|
|
18299
|
-
}
|
|
18300
|
-
|
|
18301
18809
|
// ../../packages/persistence/src/migrations.ts
|
|
18302
18810
|
function describeObject(object2) {
|
|
18303
18811
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18413,9 +18921,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18413
18921
|
}
|
|
18414
18922
|
}
|
|
18415
18923
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
|
|
18924
|
+
reapStalePartials(file2);
|
|
18925
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18926
|
+
snapshotStore(db, backup);
|
|
18419
18927
|
return backup;
|
|
18420
18928
|
}
|
|
18421
18929
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18759,6 +19267,25 @@ function parseJsonObject(s) {
|
|
|
18759
19267
|
return void 0;
|
|
18760
19268
|
}
|
|
18761
19269
|
|
|
19270
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19271
|
+
function encodeKeysetCursor(payload) {
|
|
19272
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19273
|
+
}
|
|
19274
|
+
function decodeKeysetCursor(cursor) {
|
|
19275
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19276
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19277
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19278
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19279
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19280
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19281
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19282
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19283
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19284
|
+
return parsed;
|
|
19285
|
+
}
|
|
19286
|
+
return null;
|
|
19287
|
+
}
|
|
19288
|
+
|
|
18762
19289
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18763
19290
|
var DAY_MS = 864e5;
|
|
18764
19291
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18804,16 +19331,6 @@ function utcWindow(nowMs) {
|
|
|
18804
19331
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18805
19332
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18806
19333
|
}
|
|
18807
|
-
function encodeCursor(payload) {
|
|
18808
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18809
|
-
}
|
|
18810
|
-
function decodeCursor(cursor) {
|
|
18811
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18812
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18813
|
-
return parsed;
|
|
18814
|
-
}
|
|
18815
|
-
return null;
|
|
18816
|
-
}
|
|
18817
19334
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18818
19335
|
session: "session",
|
|
18819
19336
|
prompt: "prompt",
|
|
@@ -18958,7 +19475,7 @@ var SqliteActivityRepository = class {
|
|
|
18958
19475
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18959
19476
|
}
|
|
18960
19477
|
listSessions(query) {
|
|
18961
|
-
const cursor = query.cursor ?
|
|
19478
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18962
19479
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18963
19480
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18964
19481
|
const conditions = [SESSION_ROOT];
|
|
@@ -19032,7 +19549,7 @@ var SqliteActivityRepository = class {
|
|
|
19032
19549
|
)
|
|
19033
19550
|
);
|
|
19034
19551
|
const last = page[page.length - 1];
|
|
19035
|
-
const nextCursor = hasMore && last ?
|
|
19552
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19036
19553
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19037
19554
|
}
|
|
19038
19555
|
getSession(sessionId) {
|
|
@@ -19905,7 +20422,7 @@ var SqliteEventsRepository = class {
|
|
|
19905
20422
|
};
|
|
19906
20423
|
|
|
19907
20424
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19908
|
-
import { randomUUID } from "crypto";
|
|
20425
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19909
20426
|
|
|
19910
20427
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19911
20428
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19941,8 +20458,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19941
20458
|
AND conditions IS NULL
|
|
19942
20459
|
AND ${ACTIVE_PREDICATE}`;
|
|
19943
20460
|
var SqliteExceptionsRepository = class {
|
|
19944
|
-
constructor(db) {
|
|
20461
|
+
constructor(db, now = () => Date.now()) {
|
|
19945
20462
|
this.db = db;
|
|
20463
|
+
this.now = now;
|
|
19946
20464
|
this.consumeStmt = db.prepare(
|
|
19947
20465
|
`UPDATE exceptions
|
|
19948
20466
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19960,6 +20478,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19960
20478
|
);
|
|
19961
20479
|
}
|
|
19962
20480
|
db;
|
|
20481
|
+
now;
|
|
19963
20482
|
consumeStmt;
|
|
19964
20483
|
insertBlockedStmt;
|
|
19965
20484
|
sweepBlockedStmt;
|
|
@@ -19986,8 +20505,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19986
20505
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19987
20506
|
);
|
|
19988
20507
|
}
|
|
19989
|
-
const id =
|
|
19990
|
-
const now =
|
|
20508
|
+
const id = randomUUID2();
|
|
20509
|
+
const now = this.now();
|
|
19991
20510
|
try {
|
|
19992
20511
|
this.insertExceptionRow(id, input, now);
|
|
19993
20512
|
} catch (err) {
|
|
@@ -20065,7 +20584,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20065
20584
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20066
20585
|
const rows = allRows(
|
|
20067
20586
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20068
|
-
opts?.includeTerminal ? {} : { now:
|
|
20587
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20069
20588
|
);
|
|
20070
20589
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20071
20590
|
return Promise.resolve(exceptions);
|
|
@@ -20100,7 +20619,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20100
20619
|
* already revoked.
|
|
20101
20620
|
*/
|
|
20102
20621
|
revoke(id, revokedBy, reason) {
|
|
20103
|
-
const now =
|
|
20622
|
+
const now = this.now();
|
|
20104
20623
|
const result = this.db.prepare(
|
|
20105
20624
|
`UPDATE exceptions
|
|
20106
20625
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20114,7 +20633,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20114
20633
|
* callers must treat identically — means it does not and the detection is
|
|
20115
20634
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20116
20635
|
*/
|
|
20117
|
-
consume(id, now =
|
|
20636
|
+
consume(id, now = this.now()) {
|
|
20118
20637
|
const result = this.consumeStmt.run({ id, now });
|
|
20119
20638
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20120
20639
|
}
|
|
@@ -20123,7 +20642,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20123
20642
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20124
20643
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20125
20644
|
*/
|
|
20126
|
-
activeBundleEntries(keyVersion, now =
|
|
20645
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20127
20646
|
const rows = allRows(
|
|
20128
20647
|
this.db.prepare(
|
|
20129
20648
|
`SELECT * FROM exceptions
|
|
@@ -20155,7 +20674,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20155
20674
|
* than the retention window on every write, so the ledger self-limits.
|
|
20156
20675
|
*/
|
|
20157
20676
|
recordBlocked(entry) {
|
|
20158
|
-
const now =
|
|
20677
|
+
const now = this.now();
|
|
20159
20678
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20160
20679
|
this.insertBlockedStmt.run({
|
|
20161
20680
|
reference: entry.reference,
|
|
@@ -20178,7 +20697,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20178
20697
|
WHERE blocked_at > :cutoff
|
|
20179
20698
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20180
20699
|
),
|
|
20181
|
-
{ cutoff:
|
|
20700
|
+
{ cutoff: this.now() - windowMs }
|
|
20182
20701
|
);
|
|
20183
20702
|
return Promise.resolve(
|
|
20184
20703
|
rows.map((row) => ({
|
|
@@ -20206,8 +20725,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20206
20725
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20207
20726
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20208
20727
|
*/
|
|
20209
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20728
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20210
20729
|
try {
|
|
20730
|
+
const at = now ?? this.now();
|
|
20211
20731
|
const row = getRow(
|
|
20212
20732
|
this.db.prepare(
|
|
20213
20733
|
`SELECT id FROM exceptions
|
|
@@ -20216,7 +20736,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20216
20736
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20217
20737
|
LIMIT 1`
|
|
20218
20738
|
),
|
|
20219
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20739
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20220
20740
|
);
|
|
20221
20741
|
return Promise.resolve(row ?? null);
|
|
20222
20742
|
} catch (err) {
|
|
@@ -20230,7 +20750,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20230
20750
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20231
20751
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20232
20752
|
*/
|
|
20233
|
-
sweepTerminal(retentionMs, now =
|
|
20753
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20234
20754
|
const result = this.db.prepare(
|
|
20235
20755
|
`DELETE FROM exceptions
|
|
20236
20756
|
WHERE updated_at < :cutoff
|
|
@@ -20293,6 +20813,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20293
20813
|
|
|
20294
20814
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20295
20815
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20816
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20817
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20818
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20819
|
+
function compareLocationOrder(a, b) {
|
|
20820
|
+
return compareFindingGroupOrder(
|
|
20821
|
+
{
|
|
20822
|
+
severity: a.maxSeverity,
|
|
20823
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20824
|
+
id: ""
|
|
20825
|
+
},
|
|
20826
|
+
{
|
|
20827
|
+
severity: b.maxSeverity,
|
|
20828
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20829
|
+
id: ""
|
|
20830
|
+
}
|
|
20831
|
+
);
|
|
20832
|
+
}
|
|
20296
20833
|
var CONCAT_SEP = ",";
|
|
20297
20834
|
var TUPLE_SEP = "|";
|
|
20298
20835
|
function splitConcat(value) {
|
|
@@ -20305,6 +20842,33 @@ function deriveInstanceStatus(row) {
|
|
|
20305
20842
|
latestResolutionStatus: row.latest_status
|
|
20306
20843
|
});
|
|
20307
20844
|
}
|
|
20845
|
+
function encodeGroupCursor(group) {
|
|
20846
|
+
const payload = {
|
|
20847
|
+
sev: group.severity,
|
|
20848
|
+
t: group.latestDetectedAt,
|
|
20849
|
+
id: group.id
|
|
20850
|
+
};
|
|
20851
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20852
|
+
}
|
|
20853
|
+
function decodeGroupCursor(cursor) {
|
|
20854
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20855
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20856
|
+
return {
|
|
20857
|
+
severity: parsed.sev,
|
|
20858
|
+
latestDetectedAt: parsed.t,
|
|
20859
|
+
id: parsed.id
|
|
20860
|
+
};
|
|
20861
|
+
}
|
|
20862
|
+
return null;
|
|
20863
|
+
}
|
|
20864
|
+
function firstAfter(sorted, cursor) {
|
|
20865
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20866
|
+
return index === -1 ? sorted.length : index;
|
|
20867
|
+
}
|
|
20868
|
+
function findDeepLinked(sorted, page, id) {
|
|
20869
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20870
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20871
|
+
}
|
|
20308
20872
|
var DAY_MS3 = 864e5;
|
|
20309
20873
|
var SqliteFindingsRepository = class {
|
|
20310
20874
|
constructor(db) {
|
|
@@ -20414,8 +20978,13 @@ var SqliteFindingsRepository = class {
|
|
|
20414
20978
|
*/
|
|
20415
20979
|
listGroupedFindings(query) {
|
|
20416
20980
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20417
|
-
const
|
|
20418
|
-
const
|
|
20981
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20982
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20983
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20984
|
+
const sessionParams = {
|
|
20985
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20986
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20987
|
+
};
|
|
20419
20988
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20420
20989
|
predicate,
|
|
20421
20990
|
params: sessionParams
|
|
@@ -20423,7 +20992,8 @@ var SqliteFindingsRepository = class {
|
|
|
20423
20992
|
const rows = allRows(
|
|
20424
20993
|
this.db.prepare(
|
|
20425
20994
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20426
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20995
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20996
|
+
kind, finding_key, latest_status
|
|
20427
20997
|
FROM (
|
|
20428
20998
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20429
20999
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20433,6 +21003,7 @@ var SqliteFindingsRepository = class {
|
|
|
20433
21003
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20434
21004
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20435
21005
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21006
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20436
21007
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20437
21008
|
latest.status AS latest_status,
|
|
20438
21009
|
ROW_NUMBER() OVER (
|
|
@@ -20464,6 +21035,8 @@ var SqliteFindingsRepository = class {
|
|
|
20464
21035
|
repo: r.repo ?? "",
|
|
20465
21036
|
file: r.file ?? "",
|
|
20466
21037
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21038
|
+
eventId: r.event_id,
|
|
21039
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20467
21040
|
status: deriveInstanceStatus(r)
|
|
20468
21041
|
}));
|
|
20469
21042
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20487,18 +21060,23 @@ var SqliteFindingsRepository = class {
|
|
|
20487
21060
|
groups: sorted.length
|
|
20488
21061
|
};
|
|
20489
21062
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21063
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21064
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21065
|
+
const page = sorted.slice(start, start + limit);
|
|
21066
|
+
const lastOnPage = page.at(-1);
|
|
21067
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21068
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20490
21069
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20491
|
-
const
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
);
|
|
21070
|
+
const narrow = (g) => statusSet ? {
|
|
21071
|
+
...g,
|
|
21072
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21073
|
+
} : g;
|
|
21074
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20497
21075
|
return Promise.resolve({
|
|
20498
21076
|
totals,
|
|
20499
21077
|
facets,
|
|
20500
21078
|
items,
|
|
20501
|
-
nextCursor
|
|
21079
|
+
nextCursor,
|
|
20502
21080
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20503
21081
|
});
|
|
20504
21082
|
}
|
|
@@ -20530,6 +21108,266 @@ var SqliteFindingsRepository = class {
|
|
|
20530
21108
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20531
21109
|
* path repeating across tuples.)
|
|
20532
21110
|
*/
|
|
21111
|
+
/**
|
|
21112
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21113
|
+
* paged by keyset.
|
|
21114
|
+
*
|
|
21115
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21116
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21117
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21118
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21119
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21120
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21121
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21122
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21123
|
+
* none of the mappers names", which no IN-list can say.
|
|
21124
|
+
*
|
|
21125
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21126
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21127
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21128
|
+
* while the counting runs, and only the page itself is retained.
|
|
21129
|
+
*/
|
|
21130
|
+
listFindingInstances(query) {
|
|
21131
|
+
const opts = {
|
|
21132
|
+
severity: query.severity,
|
|
21133
|
+
subtype: query.subtype,
|
|
21134
|
+
providers: query.provider,
|
|
21135
|
+
actions: query.action,
|
|
21136
|
+
statuses: query.status,
|
|
21137
|
+
tools: query.tool,
|
|
21138
|
+
repo: query.repo,
|
|
21139
|
+
file: query.file,
|
|
21140
|
+
q: query.q
|
|
21141
|
+
};
|
|
21142
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21143
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21144
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21145
|
+
const items = [];
|
|
21146
|
+
let total = 0;
|
|
21147
|
+
let last;
|
|
21148
|
+
let hasMore = false;
|
|
21149
|
+
for (const row of this.scanFindingRows({
|
|
21150
|
+
sessionId: query.sessionId,
|
|
21151
|
+
from: query.from
|
|
21152
|
+
})) {
|
|
21153
|
+
accumulator.add(row);
|
|
21154
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21155
|
+
total += 1;
|
|
21156
|
+
if (items.length < limit) {
|
|
21157
|
+
items.push(toInstanceDetail(row));
|
|
21158
|
+
last = row;
|
|
21159
|
+
} else {
|
|
21160
|
+
hasMore = true;
|
|
21161
|
+
}
|
|
21162
|
+
}
|
|
21163
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21164
|
+
if (cursor !== null) {
|
|
21165
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21166
|
+
return Promise.resolve({
|
|
21167
|
+
totals: { findings: total },
|
|
21168
|
+
facets: accumulator.facets(),
|
|
21169
|
+
items: resumed.items,
|
|
21170
|
+
nextCursor: resumed.nextCursor
|
|
21171
|
+
});
|
|
21172
|
+
}
|
|
21173
|
+
return Promise.resolve({
|
|
21174
|
+
totals: { findings: total },
|
|
21175
|
+
facets: accumulator.facets(),
|
|
21176
|
+
items,
|
|
21177
|
+
nextCursor
|
|
21178
|
+
});
|
|
21179
|
+
}
|
|
21180
|
+
/**
|
|
21181
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21182
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21183
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21184
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21185
|
+
*/
|
|
21186
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21187
|
+
const items = [];
|
|
21188
|
+
let last;
|
|
21189
|
+
let hasMore = false;
|
|
21190
|
+
for (const row of this.scanFindingRows({
|
|
21191
|
+
sessionId: query.sessionId,
|
|
21192
|
+
from: query.from,
|
|
21193
|
+
after: cursor
|
|
21194
|
+
})) {
|
|
21195
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21196
|
+
if (items.length < limit) {
|
|
21197
|
+
items.push(toInstanceDetail(row));
|
|
21198
|
+
last = row;
|
|
21199
|
+
} else {
|
|
21200
|
+
hasMore = true;
|
|
21201
|
+
break;
|
|
21202
|
+
}
|
|
21203
|
+
}
|
|
21204
|
+
return {
|
|
21205
|
+
items,
|
|
21206
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21207
|
+
};
|
|
21208
|
+
}
|
|
21209
|
+
/**
|
|
21210
|
+
* The same findings folded by location: repository, then file within it.
|
|
21211
|
+
*
|
|
21212
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21213
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21214
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21215
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21216
|
+
* filter can name it.
|
|
21217
|
+
*/
|
|
21218
|
+
listFindingLocations(query) {
|
|
21219
|
+
const opts = {
|
|
21220
|
+
severity: query.severity,
|
|
21221
|
+
subtype: query.subtype,
|
|
21222
|
+
providers: query.provider,
|
|
21223
|
+
actions: query.action,
|
|
21224
|
+
statuses: query.status,
|
|
21225
|
+
tools: query.tool,
|
|
21226
|
+
q: query.q
|
|
21227
|
+
};
|
|
21228
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21229
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21230
|
+
let total = 0;
|
|
21231
|
+
for (const row of this.scanFindingRows({
|
|
21232
|
+
sessionId: query.sessionId,
|
|
21233
|
+
from: query.from
|
|
21234
|
+
})) {
|
|
21235
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21236
|
+
total += 1;
|
|
21237
|
+
let files = byRepo.get(row.repo);
|
|
21238
|
+
if (files === void 0) {
|
|
21239
|
+
files = /* @__PURE__ */ new Map();
|
|
21240
|
+
byRepo.set(row.repo, files);
|
|
21241
|
+
}
|
|
21242
|
+
let acc = files.get(row.file);
|
|
21243
|
+
if (acc === void 0) {
|
|
21244
|
+
acc = newLocationAccumulator();
|
|
21245
|
+
files.set(row.file, acc);
|
|
21246
|
+
}
|
|
21247
|
+
addToLocation(acc, row);
|
|
21248
|
+
}
|
|
21249
|
+
let fileCount = 0;
|
|
21250
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21251
|
+
fileCount += files.size;
|
|
21252
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21253
|
+
file: file2,
|
|
21254
|
+
instanceCount: acc.instanceCount,
|
|
21255
|
+
maxSeverity: acc.maxSeverity,
|
|
21256
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21257
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21258
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21259
|
+
})).sort(compareLocationOrder);
|
|
21260
|
+
const rollup = fileRows.reduce(
|
|
21261
|
+
(a, f) => ({
|
|
21262
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21263
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21264
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21265
|
+
}),
|
|
21266
|
+
{
|
|
21267
|
+
instanceCount: 0,
|
|
21268
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21269
|
+
latestDetectedAt: ""
|
|
21270
|
+
}
|
|
21271
|
+
);
|
|
21272
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21273
|
+
const folded = foldGroupStatus(statuses);
|
|
21274
|
+
return {
|
|
21275
|
+
repo,
|
|
21276
|
+
instanceCount: rollup.instanceCount,
|
|
21277
|
+
maxSeverity: rollup.maxSeverity,
|
|
21278
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21279
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21280
|
+
files: fileRows
|
|
21281
|
+
};
|
|
21282
|
+
});
|
|
21283
|
+
repos.sort(compareLocationOrder);
|
|
21284
|
+
return Promise.resolve({
|
|
21285
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21286
|
+
items: repos.slice(0, limit),
|
|
21287
|
+
hasMore: repos.length > limit
|
|
21288
|
+
});
|
|
21289
|
+
}
|
|
21290
|
+
/**
|
|
21291
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21292
|
+
*
|
|
21293
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21294
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21295
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21296
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21297
|
+
* rather than one unbounded result set.
|
|
21298
|
+
*
|
|
21299
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21300
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21301
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21302
|
+
* a window over the whole resolution table once per batch.
|
|
21303
|
+
*
|
|
21304
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21305
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21306
|
+
* dimension — see listFindingInstances.
|
|
21307
|
+
*/
|
|
21308
|
+
*scanFindingRows(scope) {
|
|
21309
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21310
|
+
const params = [];
|
|
21311
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21312
|
+
conditions.push("e.root_session_id = ?");
|
|
21313
|
+
params.push(scope.sessionId);
|
|
21314
|
+
}
|
|
21315
|
+
if (scope.from !== void 0) {
|
|
21316
|
+
conditions.push("e.started_at >= ?");
|
|
21317
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21318
|
+
}
|
|
21319
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21320
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21321
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21322
|
+
e.started_at AS occurred_at,
|
|
21323
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21324
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21325
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21326
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21327
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21328
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21329
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21330
|
+
FROM inspection_findings f
|
|
21331
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21332
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21333
|
+
WHERE ${conditions.join(" AND ")}
|
|
21334
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21335
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21336
|
+
LIMIT ?`;
|
|
21337
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21338
|
+
for (; ; ) {
|
|
21339
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21340
|
+
...params,
|
|
21341
|
+
after.startedAtMs,
|
|
21342
|
+
after.startedAtMs,
|
|
21343
|
+
after.id,
|
|
21344
|
+
SCAN_BATCH_ROWS
|
|
21345
|
+
]);
|
|
21346
|
+
for (const r of rows) {
|
|
21347
|
+
yield {
|
|
21348
|
+
id: r.id,
|
|
21349
|
+
ruleId: r.rule_id,
|
|
21350
|
+
category: r.category,
|
|
21351
|
+
severity: r.severity,
|
|
21352
|
+
maskedMatch: r.masked_match,
|
|
21353
|
+
actionTaken: r.action_taken,
|
|
21354
|
+
confidence: r.confidence,
|
|
21355
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21356
|
+
sourceTool: r.source_tool,
|
|
21357
|
+
repo: r.repo ?? "",
|
|
21358
|
+
file: r.file ?? "",
|
|
21359
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21360
|
+
eventId: r.event_id,
|
|
21361
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21362
|
+
status: deriveInstanceStatus(r)
|
|
21363
|
+
};
|
|
21364
|
+
}
|
|
21365
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21366
|
+
const lastRow = rows[rows.length - 1];
|
|
21367
|
+
if (lastRow === void 0) return;
|
|
21368
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21369
|
+
}
|
|
21370
|
+
}
|
|
20533
21371
|
groupAggregates(withSearchText, scope) {
|
|
20534
21372
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20535
21373
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20790,7 +21628,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20790
21628
|
};
|
|
20791
21629
|
|
|
20792
21630
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20793
|
-
import { createHash as createHash2, randomUUID as
|
|
21631
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20794
21632
|
|
|
20795
21633
|
// ../../packages/persistence/src/semver.ts
|
|
20796
21634
|
function parse3(version2) {
|
|
@@ -20941,7 +21779,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20941
21779
|
let behind = false;
|
|
20942
21780
|
for (const row of rows) {
|
|
20943
21781
|
const params = {
|
|
20944
|
-
id:
|
|
21782
|
+
id: randomUUID3(),
|
|
20945
21783
|
namespace: row.namespace,
|
|
20946
21784
|
packId: row.packId,
|
|
20947
21785
|
version: row.version,
|
|
@@ -20953,7 +21791,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20953
21791
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20954
21792
|
this.upsertAvailableStmt.run({
|
|
20955
21793
|
...params,
|
|
20956
|
-
id:
|
|
21794
|
+
id: randomUUID3(),
|
|
20957
21795
|
recordedBy: meta3?.recordedBy ?? null
|
|
20958
21796
|
});
|
|
20959
21797
|
} else {
|
|
@@ -21276,14 +22114,15 @@ var SqliteInventoryRepository = class {
|
|
|
21276
22114
|
};
|
|
21277
22115
|
|
|
21278
22116
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21279
|
-
import { randomUUID as
|
|
22117
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21280
22118
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21281
22119
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21282
22120
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21283
22121
|
var HARNESS_LABELS = {
|
|
21284
22122
|
claudecode: "Claude Code",
|
|
21285
22123
|
cursor: "Cursor",
|
|
21286
|
-
codex: "Codex"
|
|
22124
|
+
codex: "Codex",
|
|
22125
|
+
antigravity: "Antigravity"
|
|
21287
22126
|
};
|
|
21288
22127
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21289
22128
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21298,6 +22137,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21298
22137
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21299
22138
|
if (t.includes("cursor")) return "cursor";
|
|
21300
22139
|
if (t.includes("codex")) return "codex";
|
|
22140
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21301
22141
|
return null;
|
|
21302
22142
|
}
|
|
21303
22143
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21756,7 +22596,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21756
22596
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21757
22597
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21758
22598
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21759
|
-
).run({ id:
|
|
22599
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21760
22600
|
}
|
|
21761
22601
|
return true;
|
|
21762
22602
|
}
|
|
@@ -21777,7 +22617,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21777
22617
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21778
22618
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21779
22619
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21780
|
-
).run({ id:
|
|
22620
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21781
22621
|
}
|
|
21782
22622
|
this.configRowsCache = void 0;
|
|
21783
22623
|
return "ok";
|
|
@@ -22074,7 +22914,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22074
22914
|
};
|
|
22075
22915
|
|
|
22076
22916
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22077
|
-
import { randomUUID as
|
|
22917
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22078
22918
|
var SqlitePoliciesRepository = class {
|
|
22079
22919
|
constructor(db) {
|
|
22080
22920
|
this.db = db;
|
|
@@ -22109,7 +22949,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22109
22949
|
failOpenTransaction(this.db, () => {
|
|
22110
22950
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22111
22951
|
stmt.run({
|
|
22112
|
-
id:
|
|
22952
|
+
id: randomUUID5(),
|
|
22113
22953
|
target: JSON.stringify({ category }),
|
|
22114
22954
|
action,
|
|
22115
22955
|
now: Date.now()
|
|
@@ -22129,7 +22969,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22129
22969
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22130
22970
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22131
22971
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22132
|
-
).run({ id:
|
|
22972
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22133
22973
|
}
|
|
22134
22974
|
// Caps every global per-category policy currently set to block/redact down
|
|
22135
22975
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22197,7 +23037,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22197
23037
|
};
|
|
22198
23038
|
|
|
22199
23039
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22200
|
-
import { randomUUID as
|
|
23040
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22201
23041
|
var SqliteProjectFilesRepository = class {
|
|
22202
23042
|
constructor(db) {
|
|
22203
23043
|
this.db = db;
|
|
@@ -22229,7 +23069,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22229
23069
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22230
23070
|
for (const file2 of scan2.files) {
|
|
22231
23071
|
this.upsertStmt.run({
|
|
22232
|
-
id:
|
|
23072
|
+
id: randomUUID6(),
|
|
22233
23073
|
projectId,
|
|
22234
23074
|
path: file2.path,
|
|
22235
23075
|
name: file2.name,
|
|
@@ -22243,7 +23083,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22243
23083
|
};
|
|
22244
23084
|
|
|
22245
23085
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22246
|
-
import { randomUUID as
|
|
23086
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22247
23087
|
var SqliteResolutionsRepository = class {
|
|
22248
23088
|
constructor(db, now = () => Date.now()) {
|
|
22249
23089
|
this.db = db;
|
|
@@ -22297,7 +23137,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22297
23137
|
*/
|
|
22298
23138
|
insertResolution(r) {
|
|
22299
23139
|
this.insertStmt.run({
|
|
22300
|
-
id:
|
|
23140
|
+
id: randomUUID7(),
|
|
22301
23141
|
findingKey: r.findingKey,
|
|
22302
23142
|
status: FindingStatus.parse(r.status),
|
|
22303
23143
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22356,13 +23196,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22356
23196
|
this.readStmt = db.prepare(
|
|
22357
23197
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22358
23198
|
);
|
|
23199
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23200
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23201
|
+
);
|
|
23202
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23203
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23204
|
+
);
|
|
22359
23205
|
}
|
|
22360
23206
|
db;
|
|
22361
23207
|
upsertStmt;
|
|
22362
23208
|
readStmt;
|
|
23209
|
+
countQuarantinedStmt;
|
|
23210
|
+
clearQuarantinedStmt;
|
|
22363
23211
|
getVerdict(ruleKey) {
|
|
22364
23212
|
return getRow(this.readStmt, { ruleKey });
|
|
22365
23213
|
}
|
|
23214
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23215
|
+
countQuarantined() {
|
|
23216
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23217
|
+
}
|
|
23218
|
+
/**
|
|
23219
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23220
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23221
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23222
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23223
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23224
|
+
*
|
|
23225
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23226
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23227
|
+
*
|
|
23228
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23229
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23230
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23231
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23232
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23233
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23234
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23235
|
+
* to restore are silently still disabled.
|
|
23236
|
+
*/
|
|
23237
|
+
clearQuarantined() {
|
|
23238
|
+
const before = this.countQuarantined();
|
|
23239
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23240
|
+
this.clearQuarantinedStmt.run();
|
|
23241
|
+
});
|
|
23242
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23243
|
+
}
|
|
22366
23244
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22367
23245
|
failOpenTransaction(this.db, () => {
|
|
22368
23246
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22417,7 +23295,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22417
23295
|
};
|
|
22418
23296
|
|
|
22419
23297
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22420
|
-
import { randomUUID as
|
|
23298
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23299
|
+
function pageLimit(requested, fallback) {
|
|
23300
|
+
if (requested === void 0) return fallback;
|
|
23301
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23302
|
+
}
|
|
23303
|
+
function encodeReuseCursor(payload) {
|
|
23304
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23305
|
+
}
|
|
23306
|
+
function decodeReuseCursor(cursor) {
|
|
23307
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23308
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23309
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23310
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23311
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23312
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23313
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23314
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23315
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23316
|
+
}
|
|
23317
|
+
return null;
|
|
23318
|
+
}
|
|
23319
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23320
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23321
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23322
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23323
|
+
function toSighting(row) {
|
|
23324
|
+
return {
|
|
23325
|
+
location: row.location,
|
|
23326
|
+
kind: row.kind,
|
|
23327
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23328
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23329
|
+
};
|
|
23330
|
+
}
|
|
22421
23331
|
var SELECT_COLUMNS = `
|
|
22422
23332
|
pointer_id AS pointerId,
|
|
22423
23333
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22601,39 +23511,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22601
23511
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22602
23512
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22603
23513
|
).run({
|
|
22604
|
-
id:
|
|
23514
|
+
id: randomUUID8(),
|
|
22605
23515
|
pointerId: entry.pointerId,
|
|
22606
23516
|
location: entry.location,
|
|
22607
23517
|
kind: entry.kind,
|
|
22608
23518
|
now
|
|
22609
23519
|
});
|
|
22610
23520
|
}
|
|
22611
|
-
|
|
23521
|
+
/**
|
|
23522
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23523
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23524
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23525
|
+
*
|
|
23526
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23527
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23528
|
+
*/
|
|
23529
|
+
sightingsFor(pointerIds) {
|
|
23530
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23531
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22612
23532
|
const rows = allRows(
|
|
22613
23533
|
this.db.prepare(
|
|
22614
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22615
|
-
|
|
23534
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23535
|
+
FROM secret_vault_sighting
|
|
23536
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23537
|
+
ORDER BY last_seen DESC`
|
|
22616
23538
|
),
|
|
22617
|
-
|
|
23539
|
+
pointerIds
|
|
22618
23540
|
);
|
|
23541
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23542
|
+
return byPointer;
|
|
23543
|
+
}
|
|
23544
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23545
|
+
toInventoryEntries(rows) {
|
|
23546
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22619
23547
|
return rows.map((r) => ({
|
|
22620
|
-
|
|
22621
|
-
|
|
23548
|
+
pointerId: r.pointer_id,
|
|
23549
|
+
category: r.category,
|
|
23550
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23551
|
+
maskedMatch: r.masked_match,
|
|
23552
|
+
occurrences: r.occurrence_count,
|
|
22622
23553
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22623
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23554
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23555
|
+
revealGrantId: r.grant_id,
|
|
23556
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22624
23557
|
}));
|
|
22625
23558
|
}
|
|
22626
23559
|
/**
|
|
22627
|
-
* The dashboard inventory
|
|
22628
|
-
* its sightings and the active
|
|
22629
|
-
* Raw-free by construction — neither
|
|
22630
|
-
* columns are selected.
|
|
23560
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23561
|
+
* value's descriptor data joined with its sightings and the active
|
|
23562
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23563
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23564
|
+
*
|
|
23565
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23566
|
+
* sees never depends on how far they have paged.
|
|
22631
23567
|
*/
|
|
22632
|
-
listInventory(now = Date.now()) {
|
|
23568
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23569
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23570
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23571
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22633
23572
|
const rows = allRows(
|
|
22634
23573
|
this.db.prepare(
|
|
22635
|
-
`SELECT
|
|
22636
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23574
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22637
23575
|
(SELECT e.id FROM exceptions e
|
|
22638
23576
|
WHERE e.rule_id = v.rule_id
|
|
22639
23577
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22641,45 +23579,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22641
23579
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22642
23580
|
LIMIT 1) AS grant_id
|
|
22643
23581
|
FROM secret_vault v
|
|
22644
|
-
|
|
23582
|
+
${where}
|
|
23583
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23584
|
+
LIMIT :limit`
|
|
22645
23585
|
),
|
|
22646
|
-
{
|
|
23586
|
+
bindParams({
|
|
23587
|
+
now,
|
|
23588
|
+
limit: limit + 1,
|
|
23589
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23590
|
+
})
|
|
22647
23591
|
);
|
|
22648
|
-
|
|
22649
|
-
|
|
22650
|
-
|
|
22651
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
23592
|
+
const hasMore = rows.length > limit;
|
|
23593
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23594
|
+
const last = page[page.length - 1];
|
|
23595
|
+
return {
|
|
23596
|
+
totals: { values: this.countEntries() },
|
|
23597
|
+
items: this.toInventoryEntries(page),
|
|
23598
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23599
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23600
|
+
};
|
|
23601
|
+
}
|
|
23602
|
+
/**
|
|
23603
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23604
|
+
* than one location — most-reused first, one page at a time.
|
|
23605
|
+
*
|
|
23606
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23607
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23608
|
+
* under-report exactly the values a reader most needs to see.
|
|
23609
|
+
*/
|
|
23610
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23611
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23612
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23613
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23614
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23615
|
+
const rows = allRows(
|
|
23616
|
+
this.db.prepare(
|
|
23617
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23618
|
+
(SELECT e.id FROM exceptions e
|
|
23619
|
+
WHERE e.rule_id = v.rule_id
|
|
23620
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23621
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23622
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23623
|
+
LIMIT 1) AS grant_id
|
|
23624
|
+
FROM secret_vault v
|
|
23625
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23626
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23627
|
+
LIMIT :limit`
|
|
23628
|
+
),
|
|
23629
|
+
bindParams({
|
|
23630
|
+
now,
|
|
23631
|
+
limit: limit + 1,
|
|
23632
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23633
|
+
})
|
|
23634
|
+
);
|
|
23635
|
+
const hasMore = rows.length > limit;
|
|
23636
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23637
|
+
const last = page[page.length - 1];
|
|
23638
|
+
return {
|
|
23639
|
+
totals: { reused: this.countReused() },
|
|
23640
|
+
items: this.toInventoryEntries(page),
|
|
23641
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23642
|
+
};
|
|
22659
23643
|
}
|
|
22660
23644
|
/**
|
|
22661
|
-
* The de-reference trail, newest first. By default the
|
|
22662
|
-
* reasons (display, view-render) are hidden and counted
|
|
22663
|
-
* that matter as a signal are the model crossings, and
|
|
22664
|
-
* render noise would defeat the audit's purpose.
|
|
23645
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23646
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23647
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23648
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23649
|
+
*
|
|
23650
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23651
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23652
|
+
* the reader pages.
|
|
22665
23653
|
*/
|
|
22666
|
-
listDerefs(
|
|
22667
|
-
const limit =
|
|
22668
|
-
const
|
|
23654
|
+
listDerefs(query = {}) {
|
|
23655
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23656
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23657
|
+
const conditions = [];
|
|
23658
|
+
if (query.includeBatched !== true) {
|
|
23659
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23660
|
+
}
|
|
23661
|
+
if (cursor !== null) {
|
|
23662
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23663
|
+
}
|
|
23664
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22669
23665
|
const rows = allRows(
|
|
22670
23666
|
this.db.prepare(
|
|
22671
23667
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22672
23668
|
FROM secret_vault_deref ${where}
|
|
22673
|
-
ORDER BY at DESC,
|
|
23669
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22674
23670
|
),
|
|
22675
|
-
{
|
|
23671
|
+
bindParams({
|
|
23672
|
+
limit: limit + 1,
|
|
23673
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23674
|
+
})
|
|
22676
23675
|
);
|
|
22677
|
-
const
|
|
23676
|
+
const hasMore = rows.length > limit;
|
|
23677
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23678
|
+
const last = page[page.length - 1];
|
|
23679
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22678
23680
|
this.db,
|
|
22679
23681
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22680
23682
|
);
|
|
22681
23683
|
return {
|
|
22682
|
-
|
|
23684
|
+
items: page.map((r) => ({
|
|
22683
23685
|
id: r.id,
|
|
22684
23686
|
pointerId: r.pointer_id,
|
|
22685
23687
|
at: new Date(r.at).toISOString(),
|
|
@@ -22689,12 +23691,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22689
23691
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22690
23692
|
pointerCount: r.pointer_count
|
|
22691
23693
|
})),
|
|
23694
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22692
23695
|
hiddenBatched
|
|
22693
23696
|
};
|
|
22694
23697
|
}
|
|
22695
23698
|
countEntries() {
|
|
22696
23699
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22697
23700
|
}
|
|
23701
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23702
|
+
countReused() {
|
|
23703
|
+
return countScalar(
|
|
23704
|
+
this.db,
|
|
23705
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23706
|
+
);
|
|
23707
|
+
}
|
|
22698
23708
|
};
|
|
22699
23709
|
|
|
22700
23710
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22709,7 +23719,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22709
23719
|
var SCAN_COVERAGE = [
|
|
22710
23720
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22711
23721
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22712
|
-
{ provider: "codex", coverage:
|
|
23722
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23723
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23724
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22713
23725
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22714
23726
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22715
23727
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23042,7 +24054,7 @@ var SqliteSecurityRepository = class {
|
|
|
23042
24054
|
};
|
|
23043
24055
|
|
|
23044
24056
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23045
|
-
import { randomUUID as
|
|
24057
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23046
24058
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23047
24059
|
var IN_CHUNK = 500;
|
|
23048
24060
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23298,7 +24310,7 @@ var SqliteSharesRepository = class {
|
|
|
23298
24310
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23299
24311
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23300
24312
|
).run({
|
|
23301
|
-
id:
|
|
24313
|
+
id: randomUUID9(),
|
|
23302
24314
|
destinationId,
|
|
23303
24315
|
host: dest.host,
|
|
23304
24316
|
decision,
|
|
@@ -23447,7 +24459,7 @@ var SqliteSharesRepository = class {
|
|
|
23447
24459
|
let destinationId = destIds.get(hit.host);
|
|
23448
24460
|
if (destinationId === void 0) {
|
|
23449
24461
|
destStmt.run({
|
|
23450
|
-
id:
|
|
24462
|
+
id: randomUUID9(),
|
|
23451
24463
|
kind: hit.kind,
|
|
23452
24464
|
name: hit.name,
|
|
23453
24465
|
host: hit.host,
|
|
@@ -23463,7 +24475,7 @@ var SqliteSharesRepository = class {
|
|
|
23463
24475
|
let endpointId = endpointIds.get(endpointKey);
|
|
23464
24476
|
if (endpointId === void 0) {
|
|
23465
24477
|
endpointStmt.run({
|
|
23466
|
-
id:
|
|
24478
|
+
id: randomUUID9(),
|
|
23467
24479
|
destinationId,
|
|
23468
24480
|
method: hit.method,
|
|
23469
24481
|
transport: hit.transport,
|
|
@@ -23476,7 +24488,7 @@ var SqliteSharesRepository = class {
|
|
|
23476
24488
|
endpointIds.set(endpointKey, endpointId);
|
|
23477
24489
|
}
|
|
23478
24490
|
siteStmt.run({
|
|
23479
|
-
id:
|
|
24491
|
+
id: randomUUID9(),
|
|
23480
24492
|
endpointId,
|
|
23481
24493
|
project: input.project,
|
|
23482
24494
|
projectKey: input.projectKey,
|
|
@@ -23841,6 +24853,9 @@ function purgeSampleData(db) {
|
|
|
23841
24853
|
}
|
|
23842
24854
|
|
|
23843
24855
|
// ../../packages/persistence/src/database.ts
|
|
24856
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24857
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24858
|
+
);
|
|
23844
24859
|
function linkHost(input, hostId) {
|
|
23845
24860
|
return hostId ? { ...input, hostId } : input;
|
|
23846
24861
|
}
|
|
@@ -23862,21 +24877,34 @@ function openWithPragmas(file2) {
|
|
|
23862
24877
|
}
|
|
23863
24878
|
return db;
|
|
23864
24879
|
}
|
|
23865
|
-
function backupLegacyStore(file2) {
|
|
23866
|
-
|
|
23867
|
-
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
24880
|
+
function backupLegacyStore(db, file2) {
|
|
24881
|
+
reapStalePartials(file2);
|
|
24882
|
+
const backup = backupPath(file2, "legacy");
|
|
24883
|
+
let snapshotted = false;
|
|
24884
|
+
let snapshotError;
|
|
24885
|
+
try {
|
|
24886
|
+
snapshotStore(db, backup);
|
|
24887
|
+
snapshotted = true;
|
|
24888
|
+
} catch (error51) {
|
|
24889
|
+
snapshotError = error51;
|
|
24890
|
+
} finally {
|
|
24891
|
+
db.close();
|
|
24892
|
+
}
|
|
24893
|
+
if (!snapshotted) {
|
|
24894
|
+
akaWarn(
|
|
24895
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24896
|
+
);
|
|
24897
|
+
moveStoreAside(file2, backup);
|
|
24898
|
+
return backup;
|
|
23871
24899
|
}
|
|
24900
|
+
discardStore(file2, backup);
|
|
23872
24901
|
return backup;
|
|
23873
24902
|
}
|
|
23874
24903
|
function openAndInitialize(file2) {
|
|
23875
24904
|
let db = openWithPragmas(file2);
|
|
23876
24905
|
try {
|
|
23877
24906
|
if (isForeignSqliteLineage(db)) {
|
|
23878
|
-
db
|
|
23879
|
-
const backup = backupLegacyStore(file2);
|
|
24907
|
+
const backup = backupLegacyStore(db, file2);
|
|
23880
24908
|
db = openWithPragmas(file2);
|
|
23881
24909
|
akaWarn(
|
|
23882
24910
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23920,7 +24948,7 @@ function openAndInitialize(file2) {
|
|
|
23920
24948
|
}
|
|
23921
24949
|
function openLocalDatabase(dir) {
|
|
23922
24950
|
ensureDataDirSync(dir);
|
|
23923
|
-
const file2 =
|
|
24951
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23924
24952
|
const {
|
|
23925
24953
|
db,
|
|
23926
24954
|
events,
|
|
@@ -24037,7 +25065,7 @@ function openLocalDatabase(dir) {
|
|
|
24037
25065
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24038
25066
|
if (!definitionId) continue;
|
|
24039
25067
|
inspectionFindings.insertFinding({
|
|
24040
|
-
id:
|
|
25068
|
+
id: randomUUID10(),
|
|
24041
25069
|
auditEventId: record2.scanEvent.id,
|
|
24042
25070
|
inspectionDefinitionId: definitionId,
|
|
24043
25071
|
span: finding.span,
|
|
@@ -24143,7 +25171,9 @@ function openLocalDatabase(dir) {
|
|
|
24143
25171
|
transaction,
|
|
24144
25172
|
close: () => {
|
|
24145
25173
|
db.close();
|
|
24146
|
-
}
|
|
25174
|
+
},
|
|
25175
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25176
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24147
25177
|
};
|
|
24148
25178
|
}
|
|
24149
25179
|
|
|
@@ -24167,18 +25197,32 @@ var UserGrantPolicyProvider = class {
|
|
|
24167
25197
|
}
|
|
24168
25198
|
};
|
|
24169
25199
|
|
|
25200
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25201
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25202
|
+
import {
|
|
25203
|
+
closeSync,
|
|
25204
|
+
existsSync as existsSync2,
|
|
25205
|
+
openSync,
|
|
25206
|
+
readFileSync,
|
|
25207
|
+
rmSync as rmSync3,
|
|
25208
|
+
statSync as statSync2,
|
|
25209
|
+
writeFileSync as writeFileSync2
|
|
25210
|
+
} from "fs";
|
|
25211
|
+
import { hostname as hostname3 } from "os";
|
|
25212
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25213
|
+
|
|
24170
25214
|
// ../../packages/persistence/src/finding-key.ts
|
|
24171
25215
|
import { createHash as createHash3 } from "crypto";
|
|
24172
25216
|
|
|
24173
25217
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24174
25218
|
import { createHmac, randomBytes } from "crypto";
|
|
24175
|
-
import { existsSync as
|
|
24176
|
-
import { join as
|
|
25219
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25220
|
+
import { join as join3 } from "path";
|
|
24177
25221
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24178
|
-
var
|
|
25222
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24179
25223
|
var KEY_MATERIAL_BYTES = 32;
|
|
24180
25224
|
function keyFilePath(dataDir2) {
|
|
24181
|
-
return
|
|
25225
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24182
25226
|
}
|
|
24183
25227
|
function parseKeyFile(raw) {
|
|
24184
25228
|
const parsed = JSON.parse(raw);
|
|
@@ -24216,8 +25260,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
24216
25260
|
}
|
|
24217
25261
|
};
|
|
24218
25262
|
function storedKeyVersionFloor(dataDir2) {
|
|
24219
|
-
const file2 =
|
|
24220
|
-
if (!
|
|
25263
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25264
|
+
if (!existsSync3(file2)) return 0;
|
|
24221
25265
|
let db;
|
|
24222
25266
|
try {
|
|
24223
25267
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -24242,18 +25286,36 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
24242
25286
|
db?.close();
|
|
24243
25287
|
}
|
|
24244
25288
|
}
|
|
24245
|
-
function
|
|
25289
|
+
function serializeKey(key) {
|
|
25290
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
25291
|
+
}
|
|
25292
|
+
function createKeyFile(dataDir2, key) {
|
|
24246
25293
|
ensureDataDirSync(dataDir2);
|
|
24247
25294
|
const file2 = keyFilePath(dataDir2);
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
25295
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
25296
|
+
`)) return key;
|
|
25297
|
+
const winner = readFingerprintKey(dataDir2);
|
|
25298
|
+
if (winner) {
|
|
25299
|
+
tightenFile(file2);
|
|
25300
|
+
return winner;
|
|
25301
|
+
}
|
|
25302
|
+
const occupant = classifyOccupant(file2);
|
|
25303
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
25304
|
+
}
|
|
25305
|
+
function occupantMessage(file2, kind) {
|
|
25306
|
+
switch (kind) {
|
|
25307
|
+
case "symlink":
|
|
25308
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
25309
|
+
case "gone":
|
|
25310
|
+
return "exception key file was removed while it was being created";
|
|
25311
|
+
case "unknown":
|
|
25312
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
25313
|
+
}
|
|
24252
25314
|
}
|
|
24253
25315
|
function readFingerprintKey(dataDir2) {
|
|
24254
25316
|
let raw;
|
|
24255
25317
|
try {
|
|
24256
|
-
raw =
|
|
25318
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24257
25319
|
} catch (err) {
|
|
24258
25320
|
if (err.code === "ENOENT") return null;
|
|
24259
25321
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24266,7 +25328,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
24266
25328
|
tightenFile(keyFilePath(dataDir2));
|
|
24267
25329
|
return existing;
|
|
24268
25330
|
}
|
|
24269
|
-
return
|
|
25331
|
+
return createKeyFile(dataDir2, {
|
|
24270
25332
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
24271
25333
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
24272
25334
|
});
|
|
@@ -24279,21 +25341,21 @@ function fingerprintValue(key, raw) {
|
|
|
24279
25341
|
import { renameSync as renameSync3 } from "fs";
|
|
24280
25342
|
import { mkdir } from "fs/promises";
|
|
24281
25343
|
import { homedir } from "os";
|
|
24282
|
-
import { join as
|
|
25344
|
+
import { join as join4 } from "path";
|
|
24283
25345
|
function defaultDataDir() {
|
|
24284
|
-
return
|
|
25346
|
+
return join4(homedir(), ".aka");
|
|
24285
25347
|
}
|
|
24286
25348
|
function settingsDir(base = defaultDataDir()) {
|
|
24287
|
-
return
|
|
25349
|
+
return join4(base, "settings");
|
|
24288
25350
|
}
|
|
24289
25351
|
function dataDir(base = defaultDataDir()) {
|
|
24290
|
-
return
|
|
25352
|
+
return join4(base, "data");
|
|
24291
25353
|
}
|
|
24292
25354
|
function dbPath(base = defaultDataDir()) {
|
|
24293
|
-
return
|
|
25355
|
+
return join4(dataDir(base), "aka.db");
|
|
24294
25356
|
}
|
|
24295
25357
|
function keysDir(base = defaultDataDir()) {
|
|
24296
|
-
return
|
|
25358
|
+
return join4(base, "keys");
|
|
24297
25359
|
}
|
|
24298
25360
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24299
25361
|
ensureDataDirSync(dir);
|
|
@@ -24306,8 +25368,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24306
25368
|
for (const { name, dest } of moves) {
|
|
24307
25369
|
try {
|
|
24308
25370
|
ensureDataDirSync(dest);
|
|
24309
|
-
const moved =
|
|
24310
|
-
renameSync3(
|
|
25371
|
+
const moved = join4(dest, name);
|
|
25372
|
+
renameSync3(join4(base, name), moved);
|
|
24311
25373
|
tightenFile(moved);
|
|
24312
25374
|
} catch {
|
|
24313
25375
|
}
|
|
@@ -24315,10 +25377,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24315
25377
|
}
|
|
24316
25378
|
|
|
24317
25379
|
// ../../packages/persistence/src/settings.ts
|
|
24318
|
-
import { readFileSync as
|
|
24319
|
-
import { join as
|
|
25380
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25381
|
+
import { join as join5 } from "path";
|
|
25382
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24320
25383
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24321
|
-
const record2 = readJson(
|
|
25384
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24322
25385
|
if (!record2) return defaultWorkspaceSettings();
|
|
24323
25386
|
try {
|
|
24324
25387
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24329,7 +25392,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24329
25392
|
function readJson(file2) {
|
|
24330
25393
|
let text;
|
|
24331
25394
|
try {
|
|
24332
|
-
text =
|
|
25395
|
+
text = readFileSync3(file2, "utf8");
|
|
24333
25396
|
} catch {
|
|
24334
25397
|
return null;
|
|
24335
25398
|
}
|
|
@@ -24450,13 +25513,18 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24450
25513
|
import {
|
|
24451
25514
|
chmodSync as chmodSync2,
|
|
24452
25515
|
mkdirSync as mkdirSync2,
|
|
24453
|
-
readFileSync as
|
|
25516
|
+
readFileSync as readFileSync4,
|
|
24454
25517
|
renameSync as renameSync4,
|
|
24455
|
-
rmSync as
|
|
24456
|
-
statSync,
|
|
24457
|
-
writeFileSync as
|
|
25518
|
+
rmSync as rmSync4,
|
|
25519
|
+
statSync as statSync3,
|
|
25520
|
+
writeFileSync as writeFileSync3
|
|
24458
25521
|
} from "fs";
|
|
24459
|
-
import { join as
|
|
25522
|
+
import { join as join6 } from "path";
|
|
25523
|
+
var VAULT_OCCUPANT_REASON = {
|
|
25524
|
+
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25525
|
+
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
25526
|
+
unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
|
|
25527
|
+
};
|
|
24460
25528
|
var VaultKeyEpochMissingError = class extends Error {
|
|
24461
25529
|
version;
|
|
24462
25530
|
constructor(version2) {
|
|
@@ -24549,28 +25617,28 @@ function claimRotationLock(lock, owner) {
|
|
|
24549
25617
|
throw asError(err);
|
|
24550
25618
|
}
|
|
24551
25619
|
try {
|
|
24552
|
-
|
|
25620
|
+
writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
|
|
24553
25621
|
`, { mode: DATA_FILE_MODE });
|
|
24554
25622
|
return true;
|
|
24555
25623
|
} catch (err) {
|
|
24556
|
-
|
|
25624
|
+
rmSync4(lock, { recursive: true, force: true });
|
|
24557
25625
|
throw asError(err);
|
|
24558
25626
|
}
|
|
24559
25627
|
}
|
|
24560
25628
|
function acquireRotationLock(keysDir2) {
|
|
24561
|
-
const lock =
|
|
25629
|
+
const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
24562
25630
|
const owner = randomBytes2(16).toString("hex");
|
|
24563
25631
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
24564
25632
|
let held;
|
|
24565
25633
|
try {
|
|
24566
|
-
held =
|
|
25634
|
+
held = statSync3(lock);
|
|
24567
25635
|
} catch {
|
|
24568
25636
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24569
25637
|
}
|
|
24570
25638
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
24571
25639
|
const aside = `${lock}.stale.${owner}`;
|
|
24572
25640
|
try {
|
|
24573
|
-
const now =
|
|
25641
|
+
const now = statSync3(lock);
|
|
24574
25642
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
24575
25643
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24576
25644
|
}
|
|
@@ -24579,17 +25647,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
24579
25647
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
24580
25648
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
24581
25649
|
}
|
|
24582
|
-
|
|
25650
|
+
rmSync4(aside, { recursive: true, force: true });
|
|
24583
25651
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
24584
25652
|
return { lock, owner };
|
|
24585
25653
|
}
|
|
24586
25654
|
function releaseRotationLock(lease) {
|
|
24587
25655
|
try {
|
|
24588
|
-
if (
|
|
25656
|
+
if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
24589
25657
|
} catch {
|
|
24590
25658
|
return;
|
|
24591
25659
|
}
|
|
24592
|
-
|
|
25660
|
+
rmSync4(lease.lock, { recursive: true, force: true });
|
|
24593
25661
|
}
|
|
24594
25662
|
function withRotationLock(keysDir2, work) {
|
|
24595
25663
|
ensureDataDirSync(keysDir2);
|
|
@@ -24606,7 +25674,7 @@ var FileKeyProvider = class {
|
|
|
24606
25674
|
this.#keysDir = keysDir2;
|
|
24607
25675
|
}
|
|
24608
25676
|
get filePath() {
|
|
24609
|
-
return
|
|
25677
|
+
return join6(this.#keysDir, VAULT_KEY_FILENAME);
|
|
24610
25678
|
}
|
|
24611
25679
|
loadOrCreate() {
|
|
24612
25680
|
return asAsync(() => {
|
|
@@ -24636,7 +25704,7 @@ var FileKeyProvider = class {
|
|
|
24636
25704
|
#read() {
|
|
24637
25705
|
let raw;
|
|
24638
25706
|
try {
|
|
24639
|
-
raw =
|
|
25707
|
+
raw = readFileSync4(this.filePath, "utf8");
|
|
24640
25708
|
} catch (err) {
|
|
24641
25709
|
if (err.code === "ENOENT") return null;
|
|
24642
25710
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24644,34 +25712,32 @@ var FileKeyProvider = class {
|
|
|
24644
25712
|
return parseKeyring(raw);
|
|
24645
25713
|
}
|
|
24646
25714
|
/**
|
|
24647
|
-
* First mint: the keyring is
|
|
24648
|
-
*
|
|
24649
|
-
*
|
|
24650
|
-
*
|
|
24651
|
-
*
|
|
24652
|
-
*
|
|
24653
|
-
*
|
|
24654
|
-
*
|
|
25715
|
+
* First mint: the keyring is CREATED, never replaced, so two processes racing
|
|
25716
|
+
* a fresh machine cannot each mint a different epoch 1 — with tmp + rename
|
|
25717
|
+
* the loser's replace would orphan everything the winner had already sealed.
|
|
25718
|
+
* The loser re-reads and adopts the winner's keyring; it minted nothing.
|
|
25719
|
+
*
|
|
25720
|
+
* `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
|
|
25721
|
+
* at the final path, so the keyring never exists at zero length: a reader —
|
|
25722
|
+
* including the loser, re-reading in order to adopt — sees the file absent or
|
|
25723
|
+
* whole, and never mistakes a live keyring for a corrupt one. A corrupt file
|
|
25724
|
+
* still throws from the parse and is never re-minted over.
|
|
24655
25725
|
*/
|
|
24656
25726
|
#createExclusive() {
|
|
24657
25727
|
ensureDataDirSync(this.#keysDir);
|
|
24658
25728
|
const keyring = mintKeyring();
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
if (!winner) {
|
|
24669
|
-
throw new Error("vault: key file vanished during first mint", { cause: err });
|
|
24670
|
-
}
|
|
24671
|
-
return winner;
|
|
25729
|
+
if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
25730
|
+
`)) return keyring;
|
|
25731
|
+
const winner = this.#read();
|
|
25732
|
+
if (!winner) {
|
|
25733
|
+
const occupant = classifyOccupant(this.filePath);
|
|
25734
|
+
throw new KeyUnclaimableError(
|
|
25735
|
+
`vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
|
|
25736
|
+
occupant.cause
|
|
25737
|
+
);
|
|
24672
25738
|
}
|
|
24673
25739
|
tightenFileMode(this.filePath);
|
|
24674
|
-
return
|
|
25740
|
+
return winner;
|
|
24675
25741
|
}
|
|
24676
25742
|
/**
|
|
24677
25743
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
@@ -24682,7 +25748,7 @@ var FileKeyProvider = class {
|
|
|
24682
25748
|
ensureDataDirSync(this.#keysDir);
|
|
24683
25749
|
const file2 = this.filePath;
|
|
24684
25750
|
const tmp = `${file2}.tmp`;
|
|
24685
|
-
|
|
25751
|
+
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
24686
25752
|
`, { mode: DATA_FILE_MODE });
|
|
24687
25753
|
renameSync4(tmp, file2);
|
|
24688
25754
|
tightenFileMode(file2);
|
|
@@ -24808,7 +25874,7 @@ function createKeyProvider(custody, keysDir2) {
|
|
|
24808
25874
|
}
|
|
24809
25875
|
|
|
24810
25876
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24811
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25877
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24812
25878
|
var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
|
|
24813
25879
|
var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
|
|
24814
25880
|
var VAULT_PURGE_POINTER_ID = "*";
|
|
@@ -24835,14 +25901,12 @@ function parsePointer(token) {
|
|
|
24835
25901
|
var SecretVault = class {
|
|
24836
25902
|
#repo;
|
|
24837
25903
|
#keys;
|
|
24838
|
-
#fingerprintKey;
|
|
24839
25904
|
#isConsented;
|
|
24840
25905
|
#verifyGrant;
|
|
24841
25906
|
#now;
|
|
24842
25907
|
constructor(deps) {
|
|
24843
25908
|
this.#repo = deps.repo;
|
|
24844
25909
|
this.#keys = deps.keys;
|
|
24845
|
-
this.#fingerprintKey = deps.fingerprintKey;
|
|
24846
25910
|
this.#isConsented = deps.isConsented;
|
|
24847
25911
|
this.#verifyGrant = deps.verifyGrant;
|
|
24848
25912
|
this.#now = deps.now ?? (() => Date.now());
|
|
@@ -24851,10 +25915,23 @@ var SecretVault = class {
|
|
|
24851
25915
|
* Store a value and return the pointer that stands for it. The same value
|
|
24852
25916
|
* always yields the same pointer on this machine — one row, one pointer id,
|
|
24853
25917
|
* one category — which is what makes dedup and reuse counting work.
|
|
25918
|
+
*
|
|
25919
|
+
* `fingerprintKey` is the exception-key epoch this value's fingerprint is
|
|
25920
|
+
* derived under — a different key from the vault's, with different rotation
|
|
25921
|
+
* semantics. It is a parameter of the WRITE rather than a constructor dep,
|
|
25922
|
+
* and a thunk rather than a value, so that the only way to reach a key is to
|
|
25923
|
+
* store something: a read-only caller never names it, and a caller whose
|
|
25924
|
+
* source mints on absence mints only once consent has actually opened the
|
|
25925
|
+
* write. `refreshFingerprints` takes its key the same way, for the same
|
|
25926
|
+
* reason.
|
|
25927
|
+
*
|
|
25928
|
+
* Resolved once per call, so the fingerprint and the version it is recorded
|
|
25929
|
+
* under can never come from two different epochs.
|
|
24854
25930
|
*/
|
|
24855
|
-
async tokenize(raw, meta3) {
|
|
25931
|
+
async tokenize(raw, meta3, fingerprintKey) {
|
|
24856
25932
|
if (!this.#isConsented()) return CONSENT_ABSENT;
|
|
24857
|
-
const
|
|
25933
|
+
const fpKey = fingerprintKey();
|
|
25934
|
+
const valueFingerprint = fingerprintValue(fpKey, raw);
|
|
24858
25935
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
24859
25936
|
const now = this.#now();
|
|
24860
25937
|
if (existing) {
|
|
@@ -24870,7 +25947,7 @@ var SecretVault = class {
|
|
|
24870
25947
|
{
|
|
24871
25948
|
pointerId: base32Encode(pointerId),
|
|
24872
25949
|
valueFingerprint,
|
|
24873
|
-
fingerprintKeyVersion:
|
|
25950
|
+
fingerprintKeyVersion: fpKey.version,
|
|
24874
25951
|
keyVersion: version2,
|
|
24875
25952
|
// Recorded so the row stays OPENABLE if the wire-format constant ever
|
|
24876
25953
|
// moves: it is part of this row's AEAD AAD. It is not a tag input —
|
|
@@ -25114,7 +26191,7 @@ var SecretVault = class {
|
|
|
25114
26191
|
purgeVault() {
|
|
25115
26192
|
const destroyed = this.#repo.purgeAll();
|
|
25116
26193
|
this.#repo.recordDeref({
|
|
25117
|
-
id:
|
|
26194
|
+
id: randomUUID12(),
|
|
25118
26195
|
pointerId: VAULT_PURGE_POINTER_ID,
|
|
25119
26196
|
at: this.#now(),
|
|
25120
26197
|
target: "human",
|
|
@@ -25183,7 +26260,7 @@ var SecretVault = class {
|
|
|
25183
26260
|
}
|
|
25184
26261
|
#audit(pointerId, opts, outcome) {
|
|
25185
26262
|
this.#repo.recordDeref({
|
|
25186
|
-
id:
|
|
26263
|
+
id: randomUUID12(),
|
|
25187
26264
|
pointerId,
|
|
25188
26265
|
at: this.#now(),
|
|
25189
26266
|
target: opts.target,
|
|
@@ -25198,8 +26275,8 @@ var SecretVault = class {
|
|
|
25198
26275
|
};
|
|
25199
26276
|
|
|
25200
26277
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
25201
|
-
import { existsSync as
|
|
25202
|
-
import { join as
|
|
26278
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
26279
|
+
import { join as join7 } from "path";
|
|
25203
26280
|
|
|
25204
26281
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
25205
26282
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -25250,11 +26327,11 @@ function resolveProvider() {
|
|
|
25250
26327
|
}
|
|
25251
26328
|
|
|
25252
26329
|
// ../../packages/plugin-sdk/src/config.ts
|
|
25253
|
-
function loadConfig(base = defaultDataDir()) {
|
|
26330
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
25254
26331
|
try {
|
|
25255
26332
|
ensureLayoutDirSync(base);
|
|
25256
|
-
const settingsFile =
|
|
25257
|
-
if (
|
|
26333
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
26334
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
25258
26335
|
} catch {
|
|
25259
26336
|
}
|
|
25260
26337
|
migrateLegacyLayout(base);
|
|
@@ -25265,21 +26342,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
25265
26342
|
dbPath: dbPath(base),
|
|
25266
26343
|
settingsDir: settingsDir(base),
|
|
25267
26344
|
onboarded: settings.onboardedAt != null,
|
|
25268
|
-
provider: resolveProviderSafe()
|
|
26345
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
25269
26346
|
};
|
|
25270
26347
|
}
|
|
25271
|
-
function resolveProviderSafe() {
|
|
26348
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
25272
26349
|
try {
|
|
25273
|
-
return
|
|
26350
|
+
return resolveProviderFn();
|
|
25274
26351
|
} catch {
|
|
25275
26352
|
return { provider: "anthropic" };
|
|
25276
26353
|
}
|
|
25277
26354
|
}
|
|
25278
26355
|
|
|
25279
26356
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
25280
|
-
import { readdirSync, readFileSync as
|
|
26357
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
25281
26358
|
import { homedir as homedir2 } from "os";
|
|
25282
|
-
import { basename as
|
|
26359
|
+
import { basename as basename3, join as join10 } from "path";
|
|
25283
26360
|
|
|
25284
26361
|
// ../../packages/detections/src/egress/registry.ts
|
|
25285
26362
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -27823,7 +28900,7 @@ var gcp_service_account_default = {
|
|
|
27823
28900
|
severity: "critical",
|
|
27824
28901
|
matcher: {
|
|
27825
28902
|
type: "regex",
|
|
27826
|
-
pattern: "
|
|
28903
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27827
28904
|
flags: "g"
|
|
27828
28905
|
},
|
|
27829
28906
|
examples: [
|
|
@@ -28190,37 +29267,68 @@ function registerBundledPacks() {
|
|
|
28190
29267
|
}
|
|
28191
29268
|
|
|
28192
29269
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
28193
|
-
import { existsSync as
|
|
28194
|
-
import { basename, dirname, isAbsolute, join as
|
|
29270
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29271
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
28195
29272
|
|
|
28196
29273
|
// ../../packages/plugin-sdk/src/events.ts
|
|
28197
|
-
import { createHash as createHash4, randomUUID as
|
|
29274
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
29275
|
+
|
|
29276
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29277
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29278
|
+
import { fileURLToPath } from "url";
|
|
29279
|
+
import { Worker } from "worker_threads";
|
|
28198
29280
|
|
|
28199
29281
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
28200
|
-
import { arch, hostname as
|
|
29282
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
28201
29283
|
|
|
28202
29284
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
28203
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
28204
|
-
import { join as
|
|
29285
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
29286
|
+
import { join as join11 } from "path";
|
|
28205
29287
|
|
|
28206
29288
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
28207
|
-
import { readdirSync as
|
|
28208
|
-
import { basename as
|
|
29289
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
29290
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
28209
29291
|
|
|
28210
29292
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
28211
29293
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
28212
|
-
import { existsSync as
|
|
28213
|
-
import { basename as
|
|
29294
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
29295
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
29296
|
+
|
|
29297
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
29298
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
29299
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
29300
|
+
return v;
|
|
29301
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
29302
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
29303
|
+
if (typeof v !== "string") return false;
|
|
29304
|
+
const normalized = v.trim().toLowerCase();
|
|
29305
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
29306
|
+
}, external_exports.boolean()).catch(false);
|
|
29307
|
+
var antigravityProviderEnvShape = {
|
|
29308
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
29309
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
29310
|
+
};
|
|
29311
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
29312
|
+
|
|
29313
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
29314
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
29315
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
29316
|
+
return v;
|
|
29317
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
29318
|
+
var codexProviderEnvShape = {
|
|
29319
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
29320
|
+
};
|
|
29321
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
28214
29322
|
|
|
28215
29323
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28216
|
-
import { randomUUID as
|
|
29324
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28217
29325
|
|
|
28218
29326
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28219
29327
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28220
29328
|
|
|
28221
29329
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28222
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
28223
|
-
import { join as
|
|
29330
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
29331
|
+
import { join as join13 } from "path";
|
|
28224
29332
|
|
|
28225
29333
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
28226
29334
|
function redactedPlaceholder(category) {
|
|
@@ -28462,7 +29570,6 @@ function createVaultGlue(options) {
|
|
|
28462
29570
|
const vault = new SecretVault({
|
|
28463
29571
|
repo: db.secretVault,
|
|
28464
29572
|
keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
|
|
28465
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
28466
29573
|
// Read live so a revocation applies to the very next call, not the next
|
|
28467
29574
|
// process.
|
|
28468
29575
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
|
|
@@ -28483,8 +29590,10 @@ function createVaultGlue(options) {
|
|
|
28483
29590
|
return decision.allow;
|
|
28484
29591
|
}
|
|
28485
29592
|
});
|
|
29593
|
+
let fingerprintKey;
|
|
29594
|
+
const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
|
|
28486
29595
|
const vaultWithSightings = {
|
|
28487
|
-
tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
|
|
29596
|
+
tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
|
|
28488
29597
|
detokenize: (token, opts) => vault.detokenize(token, opts),
|
|
28489
29598
|
describePointer: (token) => vault.describePointer(token),
|
|
28490
29599
|
resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
|
|
@@ -28531,14 +29640,14 @@ function describePointerSafe(token) {
|
|
|
28531
29640
|
// src/hooks/message-display-transform.ts
|
|
28532
29641
|
import {
|
|
28533
29642
|
mkdirSync as mkdirSync5,
|
|
28534
|
-
readdirSync as
|
|
28535
|
-
readFileSync as
|
|
29643
|
+
readdirSync as readdirSync5,
|
|
29644
|
+
readFileSync as readFileSync9,
|
|
28536
29645
|
renameSync as renameSync5,
|
|
28537
|
-
rmSync as
|
|
28538
|
-
statSync as
|
|
28539
|
-
writeFileSync as
|
|
29646
|
+
rmSync as rmSync5,
|
|
29647
|
+
statSync as statSync7,
|
|
29648
|
+
writeFileSync as writeFileSync7
|
|
28540
29649
|
} from "fs";
|
|
28541
|
-
import { dirname as
|
|
29650
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
28542
29651
|
var EMPTY_CARRY = Object.freeze({
|
|
28543
29652
|
tail: "",
|
|
28544
29653
|
fence: null,
|
|
@@ -28735,7 +29844,7 @@ var CARRY_FILE_PREFIX = "display-carry";
|
|
|
28735
29844
|
var STALE_CARRY_MS = 15 * 60 * 1e3;
|
|
28736
29845
|
function carryFilePath(dataDir2, sessionId) {
|
|
28737
29846
|
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 80);
|
|
28738
|
-
return
|
|
29847
|
+
return join14(dataDir2, `${CARRY_FILE_PREFIX}-${safe === "" ? "session" : safe}.json`);
|
|
28739
29848
|
}
|
|
28740
29849
|
function parseFence(value) {
|
|
28741
29850
|
if (typeof value !== "object" || value === null) return null;
|
|
@@ -28746,7 +29855,7 @@ function parseFence(value) {
|
|
|
28746
29855
|
}
|
|
28747
29856
|
function loadCarry(file2, keys) {
|
|
28748
29857
|
try {
|
|
28749
|
-
const parsed = JSON.parse(
|
|
29858
|
+
const parsed = JSON.parse(readFileSync9(file2, "utf8"));
|
|
28750
29859
|
if (typeof parsed !== "object" || parsed === null) return EMPTY_CARRY;
|
|
28751
29860
|
const record2 = parsed;
|
|
28752
29861
|
const revealedCount = record2.messageKey === keys.messageKey && typeof record2.revealedCount === "number" && Number.isFinite(record2.revealedCount) ? record2.revealedCount : 0;
|
|
@@ -28791,18 +29900,18 @@ function writeCarryRecord(file2, blockKey, messageKey, carry) {
|
|
|
28791
29900
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28792
29901
|
};
|
|
28793
29902
|
const tmp = `${file2}.tmp`;
|
|
28794
|
-
|
|
29903
|
+
writeFileSync7(tmp, JSON.stringify(record2), { mode: 384 });
|
|
28795
29904
|
renameSync5(tmp, file2);
|
|
28796
29905
|
}
|
|
28797
29906
|
function removeStaleCarryFiles(dir, keep) {
|
|
28798
29907
|
try {
|
|
28799
29908
|
const cutoff = Date.now() - STALE_CARRY_MS;
|
|
28800
|
-
for (const name of
|
|
29909
|
+
for (const name of readdirSync5(dir)) {
|
|
28801
29910
|
if (!name.startsWith(CARRY_FILE_PREFIX) || !name.endsWith(".json")) continue;
|
|
28802
|
-
const path =
|
|
29911
|
+
const path = join14(dir, name);
|
|
28803
29912
|
if (path === keep) continue;
|
|
28804
29913
|
try {
|
|
28805
|
-
if (
|
|
29914
|
+
if (statSync7(path).mtimeMs < cutoff) rmSync5(path, { force: true });
|
|
28806
29915
|
} catch {
|
|
28807
29916
|
}
|
|
28808
29917
|
}
|
|
@@ -28811,7 +29920,7 @@ function removeStaleCarryFiles(dir, keep) {
|
|
|
28811
29920
|
}
|
|
28812
29921
|
function saveCarry(file2, keys, carry) {
|
|
28813
29922
|
try {
|
|
28814
|
-
const dir =
|
|
29923
|
+
const dir = dirname4(file2);
|
|
28815
29924
|
mkdirSync5(dir, { recursive: true });
|
|
28816
29925
|
removeStaleCarryFiles(dir, file2);
|
|
28817
29926
|
writeCarryRecord(file2, keys.blockKey, keys.messageKey, carry);
|
|
@@ -28821,7 +29930,7 @@ function saveCarry(file2, keys, carry) {
|
|
|
28821
29930
|
function finalizeCarry(file2, keys, carry) {
|
|
28822
29931
|
try {
|
|
28823
29932
|
if (carry.revealedCount > 0) {
|
|
28824
|
-
mkdirSync5(
|
|
29933
|
+
mkdirSync5(dirname4(file2), { recursive: true });
|
|
28825
29934
|
writeCarryRecord(file2, null, keys.messageKey, {
|
|
28826
29935
|
...EMPTY_CARRY,
|
|
28827
29936
|
revealedCount: carry.revealedCount
|
|
@@ -28830,14 +29939,14 @@ function finalizeCarry(file2, keys, carry) {
|
|
|
28830
29939
|
}
|
|
28831
29940
|
let owned = true;
|
|
28832
29941
|
try {
|
|
28833
|
-
const parsed = JSON.parse(
|
|
29942
|
+
const parsed = JSON.parse(readFileSync9(file2, "utf8"));
|
|
28834
29943
|
if (typeof parsed === "object" && parsed !== null) {
|
|
28835
29944
|
const record2 = parsed;
|
|
28836
29945
|
owned = record2.blockKey === keys.blockKey || record2.messageKey === keys.messageKey;
|
|
28837
29946
|
}
|
|
28838
29947
|
} catch {
|
|
28839
29948
|
}
|
|
28840
|
-
if (owned)
|
|
29949
|
+
if (owned) rmSync5(file2, { force: true });
|
|
28841
29950
|
} catch {
|
|
28842
29951
|
}
|
|
28843
29952
|
}
|