@akasecurity/ai-tc-claude-code 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -0
- package/commands/setup.md +23 -0
- package/package.json +4 -3
- package/scripts/apply-suppressions.js +1665 -544
- package/scripts/backfill.js +2032 -404
- package/scripts/filescan.js +1933 -367
- package/scripts/firstrun.js +1358 -230
- package/scripts/intro.js +418 -58
- package/scripts/message-display.js +1463 -306
- package/scripts/onboard.js +1580 -249
- package/scripts/post-tool-use.js +1946 -368
- package/scripts/pre-tool-use.js +1961 -377
- package/scripts/query.js +1359 -231
- package/scripts/reconcile.js +1494 -337
- package/scripts/remediate.js +2183 -559
- package/scripts/scan-worker.js +18055 -0
- package/scripts/session-start.js +1434 -342
- package/scripts/start-light.js +441 -81
- package/scripts/statusline.js +1356 -228
- package/scripts/stop.js +400 -83
- package/scripts/user-prompt-submit.js +2071 -475
package/scripts/onboard.js
CHANGED
|
@@ -492,9 +492,8 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/persistence/src/database.ts
|
|
495
|
-
import { randomUUID as
|
|
496
|
-
import {
|
|
497
|
-
import { join, sep } from "path";
|
|
495
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
496
|
+
import { join as join2, sep } from "path";
|
|
498
497
|
import { DatabaseSync } from "node:sqlite";
|
|
499
498
|
|
|
500
499
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -574,6 +573,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
574
573
|
{
|
|
575
574
|
tag: "0018_serious_tana_nile",
|
|
576
575
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
tag: "0019_audit_started_at_index",
|
|
579
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
583
|
+
sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
|
|
577
584
|
}
|
|
578
585
|
];
|
|
579
586
|
|
|
@@ -15311,7 +15318,17 @@ var Finding = external_exports.object({
|
|
|
15311
15318
|
}).meta({ id: "Finding" });
|
|
15312
15319
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15313
15320
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15314
|
-
var FindingProvider = external_exports.enum([
|
|
15321
|
+
var FindingProvider = external_exports.enum([
|
|
15322
|
+
"claudecode",
|
|
15323
|
+
"claudedesktop",
|
|
15324
|
+
"cursor",
|
|
15325
|
+
"copilot",
|
|
15326
|
+
"chatgpt",
|
|
15327
|
+
"claudeai",
|
|
15328
|
+
"codex",
|
|
15329
|
+
"antigravity",
|
|
15330
|
+
"api"
|
|
15331
|
+
]).meta({ id: "FindingProvider" });
|
|
15315
15332
|
var FindingCategory = external_exports.enum([
|
|
15316
15333
|
"secret",
|
|
15317
15334
|
"pii",
|
|
@@ -15365,7 +15382,16 @@ var FindingInstance = external_exports.object({
|
|
|
15365
15382
|
confidence: external_exports.number().min(0).max(1),
|
|
15366
15383
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15367
15384
|
// that predate the resolution feature stay valid.
|
|
15368
|
-
status: FindingStatus.optional()
|
|
15385
|
+
status: FindingStatus.optional(),
|
|
15386
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15387
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15388
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15389
|
+
// MOST RECENT detection event, not the first.
|
|
15390
|
+
eventId: external_exports.string().optional(),
|
|
15391
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15392
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15393
|
+
// outside a session.
|
|
15394
|
+
sessionId: external_exports.string().optional()
|
|
15369
15395
|
}).meta({ id: "FindingInstance" });
|
|
15370
15396
|
var FindingGroup = external_exports.object({
|
|
15371
15397
|
id: external_exports.string(),
|
|
@@ -15409,7 +15435,11 @@ var FindingFacets = external_exports.object({
|
|
|
15409
15435
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15410
15436
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15411
15437
|
// counted under no value.
|
|
15412
|
-
status: external_exports.array(FindingFacetItem)
|
|
15438
|
+
status: external_exports.array(FindingFacetItem),
|
|
15439
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15440
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15441
|
+
// because a group spans tools.
|
|
15442
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15413
15443
|
}).meta({ id: "FindingFacets" });
|
|
15414
15444
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15415
15445
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15427,6 +15457,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15427
15457
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15428
15458
|
// session → findings drilldown). Findings without a session never match.
|
|
15429
15459
|
sessionId: external_exports.string().optional(),
|
|
15460
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15461
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15462
|
+
// means all time — this list has no default window.
|
|
15463
|
+
from: external_exports.iso.datetime().optional(),
|
|
15464
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15465
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15466
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15467
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15468
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15469
|
+
includeId: external_exports.string().optional(),
|
|
15430
15470
|
groupBy: external_exports.literal("type").optional(),
|
|
15431
15471
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15432
15472
|
cursor: external_exports.string().optional()
|
|
@@ -15471,15 +15511,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15471
15511
|
detection: FindingDetectionRef,
|
|
15472
15512
|
policy: FindingPolicyRef
|
|
15473
15513
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15514
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15515
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15516
|
+
severity: external_exports.array(Severity).optional(),
|
|
15517
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15518
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15519
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15520
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15521
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15522
|
+
// the grouped query's group-level fold.
|
|
15523
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15524
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15525
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15526
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15527
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15528
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15529
|
+
repo: external_exports.string().optional(),
|
|
15530
|
+
file: external_exports.string().optional(),
|
|
15531
|
+
q: external_exports.string().optional(),
|
|
15532
|
+
sessionId: external_exports.string().optional(),
|
|
15533
|
+
from: external_exports.iso.datetime().optional(),
|
|
15534
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15535
|
+
cursor: external_exports.string().optional()
|
|
15536
|
+
});
|
|
15537
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15538
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15539
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15540
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15541
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15542
|
+
// dimension still excludes its own filter.
|
|
15543
|
+
facets: FindingFacets,
|
|
15544
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15545
|
+
nextCursor: external_exports.string().nullable()
|
|
15546
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15547
|
+
var FindingLocationFile = external_exports.object({
|
|
15548
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15549
|
+
// with no file attribution).
|
|
15550
|
+
file: external_exports.string(),
|
|
15551
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15552
|
+
maxSeverity: Severity,
|
|
15553
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15554
|
+
// Folded from the instances' derived statuses with the same
|
|
15555
|
+
// open-dominates precedence a group uses.
|
|
15556
|
+
status: FindingStatus.optional(),
|
|
15557
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15558
|
+
// chips, and the count is what conveys scale.
|
|
15559
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15560
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15561
|
+
var FindingLocationRepo = external_exports.object({
|
|
15562
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15563
|
+
repo: external_exports.string(),
|
|
15564
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15565
|
+
maxSeverity: Severity,
|
|
15566
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15567
|
+
status: FindingStatus.optional(),
|
|
15568
|
+
files: external_exports.array(FindingLocationFile)
|
|
15569
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15570
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15571
|
+
severity: external_exports.array(Severity).optional(),
|
|
15572
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15573
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15574
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15575
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15576
|
+
// instances that match, and folds its status from those.
|
|
15577
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15578
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15579
|
+
q: external_exports.string().optional(),
|
|
15580
|
+
sessionId: external_exports.string().optional(),
|
|
15581
|
+
from: external_exports.iso.datetime().optional(),
|
|
15582
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15583
|
+
});
|
|
15584
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15585
|
+
totals: external_exports.object({
|
|
15586
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15587
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15588
|
+
files: external_exports.number().int().nonnegative()
|
|
15589
|
+
}),
|
|
15590
|
+
/** Sorted by max severity, then most recent. */
|
|
15591
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15592
|
+
/** Whether `limit` truncated the repo list. */
|
|
15593
|
+
hasMore: external_exports.boolean()
|
|
15594
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15474
15595
|
|
|
15475
15596
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15476
|
-
var Harness = external_exports.enum([
|
|
15597
|
+
var Harness = external_exports.enum([
|
|
15598
|
+
"claudecode",
|
|
15599
|
+
"cursor",
|
|
15600
|
+
"copilot",
|
|
15601
|
+
"codex",
|
|
15602
|
+
"antigravity",
|
|
15603
|
+
"windsurf",
|
|
15604
|
+
"claudedesktop",
|
|
15605
|
+
"chatgpt",
|
|
15606
|
+
"claudeai",
|
|
15607
|
+
"api"
|
|
15608
|
+
]).meta({ id: "Harness" });
|
|
15477
15609
|
var TOOL_TO_HARNESS = {
|
|
15478
15610
|
"claude-code": "claudecode",
|
|
15479
15611
|
"claude-desktop": "claudedesktop",
|
|
15480
15612
|
"github-copilot": "copilot",
|
|
15481
15613
|
cursor: "cursor",
|
|
15482
|
-
chatgpt: "chatgpt"
|
|
15614
|
+
chatgpt: "chatgpt",
|
|
15615
|
+
codex: "codex",
|
|
15616
|
+
antigravity: "antigravity",
|
|
15617
|
+
"claude-ai": "claudeai"
|
|
15483
15618
|
};
|
|
15484
15619
|
|
|
15485
15620
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15937,7 +16072,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15937
16072
|
// ../../packages/schema/src/zod/event.ts
|
|
15938
16073
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15939
16074
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15940
|
-
var SourceTool = external_exports.enum([
|
|
16075
|
+
var SourceTool = external_exports.enum([
|
|
16076
|
+
"claude-code",
|
|
16077
|
+
"claude-desktop",
|
|
16078
|
+
"cursor",
|
|
16079
|
+
"chatgpt",
|
|
16080
|
+
"claude-ai",
|
|
16081
|
+
"github-copilot",
|
|
16082
|
+
"codex",
|
|
16083
|
+
"antigravity",
|
|
16084
|
+
"cli",
|
|
16085
|
+
"unknown"
|
|
16086
|
+
]).meta({ id: "SourceTool" });
|
|
15941
16087
|
var EventMetadata = external_exports.object({
|
|
15942
16088
|
sessionId: external_exports.string().optional(),
|
|
15943
16089
|
repo: external_exports.string().optional(),
|
|
@@ -16008,7 +16154,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16008
16154
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16009
16155
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16010
16156
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16011
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16157
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16012
16158
|
var AccessCounts = external_exports.object({
|
|
16013
16159
|
open: external_exports.number().int().nonnegative(),
|
|
16014
16160
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16277,6 +16423,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16277
16423
|
useCount: true,
|
|
16278
16424
|
conditions: true
|
|
16279
16425
|
});
|
|
16426
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16280
16427
|
|
|
16281
16428
|
// ../../packages/schema/src/zod/rule.ts
|
|
16282
16429
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17092,6 +17239,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17092
17239
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17093
17240
|
}).meta({ id: "EgressWriteSummary" });
|
|
17094
17241
|
|
|
17242
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17243
|
+
var confirmation = external_exports.string().optional();
|
|
17244
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17245
|
+
reference: external_exports.string(),
|
|
17246
|
+
scope: external_exports.string(),
|
|
17247
|
+
reason: external_exports.string(),
|
|
17248
|
+
confirmation
|
|
17249
|
+
});
|
|
17250
|
+
var AddExceptionInput = external_exports.object({
|
|
17251
|
+
ruleId: external_exports.string(),
|
|
17252
|
+
value: external_exports.string(),
|
|
17253
|
+
scope: external_exports.string(),
|
|
17254
|
+
reason: external_exports.string(),
|
|
17255
|
+
confirmation
|
|
17256
|
+
});
|
|
17257
|
+
var GrantRevealInput = external_exports.object({
|
|
17258
|
+
pointer: external_exports.string(),
|
|
17259
|
+
scope: external_exports.string(),
|
|
17260
|
+
justification: external_exports.string(),
|
|
17261
|
+
confirmation
|
|
17262
|
+
});
|
|
17263
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17264
|
+
id: external_exports.string(),
|
|
17265
|
+
reason: external_exports.string()
|
|
17266
|
+
});
|
|
17267
|
+
var RotateKeyInput = external_exports.object({
|
|
17268
|
+
confirmation: external_exports.string()
|
|
17269
|
+
});
|
|
17270
|
+
|
|
17095
17271
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17096
17272
|
function toApiAction(dbVal) {
|
|
17097
17273
|
const map2 = {
|
|
@@ -17147,6 +17323,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17147
17323
|
repo: r.repo,
|
|
17148
17324
|
file: r.file,
|
|
17149
17325
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17326
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17327
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17150
17328
|
action: toApiAction(effectiveDbAction),
|
|
17151
17329
|
detectedAt: r.occurredAt,
|
|
17152
17330
|
confidence: r.confidence,
|
|
@@ -17278,14 +17456,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17278
17456
|
}
|
|
17279
17457
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17280
17458
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17459
|
+
function compareFindingGroupOrder(a, b) {
|
|
17460
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17461
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17462
|
+
const severityDiff = rankA - rankB;
|
|
17463
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17464
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17465
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17466
|
+
return a.id.localeCompare(b.id);
|
|
17467
|
+
}
|
|
17281
17468
|
function sortFindingGroups(groups) {
|
|
17282
|
-
return [...groups].sort(
|
|
17283
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17284
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17285
|
-
const severityDiff = rankA - rankB;
|
|
17286
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17287
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17288
|
-
});
|
|
17469
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17289
17470
|
}
|
|
17290
17471
|
function computeFindingFacets(allGroups, opts) {
|
|
17291
17472
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17341,15 +17522,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17341
17522
|
for (const g of forStatus) {
|
|
17342
17523
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17343
17524
|
}
|
|
17344
|
-
const
|
|
17525
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17345
17526
|
return {
|
|
17346
|
-
severity:
|
|
17347
|
-
provider:
|
|
17348
|
-
action:
|
|
17349
|
-
subtype:
|
|
17350
|
-
status:
|
|
17527
|
+
severity: toItems2(severityMap),
|
|
17528
|
+
provider: toItems2(providerMap),
|
|
17529
|
+
action: toItems2(actionMap),
|
|
17530
|
+
subtype: toItems2(subtypeMap),
|
|
17531
|
+
status: toItems2(statusMap)
|
|
17532
|
+
};
|
|
17533
|
+
}
|
|
17534
|
+
|
|
17535
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17536
|
+
function rowHaystack(row) {
|
|
17537
|
+
return [
|
|
17538
|
+
row.ruleId,
|
|
17539
|
+
row.category,
|
|
17540
|
+
row.maskedMatch,
|
|
17541
|
+
row.repo,
|
|
17542
|
+
row.file,
|
|
17543
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17544
|
+
row.id
|
|
17545
|
+
].join(" ").toLowerCase();
|
|
17546
|
+
}
|
|
17547
|
+
function matchesDimension(row, opts, dimension) {
|
|
17548
|
+
switch (dimension) {
|
|
17549
|
+
case "severity":
|
|
17550
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17551
|
+
case "subtype":
|
|
17552
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17553
|
+
case "providers":
|
|
17554
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17555
|
+
case "actions":
|
|
17556
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17557
|
+
case "statuses":
|
|
17558
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17559
|
+
case "tools":
|
|
17560
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17561
|
+
case "repo":
|
|
17562
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17563
|
+
case "file":
|
|
17564
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17565
|
+
case "q":
|
|
17566
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17567
|
+
}
|
|
17568
|
+
}
|
|
17569
|
+
var DIMENSIONS = [
|
|
17570
|
+
"severity",
|
|
17571
|
+
"subtype",
|
|
17572
|
+
"providers",
|
|
17573
|
+
"actions",
|
|
17574
|
+
"statuses",
|
|
17575
|
+
"tools",
|
|
17576
|
+
"repo",
|
|
17577
|
+
"file",
|
|
17578
|
+
"q"
|
|
17579
|
+
];
|
|
17580
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17581
|
+
for (const dimension of DIMENSIONS) {
|
|
17582
|
+
if (dimension === except) continue;
|
|
17583
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17584
|
+
}
|
|
17585
|
+
return true;
|
|
17586
|
+
}
|
|
17587
|
+
function toItems(counts) {
|
|
17588
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17589
|
+
}
|
|
17590
|
+
function bump(counts, value) {
|
|
17591
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17592
|
+
}
|
|
17593
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17594
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17595
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17596
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17597
|
+
const action = /* @__PURE__ */ new Map();
|
|
17598
|
+
const status = /* @__PURE__ */ new Map();
|
|
17599
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17600
|
+
return {
|
|
17601
|
+
add(row) {
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17603
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17604
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17605
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17606
|
+
}
|
|
17607
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17608
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17609
|
+
bump(status, row.status);
|
|
17610
|
+
}
|
|
17611
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17612
|
+
bump(tool, row.toolName);
|
|
17613
|
+
}
|
|
17614
|
+
},
|
|
17615
|
+
facets: () => ({
|
|
17616
|
+
severity: toItems(severity),
|
|
17617
|
+
subtype: toItems(subtype),
|
|
17618
|
+
provider: toItems(provider),
|
|
17619
|
+
action: toItems(action),
|
|
17620
|
+
status: toItems(status),
|
|
17621
|
+
tool: toItems(tool)
|
|
17622
|
+
})
|
|
17351
17623
|
};
|
|
17352
17624
|
}
|
|
17625
|
+
function toInstanceDetail(row) {
|
|
17626
|
+
const category = toApiCategory(row.category);
|
|
17627
|
+
return {
|
|
17628
|
+
id: row.id,
|
|
17629
|
+
provider: toApiProvider(row.sourceTool),
|
|
17630
|
+
repo: row.repo,
|
|
17631
|
+
file: row.file,
|
|
17632
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17633
|
+
eventId: row.eventId,
|
|
17634
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17635
|
+
action: toApiAction(row.actionTaken),
|
|
17636
|
+
detectedAt: row.occurredAt,
|
|
17637
|
+
confidence: row.confidence,
|
|
17638
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17639
|
+
groupId: row.ruleId,
|
|
17640
|
+
category,
|
|
17641
|
+
subtype: row.ruleId,
|
|
17642
|
+
severity: row.severity,
|
|
17643
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17644
|
+
detection: { id: row.ruleId, name: null },
|
|
17645
|
+
policy: { id: `category:${category}`, name: category }
|
|
17646
|
+
};
|
|
17647
|
+
}
|
|
17648
|
+
var SEVERITY_ORDER2 = {
|
|
17649
|
+
critical: 0,
|
|
17650
|
+
high: 1,
|
|
17651
|
+
medium: 2,
|
|
17652
|
+
low: 3
|
|
17653
|
+
};
|
|
17654
|
+
function newLocationAccumulator() {
|
|
17655
|
+
return {
|
|
17656
|
+
instanceCount: 0,
|
|
17657
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17658
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17659
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17660
|
+
maxSeverity: "low",
|
|
17661
|
+
latestDetectedAt: "",
|
|
17662
|
+
statuses: [],
|
|
17663
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17664
|
+
};
|
|
17665
|
+
}
|
|
17666
|
+
function addToLocation(acc, row) {
|
|
17667
|
+
acc.instanceCount += 1;
|
|
17668
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17669
|
+
if (rank < acc.maxSeverityRank) {
|
|
17670
|
+
acc.maxSeverityRank = rank;
|
|
17671
|
+
acc.maxSeverity = row.severity;
|
|
17672
|
+
}
|
|
17673
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17674
|
+
acc.statuses.push(row.status);
|
|
17675
|
+
acc.ruleIds.add(row.ruleId);
|
|
17676
|
+
}
|
|
17353
17677
|
|
|
17354
17678
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17355
17679
|
var InstalledPack = external_exports.object({
|
|
@@ -17483,6 +17807,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17483
17807
|
revealGrantId: external_exports.string().nullable(),
|
|
17484
17808
|
sightings: external_exports.array(VaultSighting)
|
|
17485
17809
|
});
|
|
17810
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17811
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17812
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17813
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17814
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17815
|
+
// Opaque; names the last row of the page just served.
|
|
17816
|
+
cursor: external_exports.string().optional()
|
|
17817
|
+
});
|
|
17818
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17819
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17820
|
+
// independent, so paging never changes what the count claims.
|
|
17821
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17822
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17823
|
+
// `null` once the last page is reached.
|
|
17824
|
+
nextCursor: external_exports.string().nullable()
|
|
17825
|
+
});
|
|
17826
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17827
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17828
|
+
cursor: external_exports.string().optional()
|
|
17829
|
+
});
|
|
17830
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17831
|
+
// Reused values across the whole store — the number the section's claim
|
|
17832
|
+
// ("values detected in more than one place") is about.
|
|
17833
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17834
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17835
|
+
nextCursor: external_exports.string().nullable()
|
|
17836
|
+
});
|
|
17837
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17838
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17839
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17840
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17841
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17842
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17843
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17844
|
+
cursor: external_exports.string().optional()
|
|
17845
|
+
});
|
|
17846
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17847
|
+
items: external_exports.array(VaultDeref),
|
|
17848
|
+
nextCursor: external_exports.string().nullable(),
|
|
17849
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17850
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17851
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17852
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17853
|
+
});
|
|
17486
17854
|
var VaultKeyCustody = external_exports.string();
|
|
17487
17855
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17488
17856
|
var VAULT_CONSENT_VERSION = 1;
|
|
@@ -17857,7 +18225,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17857
18225
|
// Omit for both kinds.
|
|
17858
18226
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17859
18227
|
});
|
|
17860
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18228
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17861
18229
|
var ScanCoverageProvider = external_exports.object({
|
|
17862
18230
|
provider: Provider,
|
|
17863
18231
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18099,6 +18467,155 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18099
18467
|
);
|
|
18100
18468
|
}
|
|
18101
18469
|
|
|
18470
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18471
|
+
import { randomUUID } from "crypto";
|
|
18472
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18473
|
+
import { basename, dirname, join } from "path";
|
|
18474
|
+
|
|
18475
|
+
// ../../packages/persistence/src/paths.ts
|
|
18476
|
+
import {
|
|
18477
|
+
chmodSync,
|
|
18478
|
+
linkSync,
|
|
18479
|
+
lstatSync,
|
|
18480
|
+
mkdirSync,
|
|
18481
|
+
renameSync,
|
|
18482
|
+
rmSync,
|
|
18483
|
+
writeFileSync
|
|
18484
|
+
} from "fs";
|
|
18485
|
+
import { threadId } from "worker_threads";
|
|
18486
|
+
var DATA_DIR_MODE = 448;
|
|
18487
|
+
var DATA_FILE_MODE = 384;
|
|
18488
|
+
var DB_FILENAME = "aka.db";
|
|
18489
|
+
function isSymlink(path) {
|
|
18490
|
+
try {
|
|
18491
|
+
return lstatSync(path).isSymbolicLink();
|
|
18492
|
+
} catch {
|
|
18493
|
+
return false;
|
|
18494
|
+
}
|
|
18495
|
+
}
|
|
18496
|
+
function chmodBestEffort(path, mode) {
|
|
18497
|
+
if (isSymlink(path)) return;
|
|
18498
|
+
try {
|
|
18499
|
+
chmodSync(path, mode);
|
|
18500
|
+
} catch {
|
|
18501
|
+
}
|
|
18502
|
+
}
|
|
18503
|
+
function tightenDir(dir) {
|
|
18504
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18505
|
+
}
|
|
18506
|
+
function ensureDataDirSync(dir) {
|
|
18507
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18508
|
+
tightenDir(dir);
|
|
18509
|
+
}
|
|
18510
|
+
function dbSidecars(file2) {
|
|
18511
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18512
|
+
}
|
|
18513
|
+
function tightenFile(file2) {
|
|
18514
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18515
|
+
}
|
|
18516
|
+
function tightenPerms(file2) {
|
|
18517
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18518
|
+
}
|
|
18519
|
+
function writeOwnerOnlyFileSync(file2, data) {
|
|
18520
|
+
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18521
|
+
try {
|
|
18522
|
+
rmSync(tmp, { force: true });
|
|
18523
|
+
} catch {
|
|
18524
|
+
}
|
|
18525
|
+
try {
|
|
18526
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18527
|
+
renameSync(tmp, file2);
|
|
18528
|
+
} finally {
|
|
18529
|
+
try {
|
|
18530
|
+
rmSync(tmp, { force: true });
|
|
18531
|
+
} catch {
|
|
18532
|
+
}
|
|
18533
|
+
}
|
|
18534
|
+
tightenFile(file2);
|
|
18535
|
+
}
|
|
18536
|
+
|
|
18537
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18538
|
+
function backupPath(file2, tag) {
|
|
18539
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18540
|
+
}
|
|
18541
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18542
|
+
function reapStalePartials(file2) {
|
|
18543
|
+
const dir = dirname(file2);
|
|
18544
|
+
const prefix = `${basename(file2)}.`;
|
|
18545
|
+
let entries;
|
|
18546
|
+
try {
|
|
18547
|
+
entries = readdirSync(dir);
|
|
18548
|
+
} catch {
|
|
18549
|
+
return;
|
|
18550
|
+
}
|
|
18551
|
+
for (const name of entries) {
|
|
18552
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18553
|
+
const partial2 = join(dir, name);
|
|
18554
|
+
try {
|
|
18555
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18556
|
+
rmSync2(partial2, { force: true });
|
|
18557
|
+
}
|
|
18558
|
+
} catch {
|
|
18559
|
+
}
|
|
18560
|
+
}
|
|
18561
|
+
}
|
|
18562
|
+
function snapshotStore(db, backup) {
|
|
18563
|
+
const partial2 = `${backup}.partial`;
|
|
18564
|
+
try {
|
|
18565
|
+
rmSync2(partial2, { force: true });
|
|
18566
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18567
|
+
tightenFile(partial2);
|
|
18568
|
+
renameSync2(partial2, backup);
|
|
18569
|
+
} catch (error51) {
|
|
18570
|
+
try {
|
|
18571
|
+
rmSync2(partial2, { force: true });
|
|
18572
|
+
} catch {
|
|
18573
|
+
}
|
|
18574
|
+
throw error51;
|
|
18575
|
+
}
|
|
18576
|
+
}
|
|
18577
|
+
function moveStoreAside(file2, backup) {
|
|
18578
|
+
const undo = [];
|
|
18579
|
+
renameSync2(file2, backup);
|
|
18580
|
+
undo.push([backup, file2]);
|
|
18581
|
+
try {
|
|
18582
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18583
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18584
|
+
try {
|
|
18585
|
+
renameSync2(sidecar, moved);
|
|
18586
|
+
undo.push([moved, sidecar]);
|
|
18587
|
+
} catch {
|
|
18588
|
+
rmSync2(sidecar, { force: true });
|
|
18589
|
+
}
|
|
18590
|
+
}
|
|
18591
|
+
} catch (error51) {
|
|
18592
|
+
for (const [from, to] of undo.reverse()) {
|
|
18593
|
+
try {
|
|
18594
|
+
renameSync2(from, to);
|
|
18595
|
+
} catch {
|
|
18596
|
+
}
|
|
18597
|
+
}
|
|
18598
|
+
throw error51;
|
|
18599
|
+
}
|
|
18600
|
+
tightenPerms(backup);
|
|
18601
|
+
}
|
|
18602
|
+
function discardStore(file2, backup) {
|
|
18603
|
+
try {
|
|
18604
|
+
rmSync2(file2, { force: true });
|
|
18605
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18606
|
+
rmSync2(sidecar, { force: true });
|
|
18607
|
+
}
|
|
18608
|
+
} catch (error51) {
|
|
18609
|
+
if (existsSync(file2)) {
|
|
18610
|
+
try {
|
|
18611
|
+
rmSync2(backup, { force: true });
|
|
18612
|
+
} catch {
|
|
18613
|
+
}
|
|
18614
|
+
}
|
|
18615
|
+
throw error51;
|
|
18616
|
+
}
|
|
18617
|
+
}
|
|
18618
|
+
|
|
18102
18619
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18103
18620
|
function escapeLikePattern(s) {
|
|
18104
18621
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18240,55 +18757,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18240
18757
|
return out;
|
|
18241
18758
|
}
|
|
18242
18759
|
|
|
18243
|
-
// ../../packages/persistence/src/paths.ts
|
|
18244
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18245
|
-
var DATA_DIR_MODE = 448;
|
|
18246
|
-
var DATA_FILE_MODE = 384;
|
|
18247
|
-
var DB_FILENAME = "aka.db";
|
|
18248
|
-
function chmodBestEffort(path, mode) {
|
|
18249
|
-
try {
|
|
18250
|
-
chmodSync(path, mode);
|
|
18251
|
-
} catch {
|
|
18252
|
-
}
|
|
18253
|
-
}
|
|
18254
|
-
function tightenDir(dir) {
|
|
18255
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18256
|
-
}
|
|
18257
|
-
function ensureDataDirSync(dir) {
|
|
18258
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18259
|
-
tightenDir(dir);
|
|
18260
|
-
}
|
|
18261
|
-
function dbSidecars(file2) {
|
|
18262
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18263
|
-
}
|
|
18264
|
-
function tightenFile(file2) {
|
|
18265
|
-
try {
|
|
18266
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18267
|
-
} catch {
|
|
18268
|
-
}
|
|
18269
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18270
|
-
}
|
|
18271
|
-
function tightenPerms(file2) {
|
|
18272
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18273
|
-
}
|
|
18274
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18275
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18276
|
-
try {
|
|
18277
|
-
rmSync(tmp, { force: true });
|
|
18278
|
-
} catch {
|
|
18279
|
-
}
|
|
18280
|
-
try {
|
|
18281
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18282
|
-
renameSync(tmp, file2);
|
|
18283
|
-
} finally {
|
|
18284
|
-
try {
|
|
18285
|
-
rmSync(tmp, { force: true });
|
|
18286
|
-
} catch {
|
|
18287
|
-
}
|
|
18288
|
-
}
|
|
18289
|
-
tightenFile(file2);
|
|
18290
|
-
}
|
|
18291
|
-
|
|
18292
18760
|
// ../../packages/persistence/src/migrations.ts
|
|
18293
18761
|
function describeObject(object2) {
|
|
18294
18762
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18404,9 +18872,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18404
18872
|
}
|
|
18405
18873
|
}
|
|
18406
18874
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18407
|
-
|
|
18408
|
-
|
|
18409
|
-
|
|
18875
|
+
reapStalePartials(file2);
|
|
18876
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18877
|
+
snapshotStore(db, backup);
|
|
18410
18878
|
return backup;
|
|
18411
18879
|
}
|
|
18412
18880
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18750,6 +19218,25 @@ function parseJsonObject(s) {
|
|
|
18750
19218
|
return void 0;
|
|
18751
19219
|
}
|
|
18752
19220
|
|
|
19221
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19222
|
+
function encodeKeysetCursor(payload) {
|
|
19223
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19224
|
+
}
|
|
19225
|
+
function decodeKeysetCursor(cursor) {
|
|
19226
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19227
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19228
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19229
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19230
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19231
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19232
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19233
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19234
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19235
|
+
return parsed;
|
|
19236
|
+
}
|
|
19237
|
+
return null;
|
|
19238
|
+
}
|
|
19239
|
+
|
|
18753
19240
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18754
19241
|
var DAY_MS = 864e5;
|
|
18755
19242
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18795,16 +19282,6 @@ function utcWindow(nowMs) {
|
|
|
18795
19282
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18796
19283
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18797
19284
|
}
|
|
18798
|
-
function encodeCursor(payload) {
|
|
18799
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18800
|
-
}
|
|
18801
|
-
function decodeCursor(cursor) {
|
|
18802
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18803
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18804
|
-
return parsed;
|
|
18805
|
-
}
|
|
18806
|
-
return null;
|
|
18807
|
-
}
|
|
18808
19285
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18809
19286
|
session: "session",
|
|
18810
19287
|
prompt: "prompt",
|
|
@@ -18949,7 +19426,7 @@ var SqliteActivityRepository = class {
|
|
|
18949
19426
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18950
19427
|
}
|
|
18951
19428
|
listSessions(query) {
|
|
18952
|
-
const cursor = query.cursor ?
|
|
19429
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18953
19430
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18954
19431
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18955
19432
|
const conditions = [SESSION_ROOT];
|
|
@@ -19023,7 +19500,7 @@ var SqliteActivityRepository = class {
|
|
|
19023
19500
|
)
|
|
19024
19501
|
);
|
|
19025
19502
|
const last = page[page.length - 1];
|
|
19026
|
-
const nextCursor = hasMore && last ?
|
|
19503
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19027
19504
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19028
19505
|
}
|
|
19029
19506
|
getSession(sessionId) {
|
|
@@ -19896,7 +20373,7 @@ var SqliteEventsRepository = class {
|
|
|
19896
20373
|
};
|
|
19897
20374
|
|
|
19898
20375
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19899
|
-
import { randomUUID } from "crypto";
|
|
20376
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19900
20377
|
|
|
19901
20378
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19902
20379
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19932,8 +20409,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19932
20409
|
AND conditions IS NULL
|
|
19933
20410
|
AND ${ACTIVE_PREDICATE}`;
|
|
19934
20411
|
var SqliteExceptionsRepository = class {
|
|
19935
|
-
constructor(db) {
|
|
20412
|
+
constructor(db, now = () => Date.now()) {
|
|
19936
20413
|
this.db = db;
|
|
20414
|
+
this.now = now;
|
|
19937
20415
|
this.consumeStmt = db.prepare(
|
|
19938
20416
|
`UPDATE exceptions
|
|
19939
20417
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19951,6 +20429,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19951
20429
|
);
|
|
19952
20430
|
}
|
|
19953
20431
|
db;
|
|
20432
|
+
now;
|
|
19954
20433
|
consumeStmt;
|
|
19955
20434
|
insertBlockedStmt;
|
|
19956
20435
|
sweepBlockedStmt;
|
|
@@ -19977,8 +20456,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19977
20456
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19978
20457
|
);
|
|
19979
20458
|
}
|
|
19980
|
-
const id =
|
|
19981
|
-
const now =
|
|
20459
|
+
const id = randomUUID2();
|
|
20460
|
+
const now = this.now();
|
|
19982
20461
|
try {
|
|
19983
20462
|
this.insertExceptionRow(id, input, now);
|
|
19984
20463
|
} catch (err) {
|
|
@@ -20056,7 +20535,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20056
20535
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20057
20536
|
const rows = allRows(
|
|
20058
20537
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20059
|
-
opts?.includeTerminal ? {} : { now:
|
|
20538
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20060
20539
|
);
|
|
20061
20540
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20062
20541
|
return Promise.resolve(exceptions);
|
|
@@ -20091,7 +20570,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20091
20570
|
* already revoked.
|
|
20092
20571
|
*/
|
|
20093
20572
|
revoke(id, revokedBy, reason) {
|
|
20094
|
-
const now =
|
|
20573
|
+
const now = this.now();
|
|
20095
20574
|
const result = this.db.prepare(
|
|
20096
20575
|
`UPDATE exceptions
|
|
20097
20576
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20105,7 +20584,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20105
20584
|
* callers must treat identically — means it does not and the detection is
|
|
20106
20585
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20107
20586
|
*/
|
|
20108
|
-
consume(id, now =
|
|
20587
|
+
consume(id, now = this.now()) {
|
|
20109
20588
|
const result = this.consumeStmt.run({ id, now });
|
|
20110
20589
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20111
20590
|
}
|
|
@@ -20114,7 +20593,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20114
20593
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20115
20594
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20116
20595
|
*/
|
|
20117
|
-
activeBundleEntries(keyVersion, now =
|
|
20596
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20118
20597
|
const rows = allRows(
|
|
20119
20598
|
this.db.prepare(
|
|
20120
20599
|
`SELECT * FROM exceptions
|
|
@@ -20146,7 +20625,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20146
20625
|
* than the retention window on every write, so the ledger self-limits.
|
|
20147
20626
|
*/
|
|
20148
20627
|
recordBlocked(entry) {
|
|
20149
|
-
const now =
|
|
20628
|
+
const now = this.now();
|
|
20150
20629
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20151
20630
|
this.insertBlockedStmt.run({
|
|
20152
20631
|
reference: entry.reference,
|
|
@@ -20169,7 +20648,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20169
20648
|
WHERE blocked_at > :cutoff
|
|
20170
20649
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20171
20650
|
),
|
|
20172
|
-
{ cutoff:
|
|
20651
|
+
{ cutoff: this.now() - windowMs }
|
|
20173
20652
|
);
|
|
20174
20653
|
return Promise.resolve(
|
|
20175
20654
|
rows.map((row) => ({
|
|
@@ -20197,8 +20676,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20197
20676
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20198
20677
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20199
20678
|
*/
|
|
20200
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20679
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20201
20680
|
try {
|
|
20681
|
+
const at = now ?? this.now();
|
|
20202
20682
|
const row = getRow(
|
|
20203
20683
|
this.db.prepare(
|
|
20204
20684
|
`SELECT id FROM exceptions
|
|
@@ -20207,7 +20687,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20207
20687
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20208
20688
|
LIMIT 1`
|
|
20209
20689
|
),
|
|
20210
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20690
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20211
20691
|
);
|
|
20212
20692
|
return Promise.resolve(row ?? null);
|
|
20213
20693
|
} catch (err) {
|
|
@@ -20221,7 +20701,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20221
20701
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20222
20702
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20223
20703
|
*/
|
|
20224
|
-
sweepTerminal(retentionMs, now =
|
|
20704
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20225
20705
|
const result = this.db.prepare(
|
|
20226
20706
|
`DELETE FROM exceptions
|
|
20227
20707
|
WHERE updated_at < :cutoff
|
|
@@ -20284,8 +20764,25 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20284
20764
|
|
|
20285
20765
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20286
20766
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20287
|
-
var
|
|
20288
|
-
var
|
|
20767
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20768
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20769
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20770
|
+
function compareLocationOrder(a, b) {
|
|
20771
|
+
return compareFindingGroupOrder(
|
|
20772
|
+
{
|
|
20773
|
+
severity: a.maxSeverity,
|
|
20774
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20775
|
+
id: ""
|
|
20776
|
+
},
|
|
20777
|
+
{
|
|
20778
|
+
severity: b.maxSeverity,
|
|
20779
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20780
|
+
id: ""
|
|
20781
|
+
}
|
|
20782
|
+
);
|
|
20783
|
+
}
|
|
20784
|
+
var CONCAT_SEP = ",";
|
|
20785
|
+
var TUPLE_SEP = "|";
|
|
20289
20786
|
function splitConcat(value) {
|
|
20290
20787
|
return value === null || value === "" ? [] : value.split(CONCAT_SEP);
|
|
20291
20788
|
}
|
|
@@ -20296,6 +20793,33 @@ function deriveInstanceStatus(row) {
|
|
|
20296
20793
|
latestResolutionStatus: row.latest_status
|
|
20297
20794
|
});
|
|
20298
20795
|
}
|
|
20796
|
+
function encodeGroupCursor(group) {
|
|
20797
|
+
const payload = {
|
|
20798
|
+
sev: group.severity,
|
|
20799
|
+
t: group.latestDetectedAt,
|
|
20800
|
+
id: group.id
|
|
20801
|
+
};
|
|
20802
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20803
|
+
}
|
|
20804
|
+
function decodeGroupCursor(cursor) {
|
|
20805
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20806
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20807
|
+
return {
|
|
20808
|
+
severity: parsed.sev,
|
|
20809
|
+
latestDetectedAt: parsed.t,
|
|
20810
|
+
id: parsed.id
|
|
20811
|
+
};
|
|
20812
|
+
}
|
|
20813
|
+
return null;
|
|
20814
|
+
}
|
|
20815
|
+
function firstAfter(sorted, cursor) {
|
|
20816
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20817
|
+
return index === -1 ? sorted.length : index;
|
|
20818
|
+
}
|
|
20819
|
+
function findDeepLinked(sorted, page, id) {
|
|
20820
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20821
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20822
|
+
}
|
|
20299
20823
|
var DAY_MS3 = 864e5;
|
|
20300
20824
|
var SqliteFindingsRepository = class {
|
|
20301
20825
|
constructor(db) {
|
|
@@ -20405,8 +20929,13 @@ var SqliteFindingsRepository = class {
|
|
|
20405
20929
|
*/
|
|
20406
20930
|
listGroupedFindings(query) {
|
|
20407
20931
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20408
|
-
const
|
|
20409
|
-
const
|
|
20932
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20933
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20934
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20935
|
+
const sessionParams = {
|
|
20936
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20937
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20938
|
+
};
|
|
20410
20939
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20411
20940
|
predicate,
|
|
20412
20941
|
params: sessionParams
|
|
@@ -20414,7 +20943,8 @@ var SqliteFindingsRepository = class {
|
|
|
20414
20943
|
const rows = allRows(
|
|
20415
20944
|
this.db.prepare(
|
|
20416
20945
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20417
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20946
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20947
|
+
kind, finding_key, latest_status
|
|
20418
20948
|
FROM (
|
|
20419
20949
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20420
20950
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20424,6 +20954,7 @@ var SqliteFindingsRepository = class {
|
|
|
20424
20954
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20425
20955
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20426
20956
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
20957
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20427
20958
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20428
20959
|
latest.status AS latest_status,
|
|
20429
20960
|
ROW_NUMBER() OVER (
|
|
@@ -20455,6 +20986,8 @@ var SqliteFindingsRepository = class {
|
|
|
20455
20986
|
repo: r.repo ?? "",
|
|
20456
20987
|
file: r.file ?? "",
|
|
20457
20988
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
20989
|
+
eventId: r.event_id,
|
|
20990
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20458
20991
|
status: deriveInstanceStatus(r)
|
|
20459
20992
|
}));
|
|
20460
20993
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20478,18 +21011,23 @@ var SqliteFindingsRepository = class {
|
|
|
20478
21011
|
groups: sorted.length
|
|
20479
21012
|
};
|
|
20480
21013
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21014
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21015
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21016
|
+
const page = sorted.slice(start, start + limit);
|
|
21017
|
+
const lastOnPage = page.at(-1);
|
|
21018
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21019
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20481
21020
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20482
|
-
const
|
|
20483
|
-
|
|
20484
|
-
|
|
20485
|
-
|
|
20486
|
-
|
|
20487
|
-
);
|
|
21021
|
+
const narrow = (g) => statusSet ? {
|
|
21022
|
+
...g,
|
|
21023
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21024
|
+
} : g;
|
|
21025
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20488
21026
|
return Promise.resolve({
|
|
20489
21027
|
totals,
|
|
20490
21028
|
facets,
|
|
20491
21029
|
items,
|
|
20492
|
-
nextCursor
|
|
21030
|
+
nextCursor,
|
|
20493
21031
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20494
21032
|
});
|
|
20495
21033
|
}
|
|
@@ -20521,6 +21059,266 @@ var SqliteFindingsRepository = class {
|
|
|
20521
21059
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20522
21060
|
* path repeating across tuples.)
|
|
20523
21061
|
*/
|
|
21062
|
+
/**
|
|
21063
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21064
|
+
* paged by keyset.
|
|
21065
|
+
*
|
|
21066
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21067
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21068
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21069
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21070
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21071
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21072
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21073
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21074
|
+
* none of the mappers names", which no IN-list can say.
|
|
21075
|
+
*
|
|
21076
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21077
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21078
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21079
|
+
* while the counting runs, and only the page itself is retained.
|
|
21080
|
+
*/
|
|
21081
|
+
listFindingInstances(query) {
|
|
21082
|
+
const opts = {
|
|
21083
|
+
severity: query.severity,
|
|
21084
|
+
subtype: query.subtype,
|
|
21085
|
+
providers: query.provider,
|
|
21086
|
+
actions: query.action,
|
|
21087
|
+
statuses: query.status,
|
|
21088
|
+
tools: query.tool,
|
|
21089
|
+
repo: query.repo,
|
|
21090
|
+
file: query.file,
|
|
21091
|
+
q: query.q
|
|
21092
|
+
};
|
|
21093
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21094
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21095
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21096
|
+
const items = [];
|
|
21097
|
+
let total = 0;
|
|
21098
|
+
let last;
|
|
21099
|
+
let hasMore = false;
|
|
21100
|
+
for (const row of this.scanFindingRows({
|
|
21101
|
+
sessionId: query.sessionId,
|
|
21102
|
+
from: query.from
|
|
21103
|
+
})) {
|
|
21104
|
+
accumulator.add(row);
|
|
21105
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21106
|
+
total += 1;
|
|
21107
|
+
if (items.length < limit) {
|
|
21108
|
+
items.push(toInstanceDetail(row));
|
|
21109
|
+
last = row;
|
|
21110
|
+
} else {
|
|
21111
|
+
hasMore = true;
|
|
21112
|
+
}
|
|
21113
|
+
}
|
|
21114
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21115
|
+
if (cursor !== null) {
|
|
21116
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21117
|
+
return Promise.resolve({
|
|
21118
|
+
totals: { findings: total },
|
|
21119
|
+
facets: accumulator.facets(),
|
|
21120
|
+
items: resumed.items,
|
|
21121
|
+
nextCursor: resumed.nextCursor
|
|
21122
|
+
});
|
|
21123
|
+
}
|
|
21124
|
+
return Promise.resolve({
|
|
21125
|
+
totals: { findings: total },
|
|
21126
|
+
facets: accumulator.facets(),
|
|
21127
|
+
items,
|
|
21128
|
+
nextCursor
|
|
21129
|
+
});
|
|
21130
|
+
}
|
|
21131
|
+
/**
|
|
21132
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21133
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21134
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21135
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21136
|
+
*/
|
|
21137
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21138
|
+
const items = [];
|
|
21139
|
+
let last;
|
|
21140
|
+
let hasMore = false;
|
|
21141
|
+
for (const row of this.scanFindingRows({
|
|
21142
|
+
sessionId: query.sessionId,
|
|
21143
|
+
from: query.from,
|
|
21144
|
+
after: cursor
|
|
21145
|
+
})) {
|
|
21146
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21147
|
+
if (items.length < limit) {
|
|
21148
|
+
items.push(toInstanceDetail(row));
|
|
21149
|
+
last = row;
|
|
21150
|
+
} else {
|
|
21151
|
+
hasMore = true;
|
|
21152
|
+
break;
|
|
21153
|
+
}
|
|
21154
|
+
}
|
|
21155
|
+
return {
|
|
21156
|
+
items,
|
|
21157
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21158
|
+
};
|
|
21159
|
+
}
|
|
21160
|
+
/**
|
|
21161
|
+
* The same findings folded by location: repository, then file within it.
|
|
21162
|
+
*
|
|
21163
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21164
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21165
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21166
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21167
|
+
* filter can name it.
|
|
21168
|
+
*/
|
|
21169
|
+
listFindingLocations(query) {
|
|
21170
|
+
const opts = {
|
|
21171
|
+
severity: query.severity,
|
|
21172
|
+
subtype: query.subtype,
|
|
21173
|
+
providers: query.provider,
|
|
21174
|
+
actions: query.action,
|
|
21175
|
+
statuses: query.status,
|
|
21176
|
+
tools: query.tool,
|
|
21177
|
+
q: query.q
|
|
21178
|
+
};
|
|
21179
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21180
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21181
|
+
let total = 0;
|
|
21182
|
+
for (const row of this.scanFindingRows({
|
|
21183
|
+
sessionId: query.sessionId,
|
|
21184
|
+
from: query.from
|
|
21185
|
+
})) {
|
|
21186
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21187
|
+
total += 1;
|
|
21188
|
+
let files = byRepo.get(row.repo);
|
|
21189
|
+
if (files === void 0) {
|
|
21190
|
+
files = /* @__PURE__ */ new Map();
|
|
21191
|
+
byRepo.set(row.repo, files);
|
|
21192
|
+
}
|
|
21193
|
+
let acc = files.get(row.file);
|
|
21194
|
+
if (acc === void 0) {
|
|
21195
|
+
acc = newLocationAccumulator();
|
|
21196
|
+
files.set(row.file, acc);
|
|
21197
|
+
}
|
|
21198
|
+
addToLocation(acc, row);
|
|
21199
|
+
}
|
|
21200
|
+
let fileCount = 0;
|
|
21201
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21202
|
+
fileCount += files.size;
|
|
21203
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21204
|
+
file: file2,
|
|
21205
|
+
instanceCount: acc.instanceCount,
|
|
21206
|
+
maxSeverity: acc.maxSeverity,
|
|
21207
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21208
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21209
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21210
|
+
})).sort(compareLocationOrder);
|
|
21211
|
+
const rollup = fileRows.reduce(
|
|
21212
|
+
(a, f) => ({
|
|
21213
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21214
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21215
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21216
|
+
}),
|
|
21217
|
+
{
|
|
21218
|
+
instanceCount: 0,
|
|
21219
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21220
|
+
latestDetectedAt: ""
|
|
21221
|
+
}
|
|
21222
|
+
);
|
|
21223
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21224
|
+
const folded = foldGroupStatus(statuses);
|
|
21225
|
+
return {
|
|
21226
|
+
repo,
|
|
21227
|
+
instanceCount: rollup.instanceCount,
|
|
21228
|
+
maxSeverity: rollup.maxSeverity,
|
|
21229
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21230
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21231
|
+
files: fileRows
|
|
21232
|
+
};
|
|
21233
|
+
});
|
|
21234
|
+
repos.sort(compareLocationOrder);
|
|
21235
|
+
return Promise.resolve({
|
|
21236
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21237
|
+
items: repos.slice(0, limit),
|
|
21238
|
+
hasMore: repos.length > limit
|
|
21239
|
+
});
|
|
21240
|
+
}
|
|
21241
|
+
/**
|
|
21242
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21243
|
+
*
|
|
21244
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21245
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21246
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21247
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21248
|
+
* rather than one unbounded result set.
|
|
21249
|
+
*
|
|
21250
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21251
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21252
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21253
|
+
* a window over the whole resolution table once per batch.
|
|
21254
|
+
*
|
|
21255
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21256
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21257
|
+
* dimension — see listFindingInstances.
|
|
21258
|
+
*/
|
|
21259
|
+
*scanFindingRows(scope) {
|
|
21260
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21261
|
+
const params = [];
|
|
21262
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21263
|
+
conditions.push("e.root_session_id = ?");
|
|
21264
|
+
params.push(scope.sessionId);
|
|
21265
|
+
}
|
|
21266
|
+
if (scope.from !== void 0) {
|
|
21267
|
+
conditions.push("e.started_at >= ?");
|
|
21268
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21269
|
+
}
|
|
21270
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21271
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21272
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21273
|
+
e.started_at AS occurred_at,
|
|
21274
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21275
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21276
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21277
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21278
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21279
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21280
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21281
|
+
FROM inspection_findings f
|
|
21282
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21283
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21284
|
+
WHERE ${conditions.join(" AND ")}
|
|
21285
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21286
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21287
|
+
LIMIT ?`;
|
|
21288
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21289
|
+
for (; ; ) {
|
|
21290
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21291
|
+
...params,
|
|
21292
|
+
after.startedAtMs,
|
|
21293
|
+
after.startedAtMs,
|
|
21294
|
+
after.id,
|
|
21295
|
+
SCAN_BATCH_ROWS
|
|
21296
|
+
]);
|
|
21297
|
+
for (const r of rows) {
|
|
21298
|
+
yield {
|
|
21299
|
+
id: r.id,
|
|
21300
|
+
ruleId: r.rule_id,
|
|
21301
|
+
category: r.category,
|
|
21302
|
+
severity: r.severity,
|
|
21303
|
+
maskedMatch: r.masked_match,
|
|
21304
|
+
actionTaken: r.action_taken,
|
|
21305
|
+
confidence: r.confidence,
|
|
21306
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21307
|
+
sourceTool: r.source_tool,
|
|
21308
|
+
repo: r.repo ?? "",
|
|
21309
|
+
file: r.file ?? "",
|
|
21310
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21311
|
+
eventId: r.event_id,
|
|
21312
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21313
|
+
status: deriveInstanceStatus(r)
|
|
21314
|
+
};
|
|
21315
|
+
}
|
|
21316
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21317
|
+
const lastRow = rows[rows.length - 1];
|
|
21318
|
+
if (lastRow === void 0) return;
|
|
21319
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21320
|
+
}
|
|
21321
|
+
}
|
|
20524
21322
|
groupAggregates(withSearchText, scope) {
|
|
20525
21323
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20526
21324
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20781,7 +21579,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20781
21579
|
};
|
|
20782
21580
|
|
|
20783
21581
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20784
|
-
import { createHash as createHash2, randomUUID as
|
|
21582
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20785
21583
|
|
|
20786
21584
|
// ../../packages/persistence/src/semver.ts
|
|
20787
21585
|
function parse3(version2) {
|
|
@@ -20932,7 +21730,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20932
21730
|
let behind = false;
|
|
20933
21731
|
for (const row of rows) {
|
|
20934
21732
|
const params = {
|
|
20935
|
-
id:
|
|
21733
|
+
id: randomUUID3(),
|
|
20936
21734
|
namespace: row.namespace,
|
|
20937
21735
|
packId: row.packId,
|
|
20938
21736
|
version: row.version,
|
|
@@ -20944,7 +21742,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20944
21742
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20945
21743
|
this.upsertAvailableStmt.run({
|
|
20946
21744
|
...params,
|
|
20947
|
-
id:
|
|
21745
|
+
id: randomUUID3(),
|
|
20948
21746
|
recordedBy: meta3?.recordedBy ?? null
|
|
20949
21747
|
});
|
|
20950
21748
|
} else {
|
|
@@ -21267,14 +22065,15 @@ var SqliteInventoryRepository = class {
|
|
|
21267
22065
|
};
|
|
21268
22066
|
|
|
21269
22067
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21270
|
-
import { randomUUID as
|
|
22068
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21271
22069
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21272
22070
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21273
22071
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21274
22072
|
var HARNESS_LABELS = {
|
|
21275
22073
|
claudecode: "Claude Code",
|
|
21276
22074
|
cursor: "Cursor",
|
|
21277
|
-
codex: "Codex"
|
|
22075
|
+
codex: "Codex",
|
|
22076
|
+
antigravity: "Antigravity"
|
|
21278
22077
|
};
|
|
21279
22078
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21280
22079
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21289,6 +22088,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21289
22088
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21290
22089
|
if (t.includes("cursor")) return "cursor";
|
|
21291
22090
|
if (t.includes("codex")) return "codex";
|
|
22091
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21292
22092
|
return null;
|
|
21293
22093
|
}
|
|
21294
22094
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21747,7 +22547,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21747
22547
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21748
22548
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21749
22549
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21750
|
-
).run({ id:
|
|
22550
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21751
22551
|
}
|
|
21752
22552
|
return true;
|
|
21753
22553
|
}
|
|
@@ -21768,7 +22568,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21768
22568
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21769
22569
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21770
22570
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21771
|
-
).run({ id:
|
|
22571
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21772
22572
|
}
|
|
21773
22573
|
this.configRowsCache = void 0;
|
|
21774
22574
|
return "ok";
|
|
@@ -22065,7 +22865,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22065
22865
|
};
|
|
22066
22866
|
|
|
22067
22867
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22068
|
-
import { randomUUID as
|
|
22868
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22069
22869
|
var SqlitePoliciesRepository = class {
|
|
22070
22870
|
constructor(db) {
|
|
22071
22871
|
this.db = db;
|
|
@@ -22100,7 +22900,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22100
22900
|
failOpenTransaction(this.db, () => {
|
|
22101
22901
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22102
22902
|
stmt.run({
|
|
22103
|
-
id:
|
|
22903
|
+
id: randomUUID5(),
|
|
22104
22904
|
target: JSON.stringify({ category }),
|
|
22105
22905
|
action,
|
|
22106
22906
|
now: Date.now()
|
|
@@ -22120,7 +22920,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22120
22920
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22121
22921
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22122
22922
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22123
|
-
).run({ id:
|
|
22923
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22124
22924
|
}
|
|
22125
22925
|
// Caps every global per-category policy currently set to block/redact down
|
|
22126
22926
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22188,7 +22988,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22188
22988
|
};
|
|
22189
22989
|
|
|
22190
22990
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22191
|
-
import { randomUUID as
|
|
22991
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22192
22992
|
var SqliteProjectFilesRepository = class {
|
|
22193
22993
|
constructor(db) {
|
|
22194
22994
|
this.db = db;
|
|
@@ -22220,7 +23020,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22220
23020
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22221
23021
|
for (const file2 of scan2.files) {
|
|
22222
23022
|
this.upsertStmt.run({
|
|
22223
|
-
id:
|
|
23023
|
+
id: randomUUID6(),
|
|
22224
23024
|
projectId,
|
|
22225
23025
|
path: file2.path,
|
|
22226
23026
|
name: file2.name,
|
|
@@ -22234,7 +23034,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22234
23034
|
};
|
|
22235
23035
|
|
|
22236
23036
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22237
|
-
import { randomUUID as
|
|
23037
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22238
23038
|
var SqliteResolutionsRepository = class {
|
|
22239
23039
|
constructor(db, now = () => Date.now()) {
|
|
22240
23040
|
this.db = db;
|
|
@@ -22288,7 +23088,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22288
23088
|
*/
|
|
22289
23089
|
insertResolution(r) {
|
|
22290
23090
|
this.insertStmt.run({
|
|
22291
|
-
id:
|
|
23091
|
+
id: randomUUID7(),
|
|
22292
23092
|
findingKey: r.findingKey,
|
|
22293
23093
|
status: FindingStatus.parse(r.status),
|
|
22294
23094
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22347,13 +23147,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22347
23147
|
this.readStmt = db.prepare(
|
|
22348
23148
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22349
23149
|
);
|
|
23150
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23151
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23152
|
+
);
|
|
23153
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23154
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23155
|
+
);
|
|
22350
23156
|
}
|
|
22351
23157
|
db;
|
|
22352
23158
|
upsertStmt;
|
|
22353
23159
|
readStmt;
|
|
23160
|
+
countQuarantinedStmt;
|
|
23161
|
+
clearQuarantinedStmt;
|
|
22354
23162
|
getVerdict(ruleKey) {
|
|
22355
23163
|
return getRow(this.readStmt, { ruleKey });
|
|
22356
23164
|
}
|
|
23165
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23166
|
+
countQuarantined() {
|
|
23167
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23168
|
+
}
|
|
23169
|
+
/**
|
|
23170
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23171
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23172
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23173
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23174
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23175
|
+
*
|
|
23176
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23177
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23178
|
+
*
|
|
23179
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23180
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23181
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23182
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23183
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23184
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23185
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23186
|
+
* to restore are silently still disabled.
|
|
23187
|
+
*/
|
|
23188
|
+
clearQuarantined() {
|
|
23189
|
+
const before = this.countQuarantined();
|
|
23190
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23191
|
+
this.clearQuarantinedStmt.run();
|
|
23192
|
+
});
|
|
23193
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23194
|
+
}
|
|
22357
23195
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22358
23196
|
failOpenTransaction(this.db, () => {
|
|
22359
23197
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22408,7 +23246,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22408
23246
|
};
|
|
22409
23247
|
|
|
22410
23248
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22411
|
-
import { randomUUID as
|
|
23249
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23250
|
+
function pageLimit(requested, fallback) {
|
|
23251
|
+
if (requested === void 0) return fallback;
|
|
23252
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23253
|
+
}
|
|
23254
|
+
function encodeReuseCursor(payload) {
|
|
23255
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23256
|
+
}
|
|
23257
|
+
function decodeReuseCursor(cursor) {
|
|
23258
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23259
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23260
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23261
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23262
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23263
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23264
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23265
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23266
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23267
|
+
}
|
|
23268
|
+
return null;
|
|
23269
|
+
}
|
|
23270
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23271
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23272
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23273
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23274
|
+
function toSighting(row) {
|
|
23275
|
+
return {
|
|
23276
|
+
location: row.location,
|
|
23277
|
+
kind: row.kind,
|
|
23278
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23279
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23280
|
+
};
|
|
23281
|
+
}
|
|
22412
23282
|
var SELECT_COLUMNS = `
|
|
22413
23283
|
pointer_id AS pointerId,
|
|
22414
23284
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22592,39 +23462,110 @@ var SqliteSecretVaultRepository = class {
|
|
|
22592
23462
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22593
23463
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22594
23464
|
).run({
|
|
22595
|
-
id:
|
|
23465
|
+
id: randomUUID8(),
|
|
22596
23466
|
pointerId: entry.pointerId,
|
|
22597
23467
|
location: entry.location,
|
|
22598
23468
|
kind: entry.kind,
|
|
22599
23469
|
now
|
|
22600
23470
|
});
|
|
22601
23471
|
}
|
|
22602
|
-
|
|
23472
|
+
/**
|
|
23473
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23474
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23475
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23476
|
+
*
|
|
23477
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23478
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23479
|
+
*/
|
|
23480
|
+
sightingsFor(pointerIds) {
|
|
23481
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23482
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22603
23483
|
const rows = allRows(
|
|
22604
23484
|
this.db.prepare(
|
|
22605
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22606
|
-
|
|
23485
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23486
|
+
FROM secret_vault_sighting
|
|
23487
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23488
|
+
ORDER BY last_seen DESC`
|
|
22607
23489
|
),
|
|
22608
|
-
|
|
23490
|
+
pointerIds
|
|
22609
23491
|
);
|
|
23492
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23493
|
+
return byPointer;
|
|
23494
|
+
}
|
|
23495
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23496
|
+
toInventoryEntries(rows) {
|
|
23497
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22610
23498
|
return rows.map((r) => ({
|
|
22611
|
-
|
|
22612
|
-
|
|
23499
|
+
pointerId: r.pointer_id,
|
|
23500
|
+
category: r.category,
|
|
23501
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23502
|
+
maskedMatch: r.masked_match,
|
|
23503
|
+
occurrences: r.occurrence_count,
|
|
22613
23504
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22614
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23505
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23506
|
+
revealGrantId: r.grant_id,
|
|
23507
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22615
23508
|
}));
|
|
22616
23509
|
}
|
|
22617
23510
|
/**
|
|
22618
|
-
* The dashboard inventory
|
|
22619
|
-
* its sightings and the active
|
|
22620
|
-
* Raw-free by construction — neither
|
|
22621
|
-
* columns are selected.
|
|
23511
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23512
|
+
* value's descriptor data joined with its sightings and the active
|
|
23513
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23514
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23515
|
+
*
|
|
23516
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23517
|
+
* sees never depends on how far they have paged.
|
|
23518
|
+
*/
|
|
23519
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23520
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23521
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23522
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
23523
|
+
const rows = allRows(
|
|
23524
|
+
this.db.prepare(
|
|
23525
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23526
|
+
(SELECT e.id FROM exceptions e
|
|
23527
|
+
WHERE e.rule_id = v.rule_id
|
|
23528
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23529
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23530
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23531
|
+
LIMIT 1) AS grant_id
|
|
23532
|
+
FROM secret_vault v
|
|
23533
|
+
${where}
|
|
23534
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23535
|
+
LIMIT :limit`
|
|
23536
|
+
),
|
|
23537
|
+
bindParams({
|
|
23538
|
+
now,
|
|
23539
|
+
limit: limit + 1,
|
|
23540
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23541
|
+
})
|
|
23542
|
+
);
|
|
23543
|
+
const hasMore = rows.length > limit;
|
|
23544
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23545
|
+
const last = page[page.length - 1];
|
|
23546
|
+
return {
|
|
23547
|
+
totals: { values: this.countEntries() },
|
|
23548
|
+
items: this.toInventoryEntries(page),
|
|
23549
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23550
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23551
|
+
};
|
|
23552
|
+
}
|
|
23553
|
+
/**
|
|
23554
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23555
|
+
* than one location — most-reused first, one page at a time.
|
|
23556
|
+
*
|
|
23557
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23558
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23559
|
+
* under-report exactly the values a reader most needs to see.
|
|
22622
23560
|
*/
|
|
22623
|
-
|
|
23561
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23562
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23563
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23564
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23565
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
22624
23566
|
const rows = allRows(
|
|
22625
23567
|
this.db.prepare(
|
|
22626
|
-
`SELECT
|
|
22627
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23568
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22628
23569
|
(SELECT e.id FROM exceptions e
|
|
22629
23570
|
WHERE e.rule_id = v.rule_id
|
|
22630
23571
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22632,45 +23573,66 @@ var SqliteSecretVaultRepository = class {
|
|
|
22632
23573
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22633
23574
|
LIMIT 1) AS grant_id
|
|
22634
23575
|
FROM secret_vault v
|
|
22635
|
-
|
|
23576
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23577
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23578
|
+
LIMIT :limit`
|
|
22636
23579
|
),
|
|
22637
|
-
{
|
|
23580
|
+
bindParams({
|
|
23581
|
+
now,
|
|
23582
|
+
limit: limit + 1,
|
|
23583
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23584
|
+
})
|
|
22638
23585
|
);
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
|
|
22642
|
-
|
|
22643
|
-
|
|
22644
|
-
|
|
22645
|
-
|
|
22646
|
-
|
|
22647
|
-
revealGrantId: r.grant_id,
|
|
22648
|
-
sightings: this.listSightings(r.pointer_id)
|
|
22649
|
-
}));
|
|
23586
|
+
const hasMore = rows.length > limit;
|
|
23587
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23588
|
+
const last = page[page.length - 1];
|
|
23589
|
+
return {
|
|
23590
|
+
totals: { reused: this.countReused() },
|
|
23591
|
+
items: this.toInventoryEntries(page),
|
|
23592
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23593
|
+
};
|
|
22650
23594
|
}
|
|
22651
23595
|
/**
|
|
22652
|
-
* The de-reference trail, newest first. By default the
|
|
22653
|
-
* reasons (display, view-render) are hidden and counted
|
|
22654
|
-
* that matter as a signal are the model crossings, and
|
|
22655
|
-
* render noise would defeat the audit's purpose.
|
|
23596
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23597
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23598
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23599
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23600
|
+
*
|
|
23601
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23602
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23603
|
+
* the reader pages.
|
|
22656
23604
|
*/
|
|
22657
|
-
listDerefs(
|
|
22658
|
-
const limit =
|
|
22659
|
-
const
|
|
23605
|
+
listDerefs(query = {}) {
|
|
23606
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23607
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23608
|
+
const conditions = [];
|
|
23609
|
+
if (query.includeBatched !== true) {
|
|
23610
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23611
|
+
}
|
|
23612
|
+
if (cursor !== null) {
|
|
23613
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23614
|
+
}
|
|
23615
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22660
23616
|
const rows = allRows(
|
|
22661
23617
|
this.db.prepare(
|
|
22662
23618
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22663
23619
|
FROM secret_vault_deref ${where}
|
|
22664
|
-
ORDER BY at DESC,
|
|
23620
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22665
23621
|
),
|
|
22666
|
-
{
|
|
23622
|
+
bindParams({
|
|
23623
|
+
limit: limit + 1,
|
|
23624
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23625
|
+
})
|
|
22667
23626
|
);
|
|
22668
|
-
const
|
|
23627
|
+
const hasMore = rows.length > limit;
|
|
23628
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23629
|
+
const last = page[page.length - 1];
|
|
23630
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22669
23631
|
this.db,
|
|
22670
23632
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22671
23633
|
);
|
|
22672
23634
|
return {
|
|
22673
|
-
|
|
23635
|
+
items: page.map((r) => ({
|
|
22674
23636
|
id: r.id,
|
|
22675
23637
|
pointerId: r.pointer_id,
|
|
22676
23638
|
at: new Date(r.at).toISOString(),
|
|
@@ -22680,12 +23642,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22680
23642
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22681
23643
|
pointerCount: r.pointer_count
|
|
22682
23644
|
})),
|
|
23645
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22683
23646
|
hiddenBatched
|
|
22684
23647
|
};
|
|
22685
23648
|
}
|
|
22686
23649
|
countEntries() {
|
|
22687
23650
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22688
23651
|
}
|
|
23652
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23653
|
+
countReused() {
|
|
23654
|
+
return countScalar(
|
|
23655
|
+
this.db,
|
|
23656
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23657
|
+
);
|
|
23658
|
+
}
|
|
22689
23659
|
};
|
|
22690
23660
|
|
|
22691
23661
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22700,7 +23670,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22700
23670
|
var SCAN_COVERAGE = [
|
|
22701
23671
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22702
23672
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22703
|
-
{ provider: "codex", coverage:
|
|
23673
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23674
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23675
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22704
23676
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22705
23677
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22706
23678
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23033,7 +24005,7 @@ var SqliteSecurityRepository = class {
|
|
|
23033
24005
|
};
|
|
23034
24006
|
|
|
23035
24007
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23036
|
-
import { randomUUID as
|
|
24008
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23037
24009
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23038
24010
|
var IN_CHUNK = 500;
|
|
23039
24011
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23289,7 +24261,7 @@ var SqliteSharesRepository = class {
|
|
|
23289
24261
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23290
24262
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23291
24263
|
).run({
|
|
23292
|
-
id:
|
|
24264
|
+
id: randomUUID9(),
|
|
23293
24265
|
destinationId,
|
|
23294
24266
|
host: dest.host,
|
|
23295
24267
|
decision,
|
|
@@ -23438,7 +24410,7 @@ var SqliteSharesRepository = class {
|
|
|
23438
24410
|
let destinationId = destIds.get(hit.host);
|
|
23439
24411
|
if (destinationId === void 0) {
|
|
23440
24412
|
destStmt.run({
|
|
23441
|
-
id:
|
|
24413
|
+
id: randomUUID9(),
|
|
23442
24414
|
kind: hit.kind,
|
|
23443
24415
|
name: hit.name,
|
|
23444
24416
|
host: hit.host,
|
|
@@ -23454,7 +24426,7 @@ var SqliteSharesRepository = class {
|
|
|
23454
24426
|
let endpointId = endpointIds.get(endpointKey);
|
|
23455
24427
|
if (endpointId === void 0) {
|
|
23456
24428
|
endpointStmt.run({
|
|
23457
|
-
id:
|
|
24429
|
+
id: randomUUID9(),
|
|
23458
24430
|
destinationId,
|
|
23459
24431
|
method: hit.method,
|
|
23460
24432
|
transport: hit.transport,
|
|
@@ -23467,7 +24439,7 @@ var SqliteSharesRepository = class {
|
|
|
23467
24439
|
endpointIds.set(endpointKey, endpointId);
|
|
23468
24440
|
}
|
|
23469
24441
|
siteStmt.run({
|
|
23470
|
-
id:
|
|
24442
|
+
id: randomUUID9(),
|
|
23471
24443
|
endpointId,
|
|
23472
24444
|
project: input.project,
|
|
23473
24445
|
projectKey: input.projectKey,
|
|
@@ -23832,6 +24804,9 @@ function purgeSampleData(db) {
|
|
|
23832
24804
|
}
|
|
23833
24805
|
|
|
23834
24806
|
// ../../packages/persistence/src/database.ts
|
|
24807
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24808
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24809
|
+
);
|
|
23835
24810
|
function linkHost(input, hostId) {
|
|
23836
24811
|
return hostId ? { ...input, hostId } : input;
|
|
23837
24812
|
}
|
|
@@ -23853,21 +24828,34 @@ function openWithPragmas(file2) {
|
|
|
23853
24828
|
}
|
|
23854
24829
|
return db;
|
|
23855
24830
|
}
|
|
23856
|
-
function backupLegacyStore(file2) {
|
|
23857
|
-
|
|
23858
|
-
|
|
23859
|
-
|
|
23860
|
-
|
|
23861
|
-
|
|
24831
|
+
function backupLegacyStore(db, file2) {
|
|
24832
|
+
reapStalePartials(file2);
|
|
24833
|
+
const backup = backupPath(file2, "legacy");
|
|
24834
|
+
let snapshotted = false;
|
|
24835
|
+
let snapshotError;
|
|
24836
|
+
try {
|
|
24837
|
+
snapshotStore(db, backup);
|
|
24838
|
+
snapshotted = true;
|
|
24839
|
+
} catch (error51) {
|
|
24840
|
+
snapshotError = error51;
|
|
24841
|
+
} finally {
|
|
24842
|
+
db.close();
|
|
24843
|
+
}
|
|
24844
|
+
if (!snapshotted) {
|
|
24845
|
+
akaWarn(
|
|
24846
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24847
|
+
);
|
|
24848
|
+
moveStoreAside(file2, backup);
|
|
24849
|
+
return backup;
|
|
23862
24850
|
}
|
|
24851
|
+
discardStore(file2, backup);
|
|
23863
24852
|
return backup;
|
|
23864
24853
|
}
|
|
23865
24854
|
function openAndInitialize(file2) {
|
|
23866
24855
|
let db = openWithPragmas(file2);
|
|
23867
24856
|
try {
|
|
23868
24857
|
if (isForeignSqliteLineage(db)) {
|
|
23869
|
-
db
|
|
23870
|
-
const backup = backupLegacyStore(file2);
|
|
24858
|
+
const backup = backupLegacyStore(db, file2);
|
|
23871
24859
|
db = openWithPragmas(file2);
|
|
23872
24860
|
akaWarn(
|
|
23873
24861
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23911,7 +24899,7 @@ function openAndInitialize(file2) {
|
|
|
23911
24899
|
}
|
|
23912
24900
|
function openLocalDatabase(dir) {
|
|
23913
24901
|
ensureDataDirSync(dir);
|
|
23914
|
-
const file2 =
|
|
24902
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23915
24903
|
const {
|
|
23916
24904
|
db,
|
|
23917
24905
|
events,
|
|
@@ -24028,7 +25016,7 @@ function openLocalDatabase(dir) {
|
|
|
24028
25016
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24029
25017
|
if (!definitionId) continue;
|
|
24030
25018
|
inspectionFindings.insertFinding({
|
|
24031
|
-
id:
|
|
25019
|
+
id: randomUUID10(),
|
|
24032
25020
|
auditEventId: record2.scanEvent.id,
|
|
24033
25021
|
inspectionDefinitionId: definitionId,
|
|
24034
25022
|
span: finding.span,
|
|
@@ -24134,35 +25122,251 @@ function openLocalDatabase(dir) {
|
|
|
24134
25122
|
transaction,
|
|
24135
25123
|
close: () => {
|
|
24136
25124
|
db.close();
|
|
24137
|
-
}
|
|
25125
|
+
},
|
|
25126
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25127
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24138
25128
|
};
|
|
24139
25129
|
}
|
|
24140
25130
|
|
|
25131
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25132
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25133
|
+
import {
|
|
25134
|
+
closeSync,
|
|
25135
|
+
existsSync as existsSync2,
|
|
25136
|
+
openSync,
|
|
25137
|
+
readFileSync,
|
|
25138
|
+
rmSync as rmSync3,
|
|
25139
|
+
statSync as statSync2,
|
|
25140
|
+
writeFileSync as writeFileSync2
|
|
25141
|
+
} from "fs";
|
|
25142
|
+
import { hostname as hostname3 } from "os";
|
|
25143
|
+
var LOCK_SUFFIX = ".lock";
|
|
25144
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
25145
|
+
var DEFAULT_STALE_MS = 2e3;
|
|
25146
|
+
var RETRY_INTERVAL_MS = 5;
|
|
25147
|
+
var RETRYABLE_CREATE_ERRNOS = /* @__PURE__ */ new Set(["EEXIST", "EACCES", "EPERM", "EBUSY"]);
|
|
25148
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25149
|
+
function sleepSync(ms) {
|
|
25150
|
+
Atomics.wait(PARK, 0, 0, ms);
|
|
25151
|
+
}
|
|
25152
|
+
var FileLockError = class extends Error {
|
|
25153
|
+
reason;
|
|
25154
|
+
file;
|
|
25155
|
+
holderPid;
|
|
25156
|
+
constructor(reason, file2, detail, holderPid, options) {
|
|
25157
|
+
super(
|
|
25158
|
+
`cannot lock ${file2} for writing: ${detail}` + (holderPid === void 0 ? "" : ` (held by pid ${String(holderPid)})`),
|
|
25159
|
+
options
|
|
25160
|
+
);
|
|
25161
|
+
this.name = "FileLockError";
|
|
25162
|
+
this.reason = reason;
|
|
25163
|
+
this.file = file2;
|
|
25164
|
+
this.holderPid = holderPid;
|
|
25165
|
+
}
|
|
25166
|
+
};
|
|
25167
|
+
function lockPathFor(file2) {
|
|
25168
|
+
return `${file2}${LOCK_SUFFIX}`;
|
|
25169
|
+
}
|
|
25170
|
+
function readLockBody(lock) {
|
|
25171
|
+
let raw;
|
|
25172
|
+
try {
|
|
25173
|
+
raw = readFileSync(lock, "utf8");
|
|
25174
|
+
} catch {
|
|
25175
|
+
return null;
|
|
25176
|
+
}
|
|
25177
|
+
try {
|
|
25178
|
+
const parsed = JSON.parse(raw);
|
|
25179
|
+
const { pid, token, at, host } = parsed;
|
|
25180
|
+
if (typeof pid !== "number" || typeof token !== "string" || typeof at !== "number") return null;
|
|
25181
|
+
return { pid, token, at, host: typeof host === "string" ? host : "" };
|
|
25182
|
+
} catch {
|
|
25183
|
+
return null;
|
|
25184
|
+
}
|
|
25185
|
+
}
|
|
25186
|
+
function holderIsAlive(pid) {
|
|
25187
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
25188
|
+
try {
|
|
25189
|
+
process.kill(pid, 0);
|
|
25190
|
+
return true;
|
|
25191
|
+
} catch (err) {
|
|
25192
|
+
return err.code !== "ESRCH";
|
|
25193
|
+
}
|
|
25194
|
+
}
|
|
25195
|
+
function directoryAcceptsCreates(lock) {
|
|
25196
|
+
const probe = `${lock}.probe-${randomUUID11()}`;
|
|
25197
|
+
try {
|
|
25198
|
+
closeSync(openSync(probe, "wx", DATA_FILE_MODE));
|
|
25199
|
+
return true;
|
|
25200
|
+
} catch {
|
|
25201
|
+
return false;
|
|
25202
|
+
} finally {
|
|
25203
|
+
try {
|
|
25204
|
+
rmSync3(probe, { force: true });
|
|
25205
|
+
} catch {
|
|
25206
|
+
}
|
|
25207
|
+
}
|
|
25208
|
+
}
|
|
25209
|
+
function tryAcquire(lock, file2) {
|
|
25210
|
+
const token = randomUUID11();
|
|
25211
|
+
let fd;
|
|
25212
|
+
try {
|
|
25213
|
+
fd = openSync(lock, "wx", DATA_FILE_MODE);
|
|
25214
|
+
} catch (err) {
|
|
25215
|
+
const code = err.code ?? "";
|
|
25216
|
+
if (code === "EEXIST") return null;
|
|
25217
|
+
if (RETRYABLE_CREATE_ERRNOS.has(code) && (existsSync2(lock) || directoryAcceptsCreates(lock))) {
|
|
25218
|
+
return null;
|
|
25219
|
+
}
|
|
25220
|
+
throw new FileLockError(
|
|
25221
|
+
"unavailable",
|
|
25222
|
+
file2,
|
|
25223
|
+
err instanceof Error ? err.message : String(err),
|
|
25224
|
+
void 0,
|
|
25225
|
+
{ cause: err }
|
|
25226
|
+
);
|
|
25227
|
+
}
|
|
25228
|
+
const body = { pid: process.pid, token, at: Date.now(), host: hostname3() };
|
|
25229
|
+
try {
|
|
25230
|
+
writeFileSync2(fd, `${JSON.stringify(body)}
|
|
25231
|
+
`);
|
|
25232
|
+
} catch {
|
|
25233
|
+
try {
|
|
25234
|
+
closeSync(fd);
|
|
25235
|
+
} catch {
|
|
25236
|
+
}
|
|
25237
|
+
rmSync3(lock, { force: true });
|
|
25238
|
+
return null;
|
|
25239
|
+
}
|
|
25240
|
+
try {
|
|
25241
|
+
closeSync(fd);
|
|
25242
|
+
} catch {
|
|
25243
|
+
}
|
|
25244
|
+
return token;
|
|
25245
|
+
}
|
|
25246
|
+
function isAbandoned(body, lock, staleMs) {
|
|
25247
|
+
if (!body) {
|
|
25248
|
+
try {
|
|
25249
|
+
return Date.now() - statSync2(lock).mtimeMs >= staleMs;
|
|
25250
|
+
} catch {
|
|
25251
|
+
return false;
|
|
25252
|
+
}
|
|
25253
|
+
}
|
|
25254
|
+
if (Date.now() - body.at < staleMs) return false;
|
|
25255
|
+
if (body.host !== hostname3() || !holderIsAlive(body.pid)) return true;
|
|
25256
|
+
return Date.now() - body.at >= abandonWindow(staleMs);
|
|
25257
|
+
}
|
|
25258
|
+
function breakIfStale(lock, staleMs) {
|
|
25259
|
+
const breaker = `${lock}.break`;
|
|
25260
|
+
let fd;
|
|
25261
|
+
try {
|
|
25262
|
+
fd = openSync(breaker, "wx", DATA_FILE_MODE);
|
|
25263
|
+
} catch {
|
|
25264
|
+
reapAbandonedBreaker(breaker);
|
|
25265
|
+
return false;
|
|
25266
|
+
}
|
|
25267
|
+
try {
|
|
25268
|
+
closeSync(fd);
|
|
25269
|
+
} catch {
|
|
25270
|
+
}
|
|
25271
|
+
try {
|
|
25272
|
+
if (!existsSync2(lock) || !isAbandoned(readLockBody(lock), lock, staleMs)) return false;
|
|
25273
|
+
rmSync3(lock, { force: true });
|
|
25274
|
+
return true;
|
|
25275
|
+
} catch {
|
|
25276
|
+
return false;
|
|
25277
|
+
} finally {
|
|
25278
|
+
try {
|
|
25279
|
+
rmSync3(breaker, { force: true });
|
|
25280
|
+
} catch {
|
|
25281
|
+
}
|
|
25282
|
+
}
|
|
25283
|
+
}
|
|
25284
|
+
var BREAKER_ABANDONED_MS = 1e4;
|
|
25285
|
+
function reapAbandonedBreaker(breaker) {
|
|
25286
|
+
try {
|
|
25287
|
+
if (Date.now() - statSync2(breaker).mtimeMs >= BREAKER_ABANDONED_MS) {
|
|
25288
|
+
rmSync3(breaker, { force: true });
|
|
25289
|
+
}
|
|
25290
|
+
} catch {
|
|
25291
|
+
}
|
|
25292
|
+
}
|
|
25293
|
+
function abandonWindow(staleMs) {
|
|
25294
|
+
return Math.max(staleMs * 30, 6e4);
|
|
25295
|
+
}
|
|
25296
|
+
function release(lock, token) {
|
|
25297
|
+
try {
|
|
25298
|
+
if (readLockBody(lock)?.token !== token) return;
|
|
25299
|
+
rmSync3(lock, { force: true });
|
|
25300
|
+
} catch {
|
|
25301
|
+
}
|
|
25302
|
+
}
|
|
25303
|
+
function withFileLock(file2, fn, options = {}) {
|
|
25304
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
25305
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
25306
|
+
const lock = lockPathFor(file2);
|
|
25307
|
+
const deadline = Date.now() + timeoutMs;
|
|
25308
|
+
let token = tryAcquire(lock, file2);
|
|
25309
|
+
while (token === null) {
|
|
25310
|
+
if (breakIfStale(lock, staleMs)) {
|
|
25311
|
+
token = tryAcquire(lock, file2);
|
|
25312
|
+
continue;
|
|
25313
|
+
}
|
|
25314
|
+
if (Date.now() >= deadline) {
|
|
25315
|
+
throw new FileLockError(
|
|
25316
|
+
"timeout",
|
|
25317
|
+
file2,
|
|
25318
|
+
`still held after ${String(timeoutMs)}ms`,
|
|
25319
|
+
readLockBody(lock)?.pid
|
|
25320
|
+
);
|
|
25321
|
+
}
|
|
25322
|
+
sleepSync(RETRY_INTERVAL_MS);
|
|
25323
|
+
token = tryAcquire(lock, file2);
|
|
25324
|
+
}
|
|
25325
|
+
try {
|
|
25326
|
+
const result = fn();
|
|
25327
|
+
if (isThenable(result)) {
|
|
25328
|
+
void result.then(
|
|
25329
|
+
() => void 0,
|
|
25330
|
+
() => void 0
|
|
25331
|
+
);
|
|
25332
|
+
throw new TypeError(
|
|
25333
|
+
`withFileLock(${file2}) was given an async body; the lock is released as soon as it returns, so the awaited work would run unguarded. Pass a synchronous function.`
|
|
25334
|
+
);
|
|
25335
|
+
}
|
|
25336
|
+
return result;
|
|
25337
|
+
} finally {
|
|
25338
|
+
release(lock, token);
|
|
25339
|
+
}
|
|
25340
|
+
}
|
|
25341
|
+
function isThenable(value) {
|
|
25342
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
25343
|
+
}
|
|
25344
|
+
|
|
24141
25345
|
// ../../packages/persistence/src/finding-key.ts
|
|
24142
25346
|
import { createHash as createHash3 } from "crypto";
|
|
24143
25347
|
|
|
24144
25348
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24145
25349
|
import { createHmac, randomBytes } from "crypto";
|
|
24146
|
-
import { existsSync as
|
|
24147
|
-
import { join as
|
|
25350
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25351
|
+
import { join as join3 } from "path";
|
|
24148
25352
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24149
25353
|
|
|
24150
25354
|
// ../../packages/persistence/src/local-layout.ts
|
|
24151
25355
|
import { renameSync as renameSync3 } from "fs";
|
|
24152
25356
|
import { mkdir } from "fs/promises";
|
|
24153
25357
|
import { homedir } from "os";
|
|
24154
|
-
import { join as
|
|
25358
|
+
import { join as join4 } from "path";
|
|
24155
25359
|
function defaultDataDir() {
|
|
24156
|
-
return
|
|
25360
|
+
return join4(homedir(), ".aka");
|
|
24157
25361
|
}
|
|
24158
25362
|
function settingsDir(base = defaultDataDir()) {
|
|
24159
|
-
return
|
|
25363
|
+
return join4(base, "settings");
|
|
24160
25364
|
}
|
|
24161
25365
|
function dataDir(base = defaultDataDir()) {
|
|
24162
|
-
return
|
|
25366
|
+
return join4(base, "data");
|
|
24163
25367
|
}
|
|
24164
25368
|
function dbPath(base = defaultDataDir()) {
|
|
24165
|
-
return
|
|
25369
|
+
return join4(dataDir(base), "aka.db");
|
|
24166
25370
|
}
|
|
24167
25371
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24168
25372
|
ensureDataDirSync(dir);
|
|
@@ -24175,8 +25379,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24175
25379
|
for (const { name, dest } of moves) {
|
|
24176
25380
|
try {
|
|
24177
25381
|
ensureDataDirSync(dest);
|
|
24178
|
-
const moved =
|
|
24179
|
-
renameSync3(
|
|
25382
|
+
const moved = join4(dest, name);
|
|
25383
|
+
renameSync3(join4(base, name), moved);
|
|
24180
25384
|
tightenFile(moved);
|
|
24181
25385
|
} catch {
|
|
24182
25386
|
}
|
|
@@ -24184,10 +25388,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24184
25388
|
}
|
|
24185
25389
|
|
|
24186
25390
|
// ../../packages/persistence/src/settings.ts
|
|
24187
|
-
import { readFileSync as
|
|
24188
|
-
import { join as
|
|
25391
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25392
|
+
import { join as join5 } from "path";
|
|
25393
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24189
25394
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24190
|
-
const record2 = readJson(
|
|
25395
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24191
25396
|
if (!record2) return defaultWorkspaceSettings();
|
|
24192
25397
|
try {
|
|
24193
25398
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24197,23 +25402,26 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24197
25402
|
}
|
|
24198
25403
|
function applyOnboarding(answers2, base = defaultDataDir()) {
|
|
24199
25404
|
const dir = settingsDir(base);
|
|
24200
|
-
const current = readWorkspaceSettings(base);
|
|
24201
|
-
const merged = WorkspaceSettings.parse({
|
|
24202
|
-
...current,
|
|
24203
|
-
...answers2,
|
|
24204
|
-
// First setup stamps the time; later edits keep the original completion mark.
|
|
24205
|
-
onboardedAt: answers2.onboardedAt ?? current.onboardedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
24206
|
-
});
|
|
24207
25405
|
ensureDataDirSync(dir);
|
|
24208
|
-
const file2 =
|
|
24209
|
-
|
|
25406
|
+
const file2 = join5(dir, SETTINGS_FILENAME);
|
|
25407
|
+
return withFileLock(file2, () => {
|
|
25408
|
+
const current = readWorkspaceSettings(base);
|
|
25409
|
+
const applied = typeof answers2 === "function" ? answers2(current) : answers2;
|
|
25410
|
+
const merged = WorkspaceSettings.parse({
|
|
25411
|
+
...current,
|
|
25412
|
+
...applied,
|
|
25413
|
+
// First setup stamps the time; later edits keep the original completion mark.
|
|
25414
|
+
onboardedAt: applied.onboardedAt ?? current.onboardedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
25415
|
+
});
|
|
25416
|
+
writeOwnerOnlyFileSync(file2, `${JSON.stringify(merged, null, 2)}
|
|
24210
25417
|
`);
|
|
24211
|
-
|
|
25418
|
+
return merged;
|
|
25419
|
+
});
|
|
24212
25420
|
}
|
|
24213
25421
|
function readJson(file2) {
|
|
24214
25422
|
let text;
|
|
24215
25423
|
try {
|
|
24216
|
-
text =
|
|
25424
|
+
text = readFileSync3(file2, "utf8");
|
|
24217
25425
|
} catch {
|
|
24218
25426
|
return null;
|
|
24219
25427
|
}
|
|
@@ -24235,34 +25443,34 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24235
25443
|
import {
|
|
24236
25444
|
chmodSync as chmodSync2,
|
|
24237
25445
|
mkdirSync as mkdirSync2,
|
|
24238
|
-
readFileSync as
|
|
25446
|
+
readFileSync as readFileSync4,
|
|
24239
25447
|
renameSync as renameSync4,
|
|
24240
|
-
rmSync as
|
|
24241
|
-
statSync,
|
|
24242
|
-
writeFileSync as
|
|
25448
|
+
rmSync as rmSync4,
|
|
25449
|
+
statSync as statSync3,
|
|
25450
|
+
writeFileSync as writeFileSync3
|
|
24243
25451
|
} from "fs";
|
|
24244
|
-
import { join as
|
|
25452
|
+
import { join as join6 } from "path";
|
|
24245
25453
|
|
|
24246
25454
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24247
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25455
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24248
25456
|
|
|
24249
25457
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
24250
|
-
import { existsSync as
|
|
24251
|
-
import { join as
|
|
25458
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25459
|
+
import { join as join7 } from "path";
|
|
24252
25460
|
var MARKER = "warn-era-capped";
|
|
24253
25461
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
24254
25462
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
24255
|
-
const marker =
|
|
24256
|
-
if (
|
|
25463
|
+
const marker = join7(dataDir2, MARKER);
|
|
25464
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
24257
25465
|
const capped = db.policies.capCategoryActions();
|
|
24258
|
-
|
|
25466
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
24259
25467
|
`, { mode: DATA_FILE_MODE });
|
|
24260
25468
|
return { capped };
|
|
24261
25469
|
}
|
|
24262
25470
|
|
|
24263
25471
|
// ../../packages/plugin-sdk/src/config.ts
|
|
24264
|
-
import { existsSync as
|
|
24265
|
-
import { join as
|
|
25472
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25473
|
+
import { join as join8 } from "path";
|
|
24266
25474
|
|
|
24267
25475
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
24268
25476
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -24313,11 +25521,11 @@ function resolveProvider() {
|
|
|
24313
25521
|
}
|
|
24314
25522
|
|
|
24315
25523
|
// ../../packages/plugin-sdk/src/config.ts
|
|
24316
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25524
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
24317
25525
|
try {
|
|
24318
25526
|
ensureLayoutDirSync(base);
|
|
24319
|
-
const settingsFile =
|
|
24320
|
-
if (
|
|
25527
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25528
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
24321
25529
|
} catch {
|
|
24322
25530
|
}
|
|
24323
25531
|
migrateLegacyLayout(base);
|
|
@@ -24328,21 +25536,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
24328
25536
|
dbPath: dbPath(base),
|
|
24329
25537
|
settingsDir: settingsDir(base),
|
|
24330
25538
|
onboarded: settings.onboardedAt != null,
|
|
24331
|
-
provider: resolveProviderSafe()
|
|
25539
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
24332
25540
|
};
|
|
24333
25541
|
}
|
|
24334
|
-
function resolveProviderSafe() {
|
|
25542
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
24335
25543
|
try {
|
|
24336
|
-
return
|
|
25544
|
+
return resolveProviderFn();
|
|
24337
25545
|
} catch {
|
|
24338
25546
|
return { provider: "anthropic" };
|
|
24339
25547
|
}
|
|
24340
25548
|
}
|
|
24341
25549
|
|
|
24342
25550
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
24343
|
-
import { readdirSync, readFileSync as
|
|
25551
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
24344
25552
|
import { homedir as homedir2 } from "os";
|
|
24345
|
-
import { basename as
|
|
25553
|
+
import { basename as basename3, join as join10 } from "path";
|
|
24346
25554
|
|
|
24347
25555
|
// ../../packages/detections/src/egress/registry.ts
|
|
24348
25556
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -24916,6 +26124,40 @@ function escapeRegExp2(value) {
|
|
|
24916
26124
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24917
26125
|
}
|
|
24918
26126
|
|
|
26127
|
+
// ../../packages/detections/src/regex-cache.ts
|
|
26128
|
+
var singles = /* @__PURE__ */ new WeakMap();
|
|
26129
|
+
var keywordLists = /* @__PURE__ */ new WeakMap();
|
|
26130
|
+
var labelLists = /* @__PURE__ */ new WeakMap();
|
|
26131
|
+
function listCache(kind) {
|
|
26132
|
+
return kind === "keyword" ? keywordLists : labelLists;
|
|
26133
|
+
}
|
|
26134
|
+
function memoizedRegExp(owner, build) {
|
|
26135
|
+
const cached2 = singles.get(owner);
|
|
26136
|
+
if (cached2 !== void 0) {
|
|
26137
|
+
cached2.lastIndex = 0;
|
|
26138
|
+
return cached2;
|
|
26139
|
+
}
|
|
26140
|
+
const compiled = build();
|
|
26141
|
+
singles.set(owner, compiled);
|
|
26142
|
+
return compiled;
|
|
26143
|
+
}
|
|
26144
|
+
function memoizedRegExpList(kind, owner, build) {
|
|
26145
|
+
const cache = listCache(kind);
|
|
26146
|
+
const cached2 = cache.get(owner);
|
|
26147
|
+
if (cached2 !== void 0) {
|
|
26148
|
+
if (cached2.stateful) {
|
|
26149
|
+
for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
|
|
26150
|
+
}
|
|
26151
|
+
return cached2.entries;
|
|
26152
|
+
}
|
|
26153
|
+
const entries = build();
|
|
26154
|
+
cache.set(owner, {
|
|
26155
|
+
entries,
|
|
26156
|
+
stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
|
|
26157
|
+
});
|
|
26158
|
+
return entries;
|
|
26159
|
+
}
|
|
26160
|
+
|
|
24919
26161
|
// ../../packages/detections/src/matchers/limits.ts
|
|
24920
26162
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
24921
26163
|
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
@@ -24926,10 +26168,17 @@ var KeywordMatcher2 = class {
|
|
|
24926
26168
|
if (rule.matcher.type !== "keyword") return [];
|
|
24927
26169
|
const { keywords, caseSensitive } = rule.matcher;
|
|
24928
26170
|
const spans = [];
|
|
24929
|
-
|
|
24930
|
-
|
|
26171
|
+
const compiled = memoizedRegExpList(
|
|
26172
|
+
"keyword",
|
|
26173
|
+
rule.matcher,
|
|
26174
|
+
() => keywords.map((kw) => {
|
|
26175
|
+
if (kw.length === 0) return void 0;
|
|
26176
|
+
return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
26177
|
+
})
|
|
26178
|
+
);
|
|
26179
|
+
for (const re of compiled) {
|
|
26180
|
+
if (re === void 0) continue;
|
|
24931
26181
|
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
24932
|
-
const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
24933
26182
|
let m;
|
|
24934
26183
|
while ((m = re.exec(text)) !== null) {
|
|
24935
26184
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -24945,7 +26194,10 @@ var RegexMatcher2 = class {
|
|
|
24945
26194
|
match(text, rule) {
|
|
24946
26195
|
if (rule.matcher.type !== "regex") return [];
|
|
24947
26196
|
const { pattern, flags: flags2, captureGroup } = rule.matcher;
|
|
24948
|
-
const re =
|
|
26197
|
+
const re = memoizedRegExp(
|
|
26198
|
+
rule.matcher,
|
|
26199
|
+
() => new RegExp(pattern, flags2.includes("d") ? flags2 : `${flags2}d`)
|
|
26200
|
+
);
|
|
24949
26201
|
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
24950
26202
|
const spans = [];
|
|
24951
26203
|
let m;
|
|
@@ -25053,22 +26305,27 @@ var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].m
|
|
|
25053
26305
|
);
|
|
25054
26306
|
|
|
25055
26307
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
25056
|
-
import { existsSync as
|
|
25057
|
-
import { basename, dirname, isAbsolute, join as
|
|
26308
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
26309
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
25058
26310
|
|
|
25059
26311
|
// ../../packages/plugin-sdk/src/events.ts
|
|
25060
|
-
import { createHash as createHash4, randomUUID as
|
|
26312
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
26313
|
+
|
|
26314
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
26315
|
+
import { existsSync as existsSync7 } from "fs";
|
|
26316
|
+
import { fileURLToPath } from "url";
|
|
26317
|
+
import { Worker } from "worker_threads";
|
|
25061
26318
|
|
|
25062
26319
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
25063
|
-
import { arch, hostname as
|
|
26320
|
+
import { arch, hostname as hostname4, platform, release as release2 } from "os";
|
|
25064
26321
|
|
|
25065
26322
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
25066
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
25067
|
-
import { join as
|
|
26323
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
26324
|
+
import { join as join11 } from "path";
|
|
25068
26325
|
|
|
25069
26326
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
25070
|
-
import { readdirSync as
|
|
25071
|
-
import { basename as
|
|
26327
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
26328
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
25072
26329
|
|
|
25073
26330
|
// ../../packages/plugin-sdk/src/posture.ts
|
|
25074
26331
|
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
@@ -25082,20 +26339,46 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
25082
26339
|
|
|
25083
26340
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
25084
26341
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
25085
|
-
import { existsSync as
|
|
25086
|
-
import { basename as
|
|
26342
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
26343
|
+
import { basename as basename5, join as join12 } from "path";
|
|
26344
|
+
|
|
26345
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
26346
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
26347
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
26348
|
+
return v;
|
|
26349
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
26350
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
26351
|
+
if (typeof v !== "string") return false;
|
|
26352
|
+
const normalized = v.trim().toLowerCase();
|
|
26353
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
26354
|
+
}, external_exports.boolean()).catch(false);
|
|
26355
|
+
var antigravityProviderEnvShape = {
|
|
26356
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
26357
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
26358
|
+
};
|
|
26359
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
26360
|
+
|
|
26361
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
26362
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
26363
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
26364
|
+
return v;
|
|
26365
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
26366
|
+
var codexProviderEnvShape = {
|
|
26367
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
26368
|
+
};
|
|
26369
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
25087
26370
|
|
|
25088
26371
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
25089
|
-
import { randomUUID as
|
|
26372
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
25090
26373
|
|
|
25091
26374
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
25092
26375
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
25093
26376
|
|
|
25094
26377
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
25095
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
25096
|
-
import { join as
|
|
26378
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
26379
|
+
import { join as join13 } from "path";
|
|
25097
26380
|
|
|
25098
|
-
// src/onboard-posture.ts
|
|
26381
|
+
// ../../packages/setup-wizard/src/onboard-posture.ts
|
|
25099
26382
|
function parsePosture(json2) {
|
|
25100
26383
|
const raw = JSON.parse(json2);
|
|
25101
26384
|
if (typeof raw !== "object" || raw === null) throw new Error("posture must be a JSON object");
|
|
@@ -25114,6 +26397,54 @@ function parsePosture(json2) {
|
|
|
25114
26397
|
return out;
|
|
25115
26398
|
}
|
|
25116
26399
|
|
|
26400
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
26401
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
26402
|
+
import { join as join14 } from "path";
|
|
26403
|
+
|
|
26404
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
26405
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
26406
|
+
import { tmpdir } from "os";
|
|
26407
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
26408
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
26409
|
+
ruleId: external_exports.string(),
|
|
26410
|
+
category: DetectionCategory,
|
|
26411
|
+
valueFingerprint: external_exports.string(),
|
|
26412
|
+
keyVersion: external_exports.number(),
|
|
26413
|
+
maskedValue: external_exports.string(),
|
|
26414
|
+
justification: external_exports.string()
|
|
26415
|
+
});
|
|
26416
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
26417
|
+
category: DetectionCategory,
|
|
26418
|
+
action: BuiltinPolicyId,
|
|
26419
|
+
genuineCount: external_exports.number(),
|
|
26420
|
+
fpCount: external_exports.number(),
|
|
26421
|
+
reasoning: external_exports.string()
|
|
26422
|
+
});
|
|
26423
|
+
var JoinEntrySchema = external_exports.object({
|
|
26424
|
+
id: external_exports.string(),
|
|
26425
|
+
ruleId: external_exports.string(),
|
|
26426
|
+
category: DetectionCategory,
|
|
26427
|
+
valueFingerprint: external_exports.string().optional(),
|
|
26428
|
+
keyVersion: external_exports.number().optional(),
|
|
26429
|
+
maskedMatch: external_exports.string(),
|
|
26430
|
+
maskedContext: external_exports.string()
|
|
26431
|
+
});
|
|
26432
|
+
var PLAN_FILE_VERSION = 3;
|
|
26433
|
+
var PersistedPlanSchema = external_exports.object({
|
|
26434
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
26435
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
26436
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
26437
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
26438
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
26439
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
26440
|
+
join: external_exports.array(JoinEntrySchema),
|
|
26441
|
+
notes: external_exports.string(),
|
|
26442
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
26443
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
26444
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
26445
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
26446
|
+
});
|
|
26447
|
+
|
|
25117
26448
|
// src/setup-show.ts
|
|
25118
26449
|
var SHOW_BEGIN = "<<<AKA_SHOW";
|
|
25119
26450
|
var SHOW_END = "AKA_SHOW>>>";
|
|
@@ -25160,9 +26491,9 @@ function show(body) {
|
|
|
25160
26491
|
}
|
|
25161
26492
|
|
|
25162
26493
|
// src/command-registry.ts
|
|
25163
|
-
import { readdirSync as
|
|
25164
|
-
import { fileURLToPath } from "url";
|
|
25165
|
-
var COMMANDS_DIR =
|
|
26494
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
26495
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
26496
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
25166
26497
|
|
|
25167
26498
|
// src/render.ts
|
|
25168
26499
|
var SEVERITY_GLYPH = {
|