@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/firstrun.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" });
|
|
@@ -17087,6 +17234,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17087
17234
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17088
17235
|
}).meta({ id: "EgressWriteSummary" });
|
|
17089
17236
|
|
|
17237
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17238
|
+
var confirmation = external_exports.string().optional();
|
|
17239
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17240
|
+
reference: external_exports.string(),
|
|
17241
|
+
scope: external_exports.string(),
|
|
17242
|
+
reason: external_exports.string(),
|
|
17243
|
+
confirmation
|
|
17244
|
+
});
|
|
17245
|
+
var AddExceptionInput = external_exports.object({
|
|
17246
|
+
ruleId: external_exports.string(),
|
|
17247
|
+
value: external_exports.string(),
|
|
17248
|
+
scope: external_exports.string(),
|
|
17249
|
+
reason: external_exports.string(),
|
|
17250
|
+
confirmation
|
|
17251
|
+
});
|
|
17252
|
+
var GrantRevealInput = external_exports.object({
|
|
17253
|
+
pointer: external_exports.string(),
|
|
17254
|
+
scope: external_exports.string(),
|
|
17255
|
+
justification: external_exports.string(),
|
|
17256
|
+
confirmation
|
|
17257
|
+
});
|
|
17258
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17259
|
+
id: external_exports.string(),
|
|
17260
|
+
reason: external_exports.string()
|
|
17261
|
+
});
|
|
17262
|
+
var RotateKeyInput = external_exports.object({
|
|
17263
|
+
confirmation: external_exports.string()
|
|
17264
|
+
});
|
|
17265
|
+
|
|
17090
17266
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17091
17267
|
function toApiAction(dbVal) {
|
|
17092
17268
|
const map2 = {
|
|
@@ -17142,6 +17318,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17142
17318
|
repo: r.repo,
|
|
17143
17319
|
file: r.file,
|
|
17144
17320
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17321
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17322
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17145
17323
|
action: toApiAction(effectiveDbAction),
|
|
17146
17324
|
detectedAt: r.occurredAt,
|
|
17147
17325
|
confidence: r.confidence,
|
|
@@ -17273,14 +17451,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17273
17451
|
}
|
|
17274
17452
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17275
17453
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17454
|
+
function compareFindingGroupOrder(a, b) {
|
|
17455
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17456
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17457
|
+
const severityDiff = rankA - rankB;
|
|
17458
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17459
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17460
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17461
|
+
return a.id.localeCompare(b.id);
|
|
17462
|
+
}
|
|
17276
17463
|
function sortFindingGroups(groups) {
|
|
17277
|
-
return [...groups].sort(
|
|
17278
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17279
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17280
|
-
const severityDiff = rankA - rankB;
|
|
17281
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17282
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17283
|
-
});
|
|
17464
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17284
17465
|
}
|
|
17285
17466
|
function computeFindingFacets(allGroups, opts) {
|
|
17286
17467
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17336,16 +17517,159 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17336
17517
|
for (const g of forStatus) {
|
|
17337
17518
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17338
17519
|
}
|
|
17339
|
-
const
|
|
17520
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17340
17521
|
return {
|
|
17341
|
-
severity:
|
|
17342
|
-
provider:
|
|
17343
|
-
action:
|
|
17344
|
-
subtype:
|
|
17345
|
-
status:
|
|
17522
|
+
severity: toItems2(severityMap),
|
|
17523
|
+
provider: toItems2(providerMap),
|
|
17524
|
+
action: toItems2(actionMap),
|
|
17525
|
+
subtype: toItems2(subtypeMap),
|
|
17526
|
+
status: toItems2(statusMap)
|
|
17346
17527
|
};
|
|
17347
17528
|
}
|
|
17348
17529
|
|
|
17530
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17531
|
+
function rowHaystack(row) {
|
|
17532
|
+
return [
|
|
17533
|
+
row.ruleId,
|
|
17534
|
+
row.category,
|
|
17535
|
+
row.maskedMatch,
|
|
17536
|
+
row.repo,
|
|
17537
|
+
row.file,
|
|
17538
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17539
|
+
row.id
|
|
17540
|
+
].join(" ").toLowerCase();
|
|
17541
|
+
}
|
|
17542
|
+
function matchesDimension(row, opts, dimension) {
|
|
17543
|
+
switch (dimension) {
|
|
17544
|
+
case "severity":
|
|
17545
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17546
|
+
case "subtype":
|
|
17547
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17548
|
+
case "providers":
|
|
17549
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17550
|
+
case "actions":
|
|
17551
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17552
|
+
case "statuses":
|
|
17553
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17554
|
+
case "tools":
|
|
17555
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17556
|
+
case "repo":
|
|
17557
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17558
|
+
case "file":
|
|
17559
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17560
|
+
case "q":
|
|
17561
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17562
|
+
}
|
|
17563
|
+
}
|
|
17564
|
+
var DIMENSIONS = [
|
|
17565
|
+
"severity",
|
|
17566
|
+
"subtype",
|
|
17567
|
+
"providers",
|
|
17568
|
+
"actions",
|
|
17569
|
+
"statuses",
|
|
17570
|
+
"tools",
|
|
17571
|
+
"repo",
|
|
17572
|
+
"file",
|
|
17573
|
+
"q"
|
|
17574
|
+
];
|
|
17575
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17576
|
+
for (const dimension of DIMENSIONS) {
|
|
17577
|
+
if (dimension === except) continue;
|
|
17578
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17579
|
+
}
|
|
17580
|
+
return true;
|
|
17581
|
+
}
|
|
17582
|
+
function toItems(counts) {
|
|
17583
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17584
|
+
}
|
|
17585
|
+
function bump(counts, value) {
|
|
17586
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17587
|
+
}
|
|
17588
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17589
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17590
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17591
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17592
|
+
const action = /* @__PURE__ */ new Map();
|
|
17593
|
+
const status = /* @__PURE__ */ new Map();
|
|
17594
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17595
|
+
return {
|
|
17596
|
+
add(row) {
|
|
17597
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17598
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17599
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17600
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17601
|
+
}
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17603
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17604
|
+
bump(status, row.status);
|
|
17605
|
+
}
|
|
17606
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17607
|
+
bump(tool, row.toolName);
|
|
17608
|
+
}
|
|
17609
|
+
},
|
|
17610
|
+
facets: () => ({
|
|
17611
|
+
severity: toItems(severity),
|
|
17612
|
+
subtype: toItems(subtype),
|
|
17613
|
+
provider: toItems(provider),
|
|
17614
|
+
action: toItems(action),
|
|
17615
|
+
status: toItems(status),
|
|
17616
|
+
tool: toItems(tool)
|
|
17617
|
+
})
|
|
17618
|
+
};
|
|
17619
|
+
}
|
|
17620
|
+
function toInstanceDetail(row) {
|
|
17621
|
+
const category = toApiCategory(row.category);
|
|
17622
|
+
return {
|
|
17623
|
+
id: row.id,
|
|
17624
|
+
provider: toApiProvider(row.sourceTool),
|
|
17625
|
+
repo: row.repo,
|
|
17626
|
+
file: row.file,
|
|
17627
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17628
|
+
eventId: row.eventId,
|
|
17629
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17630
|
+
action: toApiAction(row.actionTaken),
|
|
17631
|
+
detectedAt: row.occurredAt,
|
|
17632
|
+
confidence: row.confidence,
|
|
17633
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17634
|
+
groupId: row.ruleId,
|
|
17635
|
+
category,
|
|
17636
|
+
subtype: row.ruleId,
|
|
17637
|
+
severity: row.severity,
|
|
17638
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17639
|
+
detection: { id: row.ruleId, name: null },
|
|
17640
|
+
policy: { id: `category:${category}`, name: category }
|
|
17641
|
+
};
|
|
17642
|
+
}
|
|
17643
|
+
var SEVERITY_ORDER2 = {
|
|
17644
|
+
critical: 0,
|
|
17645
|
+
high: 1,
|
|
17646
|
+
medium: 2,
|
|
17647
|
+
low: 3
|
|
17648
|
+
};
|
|
17649
|
+
function newLocationAccumulator() {
|
|
17650
|
+
return {
|
|
17651
|
+
instanceCount: 0,
|
|
17652
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17653
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17654
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17655
|
+
maxSeverity: "low",
|
|
17656
|
+
latestDetectedAt: "",
|
|
17657
|
+
statuses: [],
|
|
17658
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17659
|
+
};
|
|
17660
|
+
}
|
|
17661
|
+
function addToLocation(acc, row) {
|
|
17662
|
+
acc.instanceCount += 1;
|
|
17663
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17664
|
+
if (rank < acc.maxSeverityRank) {
|
|
17665
|
+
acc.maxSeverityRank = rank;
|
|
17666
|
+
acc.maxSeverity = row.severity;
|
|
17667
|
+
}
|
|
17668
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17669
|
+
acc.statuses.push(row.status);
|
|
17670
|
+
acc.ruleIds.add(row.ruleId);
|
|
17671
|
+
}
|
|
17672
|
+
|
|
17349
17673
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17350
17674
|
var InstalledPack = external_exports.object({
|
|
17351
17675
|
id: external_exports.guid(),
|
|
@@ -17478,6 +17802,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17478
17802
|
revealGrantId: external_exports.string().nullable(),
|
|
17479
17803
|
sightings: external_exports.array(VaultSighting)
|
|
17480
17804
|
});
|
|
17805
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17806
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17807
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17808
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17809
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17810
|
+
// Opaque; names the last row of the page just served.
|
|
17811
|
+
cursor: external_exports.string().optional()
|
|
17812
|
+
});
|
|
17813
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17814
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17815
|
+
// independent, so paging never changes what the count claims.
|
|
17816
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17817
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17818
|
+
// `null` once the last page is reached.
|
|
17819
|
+
nextCursor: external_exports.string().nullable()
|
|
17820
|
+
});
|
|
17821
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17822
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17823
|
+
cursor: external_exports.string().optional()
|
|
17824
|
+
});
|
|
17825
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17826
|
+
// Reused values across the whole store — the number the section's claim
|
|
17827
|
+
// ("values detected in more than one place") is about.
|
|
17828
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17829
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17830
|
+
nextCursor: external_exports.string().nullable()
|
|
17831
|
+
});
|
|
17832
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17833
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17834
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17835
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17836
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17837
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17838
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17839
|
+
cursor: external_exports.string().optional()
|
|
17840
|
+
});
|
|
17841
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17842
|
+
items: external_exports.array(VaultDeref),
|
|
17843
|
+
nextCursor: external_exports.string().nullable(),
|
|
17844
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17845
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17846
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17847
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17848
|
+
});
|
|
17481
17849
|
var VaultKeyCustody = external_exports.string();
|
|
17482
17850
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17483
17851
|
var VaultConsent = external_exports.object({
|
|
@@ -17850,7 +18218,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17850
18218
|
// Omit for both kinds.
|
|
17851
18219
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17852
18220
|
});
|
|
17853
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18221
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17854
18222
|
var ScanCoverageProvider = external_exports.object({
|
|
17855
18223
|
provider: Provider,
|
|
17856
18224
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18103,6 +18471,138 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18103
18471
|
);
|
|
18104
18472
|
}
|
|
18105
18473
|
|
|
18474
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18475
|
+
import { randomUUID } from "crypto";
|
|
18476
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18477
|
+
import { basename, dirname, join } from "path";
|
|
18478
|
+
|
|
18479
|
+
// ../../packages/persistence/src/paths.ts
|
|
18480
|
+
import {
|
|
18481
|
+
chmodSync,
|
|
18482
|
+
linkSync,
|
|
18483
|
+
lstatSync,
|
|
18484
|
+
mkdirSync,
|
|
18485
|
+
renameSync,
|
|
18486
|
+
rmSync,
|
|
18487
|
+
writeFileSync
|
|
18488
|
+
} from "fs";
|
|
18489
|
+
import { threadId } from "worker_threads";
|
|
18490
|
+
var DATA_DIR_MODE = 448;
|
|
18491
|
+
var DATA_FILE_MODE = 384;
|
|
18492
|
+
var DB_FILENAME = "aka.db";
|
|
18493
|
+
function isSymlink(path) {
|
|
18494
|
+
try {
|
|
18495
|
+
return lstatSync(path).isSymbolicLink();
|
|
18496
|
+
} catch {
|
|
18497
|
+
return false;
|
|
18498
|
+
}
|
|
18499
|
+
}
|
|
18500
|
+
function chmodBestEffort(path, mode) {
|
|
18501
|
+
if (isSymlink(path)) return;
|
|
18502
|
+
try {
|
|
18503
|
+
chmodSync(path, mode);
|
|
18504
|
+
} catch {
|
|
18505
|
+
}
|
|
18506
|
+
}
|
|
18507
|
+
function tightenDir(dir) {
|
|
18508
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18509
|
+
}
|
|
18510
|
+
function ensureDataDirSync(dir) {
|
|
18511
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18512
|
+
tightenDir(dir);
|
|
18513
|
+
}
|
|
18514
|
+
function dbSidecars(file2) {
|
|
18515
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18516
|
+
}
|
|
18517
|
+
function tightenFile(file2) {
|
|
18518
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18519
|
+
}
|
|
18520
|
+
function tightenPerms(file2) {
|
|
18521
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18522
|
+
}
|
|
18523
|
+
|
|
18524
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18525
|
+
function backupPath(file2, tag) {
|
|
18526
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18527
|
+
}
|
|
18528
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18529
|
+
function reapStalePartials(file2) {
|
|
18530
|
+
const dir = dirname(file2);
|
|
18531
|
+
const prefix = `${basename(file2)}.`;
|
|
18532
|
+
let entries;
|
|
18533
|
+
try {
|
|
18534
|
+
entries = readdirSync(dir);
|
|
18535
|
+
} catch {
|
|
18536
|
+
return;
|
|
18537
|
+
}
|
|
18538
|
+
for (const name of entries) {
|
|
18539
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18540
|
+
const partial2 = join(dir, name);
|
|
18541
|
+
try {
|
|
18542
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18543
|
+
rmSync2(partial2, { force: true });
|
|
18544
|
+
}
|
|
18545
|
+
} catch {
|
|
18546
|
+
}
|
|
18547
|
+
}
|
|
18548
|
+
}
|
|
18549
|
+
function snapshotStore(db, backup) {
|
|
18550
|
+
const partial2 = `${backup}.partial`;
|
|
18551
|
+
try {
|
|
18552
|
+
rmSync2(partial2, { force: true });
|
|
18553
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18554
|
+
tightenFile(partial2);
|
|
18555
|
+
renameSync2(partial2, backup);
|
|
18556
|
+
} catch (error51) {
|
|
18557
|
+
try {
|
|
18558
|
+
rmSync2(partial2, { force: true });
|
|
18559
|
+
} catch {
|
|
18560
|
+
}
|
|
18561
|
+
throw error51;
|
|
18562
|
+
}
|
|
18563
|
+
}
|
|
18564
|
+
function moveStoreAside(file2, backup) {
|
|
18565
|
+
const undo = [];
|
|
18566
|
+
renameSync2(file2, backup);
|
|
18567
|
+
undo.push([backup, file2]);
|
|
18568
|
+
try {
|
|
18569
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18570
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18571
|
+
try {
|
|
18572
|
+
renameSync2(sidecar, moved);
|
|
18573
|
+
undo.push([moved, sidecar]);
|
|
18574
|
+
} catch {
|
|
18575
|
+
rmSync2(sidecar, { force: true });
|
|
18576
|
+
}
|
|
18577
|
+
}
|
|
18578
|
+
} catch (error51) {
|
|
18579
|
+
for (const [from, to] of undo.reverse()) {
|
|
18580
|
+
try {
|
|
18581
|
+
renameSync2(from, to);
|
|
18582
|
+
} catch {
|
|
18583
|
+
}
|
|
18584
|
+
}
|
|
18585
|
+
throw error51;
|
|
18586
|
+
}
|
|
18587
|
+
tightenPerms(backup);
|
|
18588
|
+
}
|
|
18589
|
+
function discardStore(file2, backup) {
|
|
18590
|
+
try {
|
|
18591
|
+
rmSync2(file2, { force: true });
|
|
18592
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18593
|
+
rmSync2(sidecar, { force: true });
|
|
18594
|
+
}
|
|
18595
|
+
} catch (error51) {
|
|
18596
|
+
if (existsSync(file2)) {
|
|
18597
|
+
try {
|
|
18598
|
+
rmSync2(backup, { force: true });
|
|
18599
|
+
} catch {
|
|
18600
|
+
}
|
|
18601
|
+
}
|
|
18602
|
+
throw error51;
|
|
18603
|
+
}
|
|
18604
|
+
}
|
|
18605
|
+
|
|
18106
18606
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18107
18607
|
function escapeLikePattern(s) {
|
|
18108
18608
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18244,38 +18744,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18244
18744
|
return out;
|
|
18245
18745
|
}
|
|
18246
18746
|
|
|
18247
|
-
// ../../packages/persistence/src/paths.ts
|
|
18248
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18249
|
-
var DATA_DIR_MODE = 448;
|
|
18250
|
-
var DATA_FILE_MODE = 384;
|
|
18251
|
-
var DB_FILENAME = "aka.db";
|
|
18252
|
-
function chmodBestEffort(path, mode) {
|
|
18253
|
-
try {
|
|
18254
|
-
chmodSync(path, mode);
|
|
18255
|
-
} catch {
|
|
18256
|
-
}
|
|
18257
|
-
}
|
|
18258
|
-
function tightenDir(dir) {
|
|
18259
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18260
|
-
}
|
|
18261
|
-
function ensureDataDirSync(dir) {
|
|
18262
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18263
|
-
tightenDir(dir);
|
|
18264
|
-
}
|
|
18265
|
-
function dbSidecars(file2) {
|
|
18266
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18267
|
-
}
|
|
18268
|
-
function tightenFile(file2) {
|
|
18269
|
-
try {
|
|
18270
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18271
|
-
} catch {
|
|
18272
|
-
}
|
|
18273
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18274
|
-
}
|
|
18275
|
-
function tightenPerms(file2) {
|
|
18276
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18277
|
-
}
|
|
18278
|
-
|
|
18279
18747
|
// ../../packages/persistence/src/migrations.ts
|
|
18280
18748
|
function describeObject(object2) {
|
|
18281
18749
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18391,9 +18859,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18391
18859
|
}
|
|
18392
18860
|
}
|
|
18393
18861
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18862
|
+
reapStalePartials(file2);
|
|
18863
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18864
|
+
snapshotStore(db, backup);
|
|
18397
18865
|
return backup;
|
|
18398
18866
|
}
|
|
18399
18867
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18737,6 +19205,25 @@ function parseJsonObject(s) {
|
|
|
18737
19205
|
return void 0;
|
|
18738
19206
|
}
|
|
18739
19207
|
|
|
19208
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19209
|
+
function encodeKeysetCursor(payload) {
|
|
19210
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19211
|
+
}
|
|
19212
|
+
function decodeKeysetCursor(cursor) {
|
|
19213
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19214
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19215
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19216
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19217
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19218
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19219
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19220
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19221
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19222
|
+
return parsed;
|
|
19223
|
+
}
|
|
19224
|
+
return null;
|
|
19225
|
+
}
|
|
19226
|
+
|
|
18740
19227
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18741
19228
|
var DAY_MS = 864e5;
|
|
18742
19229
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18782,16 +19269,6 @@ function utcWindow(nowMs) {
|
|
|
18782
19269
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18783
19270
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18784
19271
|
}
|
|
18785
|
-
function encodeCursor(payload) {
|
|
18786
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18787
|
-
}
|
|
18788
|
-
function decodeCursor(cursor) {
|
|
18789
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18790
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18791
|
-
return parsed;
|
|
18792
|
-
}
|
|
18793
|
-
return null;
|
|
18794
|
-
}
|
|
18795
19272
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18796
19273
|
session: "session",
|
|
18797
19274
|
prompt: "prompt",
|
|
@@ -18936,7 +19413,7 @@ var SqliteActivityRepository = class {
|
|
|
18936
19413
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18937
19414
|
}
|
|
18938
19415
|
listSessions(query) {
|
|
18939
|
-
const cursor = query.cursor ?
|
|
19416
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18940
19417
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18941
19418
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18942
19419
|
const conditions = [SESSION_ROOT];
|
|
@@ -19010,7 +19487,7 @@ var SqliteActivityRepository = class {
|
|
|
19010
19487
|
)
|
|
19011
19488
|
);
|
|
19012
19489
|
const last = page[page.length - 1];
|
|
19013
|
-
const nextCursor = hasMore && last ?
|
|
19490
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19014
19491
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19015
19492
|
}
|
|
19016
19493
|
getSession(sessionId) {
|
|
@@ -19883,7 +20360,7 @@ var SqliteEventsRepository = class {
|
|
|
19883
20360
|
};
|
|
19884
20361
|
|
|
19885
20362
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19886
|
-
import { randomUUID } from "crypto";
|
|
20363
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19887
20364
|
|
|
19888
20365
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19889
20366
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19919,8 +20396,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19919
20396
|
AND conditions IS NULL
|
|
19920
20397
|
AND ${ACTIVE_PREDICATE}`;
|
|
19921
20398
|
var SqliteExceptionsRepository = class {
|
|
19922
|
-
constructor(db) {
|
|
20399
|
+
constructor(db, now = () => Date.now()) {
|
|
19923
20400
|
this.db = db;
|
|
20401
|
+
this.now = now;
|
|
19924
20402
|
this.consumeStmt = db.prepare(
|
|
19925
20403
|
`UPDATE exceptions
|
|
19926
20404
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19938,6 +20416,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19938
20416
|
);
|
|
19939
20417
|
}
|
|
19940
20418
|
db;
|
|
20419
|
+
now;
|
|
19941
20420
|
consumeStmt;
|
|
19942
20421
|
insertBlockedStmt;
|
|
19943
20422
|
sweepBlockedStmt;
|
|
@@ -19964,8 +20443,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19964
20443
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19965
20444
|
);
|
|
19966
20445
|
}
|
|
19967
|
-
const id =
|
|
19968
|
-
const now =
|
|
20446
|
+
const id = randomUUID2();
|
|
20447
|
+
const now = this.now();
|
|
19969
20448
|
try {
|
|
19970
20449
|
this.insertExceptionRow(id, input, now);
|
|
19971
20450
|
} catch (err) {
|
|
@@ -20043,7 +20522,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20043
20522
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20044
20523
|
const rows = allRows(
|
|
20045
20524
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20046
|
-
opts?.includeTerminal ? {} : { now:
|
|
20525
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20047
20526
|
);
|
|
20048
20527
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20049
20528
|
return Promise.resolve(exceptions);
|
|
@@ -20078,7 +20557,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20078
20557
|
* already revoked.
|
|
20079
20558
|
*/
|
|
20080
20559
|
revoke(id, revokedBy, reason) {
|
|
20081
|
-
const now =
|
|
20560
|
+
const now = this.now();
|
|
20082
20561
|
const result = this.db.prepare(
|
|
20083
20562
|
`UPDATE exceptions
|
|
20084
20563
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20092,7 +20571,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20092
20571
|
* callers must treat identically — means it does not and the detection is
|
|
20093
20572
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20094
20573
|
*/
|
|
20095
|
-
consume(id, now =
|
|
20574
|
+
consume(id, now = this.now()) {
|
|
20096
20575
|
const result = this.consumeStmt.run({ id, now });
|
|
20097
20576
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20098
20577
|
}
|
|
@@ -20101,7 +20580,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20101
20580
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20102
20581
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20103
20582
|
*/
|
|
20104
|
-
activeBundleEntries(keyVersion, now =
|
|
20583
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20105
20584
|
const rows = allRows(
|
|
20106
20585
|
this.db.prepare(
|
|
20107
20586
|
`SELECT * FROM exceptions
|
|
@@ -20133,7 +20612,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20133
20612
|
* than the retention window on every write, so the ledger self-limits.
|
|
20134
20613
|
*/
|
|
20135
20614
|
recordBlocked(entry) {
|
|
20136
|
-
const now =
|
|
20615
|
+
const now = this.now();
|
|
20137
20616
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20138
20617
|
this.insertBlockedStmt.run({
|
|
20139
20618
|
reference: entry.reference,
|
|
@@ -20156,7 +20635,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20156
20635
|
WHERE blocked_at > :cutoff
|
|
20157
20636
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20158
20637
|
),
|
|
20159
|
-
{ cutoff:
|
|
20638
|
+
{ cutoff: this.now() - windowMs }
|
|
20160
20639
|
);
|
|
20161
20640
|
return Promise.resolve(
|
|
20162
20641
|
rows.map((row) => ({
|
|
@@ -20184,8 +20663,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20184
20663
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20185
20664
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20186
20665
|
*/
|
|
20187
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20666
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20188
20667
|
try {
|
|
20668
|
+
const at = now ?? this.now();
|
|
20189
20669
|
const row = getRow(
|
|
20190
20670
|
this.db.prepare(
|
|
20191
20671
|
`SELECT id FROM exceptions
|
|
@@ -20194,7 +20674,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20194
20674
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20195
20675
|
LIMIT 1`
|
|
20196
20676
|
),
|
|
20197
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20677
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20198
20678
|
);
|
|
20199
20679
|
return Promise.resolve(row ?? null);
|
|
20200
20680
|
} catch (err) {
|
|
@@ -20208,7 +20688,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20208
20688
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20209
20689
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20210
20690
|
*/
|
|
20211
|
-
sweepTerminal(retentionMs, now =
|
|
20691
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20212
20692
|
const result = this.db.prepare(
|
|
20213
20693
|
`DELETE FROM exceptions
|
|
20214
20694
|
WHERE updated_at < :cutoff
|
|
@@ -20271,6 +20751,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20271
20751
|
|
|
20272
20752
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20273
20753
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20754
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20755
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20756
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20757
|
+
function compareLocationOrder(a, b) {
|
|
20758
|
+
return compareFindingGroupOrder(
|
|
20759
|
+
{
|
|
20760
|
+
severity: a.maxSeverity,
|
|
20761
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20762
|
+
id: ""
|
|
20763
|
+
},
|
|
20764
|
+
{
|
|
20765
|
+
severity: b.maxSeverity,
|
|
20766
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20767
|
+
id: ""
|
|
20768
|
+
}
|
|
20769
|
+
);
|
|
20770
|
+
}
|
|
20274
20771
|
var CONCAT_SEP = ",";
|
|
20275
20772
|
var TUPLE_SEP = "|";
|
|
20276
20773
|
function splitConcat(value) {
|
|
@@ -20283,6 +20780,33 @@ function deriveInstanceStatus(row) {
|
|
|
20283
20780
|
latestResolutionStatus: row.latest_status
|
|
20284
20781
|
});
|
|
20285
20782
|
}
|
|
20783
|
+
function encodeGroupCursor(group) {
|
|
20784
|
+
const payload = {
|
|
20785
|
+
sev: group.severity,
|
|
20786
|
+
t: group.latestDetectedAt,
|
|
20787
|
+
id: group.id
|
|
20788
|
+
};
|
|
20789
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20790
|
+
}
|
|
20791
|
+
function decodeGroupCursor(cursor) {
|
|
20792
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20793
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20794
|
+
return {
|
|
20795
|
+
severity: parsed.sev,
|
|
20796
|
+
latestDetectedAt: parsed.t,
|
|
20797
|
+
id: parsed.id
|
|
20798
|
+
};
|
|
20799
|
+
}
|
|
20800
|
+
return null;
|
|
20801
|
+
}
|
|
20802
|
+
function firstAfter(sorted, cursor) {
|
|
20803
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20804
|
+
return index === -1 ? sorted.length : index;
|
|
20805
|
+
}
|
|
20806
|
+
function findDeepLinked(sorted, page, id) {
|
|
20807
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20808
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20809
|
+
}
|
|
20286
20810
|
var DAY_MS3 = 864e5;
|
|
20287
20811
|
var SqliteFindingsRepository = class {
|
|
20288
20812
|
constructor(db) {
|
|
@@ -20392,8 +20916,13 @@ var SqliteFindingsRepository = class {
|
|
|
20392
20916
|
*/
|
|
20393
20917
|
listGroupedFindings(query) {
|
|
20394
20918
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20395
|
-
const
|
|
20396
|
-
const
|
|
20919
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20920
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20921
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20922
|
+
const sessionParams = {
|
|
20923
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20924
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20925
|
+
};
|
|
20397
20926
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20398
20927
|
predicate,
|
|
20399
20928
|
params: sessionParams
|
|
@@ -20401,7 +20930,8 @@ var SqliteFindingsRepository = class {
|
|
|
20401
20930
|
const rows = allRows(
|
|
20402
20931
|
this.db.prepare(
|
|
20403
20932
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20404
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20933
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20934
|
+
kind, finding_key, latest_status
|
|
20405
20935
|
FROM (
|
|
20406
20936
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20407
20937
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20411,6 +20941,7 @@ var SqliteFindingsRepository = class {
|
|
|
20411
20941
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20412
20942
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20413
20943
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
20944
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20414
20945
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20415
20946
|
latest.status AS latest_status,
|
|
20416
20947
|
ROW_NUMBER() OVER (
|
|
@@ -20442,6 +20973,8 @@ var SqliteFindingsRepository = class {
|
|
|
20442
20973
|
repo: r.repo ?? "",
|
|
20443
20974
|
file: r.file ?? "",
|
|
20444
20975
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
20976
|
+
eventId: r.event_id,
|
|
20977
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20445
20978
|
status: deriveInstanceStatus(r)
|
|
20446
20979
|
}));
|
|
20447
20980
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20465,18 +20998,23 @@ var SqliteFindingsRepository = class {
|
|
|
20465
20998
|
groups: sorted.length
|
|
20466
20999
|
};
|
|
20467
21000
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21001
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21002
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21003
|
+
const page = sorted.slice(start, start + limit);
|
|
21004
|
+
const lastOnPage = page.at(-1);
|
|
21005
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21006
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20468
21007
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20469
|
-
const
|
|
20470
|
-
|
|
20471
|
-
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
);
|
|
21008
|
+
const narrow = (g) => statusSet ? {
|
|
21009
|
+
...g,
|
|
21010
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21011
|
+
} : g;
|
|
21012
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20475
21013
|
return Promise.resolve({
|
|
20476
21014
|
totals,
|
|
20477
21015
|
facets,
|
|
20478
21016
|
items,
|
|
20479
|
-
nextCursor
|
|
21017
|
+
nextCursor,
|
|
20480
21018
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20481
21019
|
});
|
|
20482
21020
|
}
|
|
@@ -20508,6 +21046,266 @@ var SqliteFindingsRepository = class {
|
|
|
20508
21046
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20509
21047
|
* path repeating across tuples.)
|
|
20510
21048
|
*/
|
|
21049
|
+
/**
|
|
21050
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21051
|
+
* paged by keyset.
|
|
21052
|
+
*
|
|
21053
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21054
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21055
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21056
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21057
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21058
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21059
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21060
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21061
|
+
* none of the mappers names", which no IN-list can say.
|
|
21062
|
+
*
|
|
21063
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21064
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21065
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21066
|
+
* while the counting runs, and only the page itself is retained.
|
|
21067
|
+
*/
|
|
21068
|
+
listFindingInstances(query) {
|
|
21069
|
+
const opts = {
|
|
21070
|
+
severity: query.severity,
|
|
21071
|
+
subtype: query.subtype,
|
|
21072
|
+
providers: query.provider,
|
|
21073
|
+
actions: query.action,
|
|
21074
|
+
statuses: query.status,
|
|
21075
|
+
tools: query.tool,
|
|
21076
|
+
repo: query.repo,
|
|
21077
|
+
file: query.file,
|
|
21078
|
+
q: query.q
|
|
21079
|
+
};
|
|
21080
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21081
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21082
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21083
|
+
const items = [];
|
|
21084
|
+
let total = 0;
|
|
21085
|
+
let last;
|
|
21086
|
+
let hasMore = false;
|
|
21087
|
+
for (const row of this.scanFindingRows({
|
|
21088
|
+
sessionId: query.sessionId,
|
|
21089
|
+
from: query.from
|
|
21090
|
+
})) {
|
|
21091
|
+
accumulator.add(row);
|
|
21092
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21093
|
+
total += 1;
|
|
21094
|
+
if (items.length < limit) {
|
|
21095
|
+
items.push(toInstanceDetail(row));
|
|
21096
|
+
last = row;
|
|
21097
|
+
} else {
|
|
21098
|
+
hasMore = true;
|
|
21099
|
+
}
|
|
21100
|
+
}
|
|
21101
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21102
|
+
if (cursor !== null) {
|
|
21103
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21104
|
+
return Promise.resolve({
|
|
21105
|
+
totals: { findings: total },
|
|
21106
|
+
facets: accumulator.facets(),
|
|
21107
|
+
items: resumed.items,
|
|
21108
|
+
nextCursor: resumed.nextCursor
|
|
21109
|
+
});
|
|
21110
|
+
}
|
|
21111
|
+
return Promise.resolve({
|
|
21112
|
+
totals: { findings: total },
|
|
21113
|
+
facets: accumulator.facets(),
|
|
21114
|
+
items,
|
|
21115
|
+
nextCursor
|
|
21116
|
+
});
|
|
21117
|
+
}
|
|
21118
|
+
/**
|
|
21119
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21120
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21121
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21122
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21123
|
+
*/
|
|
21124
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21125
|
+
const items = [];
|
|
21126
|
+
let last;
|
|
21127
|
+
let hasMore = false;
|
|
21128
|
+
for (const row of this.scanFindingRows({
|
|
21129
|
+
sessionId: query.sessionId,
|
|
21130
|
+
from: query.from,
|
|
21131
|
+
after: cursor
|
|
21132
|
+
})) {
|
|
21133
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21134
|
+
if (items.length < limit) {
|
|
21135
|
+
items.push(toInstanceDetail(row));
|
|
21136
|
+
last = row;
|
|
21137
|
+
} else {
|
|
21138
|
+
hasMore = true;
|
|
21139
|
+
break;
|
|
21140
|
+
}
|
|
21141
|
+
}
|
|
21142
|
+
return {
|
|
21143
|
+
items,
|
|
21144
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21145
|
+
};
|
|
21146
|
+
}
|
|
21147
|
+
/**
|
|
21148
|
+
* The same findings folded by location: repository, then file within it.
|
|
21149
|
+
*
|
|
21150
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21151
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21152
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21153
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21154
|
+
* filter can name it.
|
|
21155
|
+
*/
|
|
21156
|
+
listFindingLocations(query) {
|
|
21157
|
+
const opts = {
|
|
21158
|
+
severity: query.severity,
|
|
21159
|
+
subtype: query.subtype,
|
|
21160
|
+
providers: query.provider,
|
|
21161
|
+
actions: query.action,
|
|
21162
|
+
statuses: query.status,
|
|
21163
|
+
tools: query.tool,
|
|
21164
|
+
q: query.q
|
|
21165
|
+
};
|
|
21166
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21167
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21168
|
+
let total = 0;
|
|
21169
|
+
for (const row of this.scanFindingRows({
|
|
21170
|
+
sessionId: query.sessionId,
|
|
21171
|
+
from: query.from
|
|
21172
|
+
})) {
|
|
21173
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21174
|
+
total += 1;
|
|
21175
|
+
let files = byRepo.get(row.repo);
|
|
21176
|
+
if (files === void 0) {
|
|
21177
|
+
files = /* @__PURE__ */ new Map();
|
|
21178
|
+
byRepo.set(row.repo, files);
|
|
21179
|
+
}
|
|
21180
|
+
let acc = files.get(row.file);
|
|
21181
|
+
if (acc === void 0) {
|
|
21182
|
+
acc = newLocationAccumulator();
|
|
21183
|
+
files.set(row.file, acc);
|
|
21184
|
+
}
|
|
21185
|
+
addToLocation(acc, row);
|
|
21186
|
+
}
|
|
21187
|
+
let fileCount = 0;
|
|
21188
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21189
|
+
fileCount += files.size;
|
|
21190
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21191
|
+
file: file2,
|
|
21192
|
+
instanceCount: acc.instanceCount,
|
|
21193
|
+
maxSeverity: acc.maxSeverity,
|
|
21194
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21195
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21196
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21197
|
+
})).sort(compareLocationOrder);
|
|
21198
|
+
const rollup = fileRows.reduce(
|
|
21199
|
+
(a, f) => ({
|
|
21200
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21201
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21202
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21203
|
+
}),
|
|
21204
|
+
{
|
|
21205
|
+
instanceCount: 0,
|
|
21206
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21207
|
+
latestDetectedAt: ""
|
|
21208
|
+
}
|
|
21209
|
+
);
|
|
21210
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21211
|
+
const folded = foldGroupStatus(statuses);
|
|
21212
|
+
return {
|
|
21213
|
+
repo,
|
|
21214
|
+
instanceCount: rollup.instanceCount,
|
|
21215
|
+
maxSeverity: rollup.maxSeverity,
|
|
21216
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21217
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21218
|
+
files: fileRows
|
|
21219
|
+
};
|
|
21220
|
+
});
|
|
21221
|
+
repos.sort(compareLocationOrder);
|
|
21222
|
+
return Promise.resolve({
|
|
21223
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21224
|
+
items: repos.slice(0, limit),
|
|
21225
|
+
hasMore: repos.length > limit
|
|
21226
|
+
});
|
|
21227
|
+
}
|
|
21228
|
+
/**
|
|
21229
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21230
|
+
*
|
|
21231
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21232
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21233
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21234
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21235
|
+
* rather than one unbounded result set.
|
|
21236
|
+
*
|
|
21237
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21238
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21239
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21240
|
+
* a window over the whole resolution table once per batch.
|
|
21241
|
+
*
|
|
21242
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21243
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21244
|
+
* dimension — see listFindingInstances.
|
|
21245
|
+
*/
|
|
21246
|
+
*scanFindingRows(scope) {
|
|
21247
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21248
|
+
const params = [];
|
|
21249
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21250
|
+
conditions.push("e.root_session_id = ?");
|
|
21251
|
+
params.push(scope.sessionId);
|
|
21252
|
+
}
|
|
21253
|
+
if (scope.from !== void 0) {
|
|
21254
|
+
conditions.push("e.started_at >= ?");
|
|
21255
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21256
|
+
}
|
|
21257
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21258
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21259
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21260
|
+
e.started_at AS occurred_at,
|
|
21261
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21262
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21263
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21264
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21265
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21266
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21267
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21268
|
+
FROM inspection_findings f
|
|
21269
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21270
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21271
|
+
WHERE ${conditions.join(" AND ")}
|
|
21272
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21273
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21274
|
+
LIMIT ?`;
|
|
21275
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21276
|
+
for (; ; ) {
|
|
21277
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21278
|
+
...params,
|
|
21279
|
+
after.startedAtMs,
|
|
21280
|
+
after.startedAtMs,
|
|
21281
|
+
after.id,
|
|
21282
|
+
SCAN_BATCH_ROWS
|
|
21283
|
+
]);
|
|
21284
|
+
for (const r of rows) {
|
|
21285
|
+
yield {
|
|
21286
|
+
id: r.id,
|
|
21287
|
+
ruleId: r.rule_id,
|
|
21288
|
+
category: r.category,
|
|
21289
|
+
severity: r.severity,
|
|
21290
|
+
maskedMatch: r.masked_match,
|
|
21291
|
+
actionTaken: r.action_taken,
|
|
21292
|
+
confidence: r.confidence,
|
|
21293
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21294
|
+
sourceTool: r.source_tool,
|
|
21295
|
+
repo: r.repo ?? "",
|
|
21296
|
+
file: r.file ?? "",
|
|
21297
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21298
|
+
eventId: r.event_id,
|
|
21299
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21300
|
+
status: deriveInstanceStatus(r)
|
|
21301
|
+
};
|
|
21302
|
+
}
|
|
21303
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21304
|
+
const lastRow = rows[rows.length - 1];
|
|
21305
|
+
if (lastRow === void 0) return;
|
|
21306
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21307
|
+
}
|
|
21308
|
+
}
|
|
20511
21309
|
groupAggregates(withSearchText, scope) {
|
|
20512
21310
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20513
21311
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20768,7 +21566,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20768
21566
|
};
|
|
20769
21567
|
|
|
20770
21568
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20771
|
-
import { createHash as createHash2, randomUUID as
|
|
21569
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20772
21570
|
|
|
20773
21571
|
// ../../packages/persistence/src/semver.ts
|
|
20774
21572
|
function parse3(version2) {
|
|
@@ -20919,7 +21717,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20919
21717
|
let behind = false;
|
|
20920
21718
|
for (const row of rows) {
|
|
20921
21719
|
const params = {
|
|
20922
|
-
id:
|
|
21720
|
+
id: randomUUID3(),
|
|
20923
21721
|
namespace: row.namespace,
|
|
20924
21722
|
packId: row.packId,
|
|
20925
21723
|
version: row.version,
|
|
@@ -20931,7 +21729,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20931
21729
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20932
21730
|
this.upsertAvailableStmt.run({
|
|
20933
21731
|
...params,
|
|
20934
|
-
id:
|
|
21732
|
+
id: randomUUID3(),
|
|
20935
21733
|
recordedBy: meta3?.recordedBy ?? null
|
|
20936
21734
|
});
|
|
20937
21735
|
} else {
|
|
@@ -21254,14 +22052,15 @@ var SqliteInventoryRepository = class {
|
|
|
21254
22052
|
};
|
|
21255
22053
|
|
|
21256
22054
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21257
|
-
import { randomUUID as
|
|
22055
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21258
22056
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21259
22057
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21260
22058
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21261
22059
|
var HARNESS_LABELS = {
|
|
21262
22060
|
claudecode: "Claude Code",
|
|
21263
22061
|
cursor: "Cursor",
|
|
21264
|
-
codex: "Codex"
|
|
22062
|
+
codex: "Codex",
|
|
22063
|
+
antigravity: "Antigravity"
|
|
21265
22064
|
};
|
|
21266
22065
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21267
22066
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21276,6 +22075,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21276
22075
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21277
22076
|
if (t.includes("cursor")) return "cursor";
|
|
21278
22077
|
if (t.includes("codex")) return "codex";
|
|
22078
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21279
22079
|
return null;
|
|
21280
22080
|
}
|
|
21281
22081
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21734,7 +22534,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21734
22534
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21735
22535
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21736
22536
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21737
|
-
).run({ id:
|
|
22537
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21738
22538
|
}
|
|
21739
22539
|
return true;
|
|
21740
22540
|
}
|
|
@@ -21755,7 +22555,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21755
22555
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21756
22556
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21757
22557
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21758
|
-
).run({ id:
|
|
22558
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21759
22559
|
}
|
|
21760
22560
|
this.configRowsCache = void 0;
|
|
21761
22561
|
return "ok";
|
|
@@ -22052,7 +22852,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22052
22852
|
};
|
|
22053
22853
|
|
|
22054
22854
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22055
|
-
import { randomUUID as
|
|
22855
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22056
22856
|
var SqlitePoliciesRepository = class {
|
|
22057
22857
|
constructor(db) {
|
|
22058
22858
|
this.db = db;
|
|
@@ -22087,7 +22887,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22087
22887
|
failOpenTransaction(this.db, () => {
|
|
22088
22888
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22089
22889
|
stmt.run({
|
|
22090
|
-
id:
|
|
22890
|
+
id: randomUUID5(),
|
|
22091
22891
|
target: JSON.stringify({ category }),
|
|
22092
22892
|
action,
|
|
22093
22893
|
now: Date.now()
|
|
@@ -22107,7 +22907,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22107
22907
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22108
22908
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22109
22909
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22110
|
-
).run({ id:
|
|
22910
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22111
22911
|
}
|
|
22112
22912
|
// Caps every global per-category policy currently set to block/redact down
|
|
22113
22913
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22175,7 +22975,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22175
22975
|
};
|
|
22176
22976
|
|
|
22177
22977
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22178
|
-
import { randomUUID as
|
|
22978
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22179
22979
|
var SqliteProjectFilesRepository = class {
|
|
22180
22980
|
constructor(db) {
|
|
22181
22981
|
this.db = db;
|
|
@@ -22207,7 +23007,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22207
23007
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22208
23008
|
for (const file2 of scan2.files) {
|
|
22209
23009
|
this.upsertStmt.run({
|
|
22210
|
-
id:
|
|
23010
|
+
id: randomUUID6(),
|
|
22211
23011
|
projectId,
|
|
22212
23012
|
path: file2.path,
|
|
22213
23013
|
name: file2.name,
|
|
@@ -22221,7 +23021,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22221
23021
|
};
|
|
22222
23022
|
|
|
22223
23023
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22224
|
-
import { randomUUID as
|
|
23024
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22225
23025
|
var SqliteResolutionsRepository = class {
|
|
22226
23026
|
constructor(db, now = () => Date.now()) {
|
|
22227
23027
|
this.db = db;
|
|
@@ -22275,7 +23075,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22275
23075
|
*/
|
|
22276
23076
|
insertResolution(r) {
|
|
22277
23077
|
this.insertStmt.run({
|
|
22278
|
-
id:
|
|
23078
|
+
id: randomUUID7(),
|
|
22279
23079
|
findingKey: r.findingKey,
|
|
22280
23080
|
status: FindingStatus.parse(r.status),
|
|
22281
23081
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22334,13 +23134,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22334
23134
|
this.readStmt = db.prepare(
|
|
22335
23135
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22336
23136
|
);
|
|
23137
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23138
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23139
|
+
);
|
|
23140
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23141
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23142
|
+
);
|
|
22337
23143
|
}
|
|
22338
23144
|
db;
|
|
22339
23145
|
upsertStmt;
|
|
22340
23146
|
readStmt;
|
|
23147
|
+
countQuarantinedStmt;
|
|
23148
|
+
clearQuarantinedStmt;
|
|
22341
23149
|
getVerdict(ruleKey) {
|
|
22342
23150
|
return getRow(this.readStmt, { ruleKey });
|
|
22343
23151
|
}
|
|
23152
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23153
|
+
countQuarantined() {
|
|
23154
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23155
|
+
}
|
|
23156
|
+
/**
|
|
23157
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23158
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23159
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23160
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23161
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23162
|
+
*
|
|
23163
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23164
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23165
|
+
*
|
|
23166
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23167
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23168
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23169
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23170
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23171
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23172
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23173
|
+
* to restore are silently still disabled.
|
|
23174
|
+
*/
|
|
23175
|
+
clearQuarantined() {
|
|
23176
|
+
const before = this.countQuarantined();
|
|
23177
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23178
|
+
this.clearQuarantinedStmt.run();
|
|
23179
|
+
});
|
|
23180
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23181
|
+
}
|
|
22344
23182
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22345
23183
|
failOpenTransaction(this.db, () => {
|
|
22346
23184
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22395,7 +23233,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22395
23233
|
};
|
|
22396
23234
|
|
|
22397
23235
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22398
|
-
import { randomUUID as
|
|
23236
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23237
|
+
function pageLimit(requested, fallback) {
|
|
23238
|
+
if (requested === void 0) return fallback;
|
|
23239
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23240
|
+
}
|
|
23241
|
+
function encodeReuseCursor(payload) {
|
|
23242
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23243
|
+
}
|
|
23244
|
+
function decodeReuseCursor(cursor) {
|
|
23245
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23246
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23247
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23248
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23249
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23250
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23251
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23252
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23253
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23254
|
+
}
|
|
23255
|
+
return null;
|
|
23256
|
+
}
|
|
23257
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23258
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23259
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23260
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23261
|
+
function toSighting(row) {
|
|
23262
|
+
return {
|
|
23263
|
+
location: row.location,
|
|
23264
|
+
kind: row.kind,
|
|
23265
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23266
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23267
|
+
};
|
|
23268
|
+
}
|
|
22399
23269
|
var SELECT_COLUMNS = `
|
|
22400
23270
|
pointer_id AS pointerId,
|
|
22401
23271
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22579,39 +23449,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22579
23449
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22580
23450
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22581
23451
|
).run({
|
|
22582
|
-
id:
|
|
23452
|
+
id: randomUUID8(),
|
|
22583
23453
|
pointerId: entry.pointerId,
|
|
22584
23454
|
location: entry.location,
|
|
22585
23455
|
kind: entry.kind,
|
|
22586
23456
|
now
|
|
22587
23457
|
});
|
|
22588
23458
|
}
|
|
22589
|
-
|
|
23459
|
+
/**
|
|
23460
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23461
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23462
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23463
|
+
*
|
|
23464
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23465
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23466
|
+
*/
|
|
23467
|
+
sightingsFor(pointerIds) {
|
|
23468
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23469
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22590
23470
|
const rows = allRows(
|
|
22591
23471
|
this.db.prepare(
|
|
22592
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22593
|
-
|
|
23472
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23473
|
+
FROM secret_vault_sighting
|
|
23474
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23475
|
+
ORDER BY last_seen DESC`
|
|
22594
23476
|
),
|
|
22595
|
-
|
|
23477
|
+
pointerIds
|
|
22596
23478
|
);
|
|
23479
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23480
|
+
return byPointer;
|
|
23481
|
+
}
|
|
23482
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23483
|
+
toInventoryEntries(rows) {
|
|
23484
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22597
23485
|
return rows.map((r) => ({
|
|
22598
|
-
|
|
22599
|
-
|
|
23486
|
+
pointerId: r.pointer_id,
|
|
23487
|
+
category: r.category,
|
|
23488
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23489
|
+
maskedMatch: r.masked_match,
|
|
23490
|
+
occurrences: r.occurrence_count,
|
|
22600
23491
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22601
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23492
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23493
|
+
revealGrantId: r.grant_id,
|
|
23494
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22602
23495
|
}));
|
|
22603
23496
|
}
|
|
22604
23497
|
/**
|
|
22605
|
-
* The dashboard inventory
|
|
22606
|
-
* its sightings and the active
|
|
22607
|
-
* Raw-free by construction — neither
|
|
22608
|
-
* columns are selected.
|
|
23498
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23499
|
+
* value's descriptor data joined with its sightings and the active
|
|
23500
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23501
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23502
|
+
*
|
|
23503
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23504
|
+
* sees never depends on how far they have paged.
|
|
22609
23505
|
*/
|
|
22610
|
-
listInventory(now = Date.now()) {
|
|
23506
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23507
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23508
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23509
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22611
23510
|
const rows = allRows(
|
|
22612
23511
|
this.db.prepare(
|
|
22613
|
-
`SELECT
|
|
22614
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23512
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22615
23513
|
(SELECT e.id FROM exceptions e
|
|
22616
23514
|
WHERE e.rule_id = v.rule_id
|
|
22617
23515
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22619,45 +23517,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22619
23517
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22620
23518
|
LIMIT 1) AS grant_id
|
|
22621
23519
|
FROM secret_vault v
|
|
22622
|
-
|
|
23520
|
+
${where}
|
|
23521
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23522
|
+
LIMIT :limit`
|
|
22623
23523
|
),
|
|
22624
|
-
{
|
|
23524
|
+
bindParams({
|
|
23525
|
+
now,
|
|
23526
|
+
limit: limit + 1,
|
|
23527
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23528
|
+
})
|
|
22625
23529
|
);
|
|
22626
|
-
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22632
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
23530
|
+
const hasMore = rows.length > limit;
|
|
23531
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23532
|
+
const last = page[page.length - 1];
|
|
23533
|
+
return {
|
|
23534
|
+
totals: { values: this.countEntries() },
|
|
23535
|
+
items: this.toInventoryEntries(page),
|
|
23536
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23537
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23538
|
+
};
|
|
23539
|
+
}
|
|
23540
|
+
/**
|
|
23541
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23542
|
+
* than one location — most-reused first, one page at a time.
|
|
23543
|
+
*
|
|
23544
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23545
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23546
|
+
* under-report exactly the values a reader most needs to see.
|
|
23547
|
+
*/
|
|
23548
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23549
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23550
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23551
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23552
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23553
|
+
const rows = allRows(
|
|
23554
|
+
this.db.prepare(
|
|
23555
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23556
|
+
(SELECT e.id FROM exceptions e
|
|
23557
|
+
WHERE e.rule_id = v.rule_id
|
|
23558
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23559
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23560
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23561
|
+
LIMIT 1) AS grant_id
|
|
23562
|
+
FROM secret_vault v
|
|
23563
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23564
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23565
|
+
LIMIT :limit`
|
|
23566
|
+
),
|
|
23567
|
+
bindParams({
|
|
23568
|
+
now,
|
|
23569
|
+
limit: limit + 1,
|
|
23570
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23571
|
+
})
|
|
23572
|
+
);
|
|
23573
|
+
const hasMore = rows.length > limit;
|
|
23574
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23575
|
+
const last = page[page.length - 1];
|
|
23576
|
+
return {
|
|
23577
|
+
totals: { reused: this.countReused() },
|
|
23578
|
+
items: this.toInventoryEntries(page),
|
|
23579
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23580
|
+
};
|
|
22637
23581
|
}
|
|
22638
23582
|
/**
|
|
22639
|
-
* The de-reference trail, newest first. By default the
|
|
22640
|
-
* reasons (display, view-render) are hidden and counted
|
|
22641
|
-
* that matter as a signal are the model crossings, and
|
|
22642
|
-
* render noise would defeat the audit's purpose.
|
|
23583
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23584
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23585
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23586
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23587
|
+
*
|
|
23588
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23589
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23590
|
+
* the reader pages.
|
|
22643
23591
|
*/
|
|
22644
|
-
listDerefs(
|
|
22645
|
-
const limit =
|
|
22646
|
-
const
|
|
23592
|
+
listDerefs(query = {}) {
|
|
23593
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23594
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23595
|
+
const conditions = [];
|
|
23596
|
+
if (query.includeBatched !== true) {
|
|
23597
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23598
|
+
}
|
|
23599
|
+
if (cursor !== null) {
|
|
23600
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23601
|
+
}
|
|
23602
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22647
23603
|
const rows = allRows(
|
|
22648
23604
|
this.db.prepare(
|
|
22649
23605
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22650
23606
|
FROM secret_vault_deref ${where}
|
|
22651
|
-
ORDER BY at DESC,
|
|
23607
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22652
23608
|
),
|
|
22653
|
-
{
|
|
23609
|
+
bindParams({
|
|
23610
|
+
limit: limit + 1,
|
|
23611
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23612
|
+
})
|
|
22654
23613
|
);
|
|
22655
|
-
const
|
|
23614
|
+
const hasMore = rows.length > limit;
|
|
23615
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23616
|
+
const last = page[page.length - 1];
|
|
23617
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22656
23618
|
this.db,
|
|
22657
23619
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22658
23620
|
);
|
|
22659
23621
|
return {
|
|
22660
|
-
|
|
23622
|
+
items: page.map((r) => ({
|
|
22661
23623
|
id: r.id,
|
|
22662
23624
|
pointerId: r.pointer_id,
|
|
22663
23625
|
at: new Date(r.at).toISOString(),
|
|
@@ -22667,12 +23629,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22667
23629
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22668
23630
|
pointerCount: r.pointer_count
|
|
22669
23631
|
})),
|
|
23632
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22670
23633
|
hiddenBatched
|
|
22671
23634
|
};
|
|
22672
23635
|
}
|
|
22673
23636
|
countEntries() {
|
|
22674
23637
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22675
23638
|
}
|
|
23639
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23640
|
+
countReused() {
|
|
23641
|
+
return countScalar(
|
|
23642
|
+
this.db,
|
|
23643
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23644
|
+
);
|
|
23645
|
+
}
|
|
22676
23646
|
};
|
|
22677
23647
|
|
|
22678
23648
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22687,7 +23657,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22687
23657
|
var SCAN_COVERAGE = [
|
|
22688
23658
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22689
23659
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22690
|
-
{ provider: "codex", coverage:
|
|
23660
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23661
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23662
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22691
23663
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22692
23664
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22693
23665
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23020,7 +23992,7 @@ var SqliteSecurityRepository = class {
|
|
|
23020
23992
|
};
|
|
23021
23993
|
|
|
23022
23994
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23023
|
-
import { randomUUID as
|
|
23995
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23024
23996
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23025
23997
|
var IN_CHUNK = 500;
|
|
23026
23998
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23276,7 +24248,7 @@ var SqliteSharesRepository = class {
|
|
|
23276
24248
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23277
24249
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23278
24250
|
).run({
|
|
23279
|
-
id:
|
|
24251
|
+
id: randomUUID9(),
|
|
23280
24252
|
destinationId,
|
|
23281
24253
|
host: dest.host,
|
|
23282
24254
|
decision,
|
|
@@ -23425,7 +24397,7 @@ var SqliteSharesRepository = class {
|
|
|
23425
24397
|
let destinationId = destIds.get(hit.host);
|
|
23426
24398
|
if (destinationId === void 0) {
|
|
23427
24399
|
destStmt.run({
|
|
23428
|
-
id:
|
|
24400
|
+
id: randomUUID9(),
|
|
23429
24401
|
kind: hit.kind,
|
|
23430
24402
|
name: hit.name,
|
|
23431
24403
|
host: hit.host,
|
|
@@ -23441,7 +24413,7 @@ var SqliteSharesRepository = class {
|
|
|
23441
24413
|
let endpointId = endpointIds.get(endpointKey);
|
|
23442
24414
|
if (endpointId === void 0) {
|
|
23443
24415
|
endpointStmt.run({
|
|
23444
|
-
id:
|
|
24416
|
+
id: randomUUID9(),
|
|
23445
24417
|
destinationId,
|
|
23446
24418
|
method: hit.method,
|
|
23447
24419
|
transport: hit.transport,
|
|
@@ -23454,7 +24426,7 @@ var SqliteSharesRepository = class {
|
|
|
23454
24426
|
endpointIds.set(endpointKey, endpointId);
|
|
23455
24427
|
}
|
|
23456
24428
|
siteStmt.run({
|
|
23457
|
-
id:
|
|
24429
|
+
id: randomUUID9(),
|
|
23458
24430
|
endpointId,
|
|
23459
24431
|
project: input.project,
|
|
23460
24432
|
projectKey: input.projectKey,
|
|
@@ -23819,6 +24791,9 @@ function purgeSampleData(db) {
|
|
|
23819
24791
|
}
|
|
23820
24792
|
|
|
23821
24793
|
// ../../packages/persistence/src/database.ts
|
|
24794
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24795
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24796
|
+
);
|
|
23822
24797
|
function linkHost(input, hostId) {
|
|
23823
24798
|
return hostId ? { ...input, hostId } : input;
|
|
23824
24799
|
}
|
|
@@ -23840,21 +24815,34 @@ function openWithPragmas(file2) {
|
|
|
23840
24815
|
}
|
|
23841
24816
|
return db;
|
|
23842
24817
|
}
|
|
23843
|
-
function backupLegacyStore(file2) {
|
|
23844
|
-
|
|
23845
|
-
|
|
23846
|
-
|
|
23847
|
-
|
|
23848
|
-
|
|
24818
|
+
function backupLegacyStore(db, file2) {
|
|
24819
|
+
reapStalePartials(file2);
|
|
24820
|
+
const backup = backupPath(file2, "legacy");
|
|
24821
|
+
let snapshotted = false;
|
|
24822
|
+
let snapshotError;
|
|
24823
|
+
try {
|
|
24824
|
+
snapshotStore(db, backup);
|
|
24825
|
+
snapshotted = true;
|
|
24826
|
+
} catch (error51) {
|
|
24827
|
+
snapshotError = error51;
|
|
24828
|
+
} finally {
|
|
24829
|
+
db.close();
|
|
23849
24830
|
}
|
|
24831
|
+
if (!snapshotted) {
|
|
24832
|
+
akaWarn(
|
|
24833
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24834
|
+
);
|
|
24835
|
+
moveStoreAside(file2, backup);
|
|
24836
|
+
return backup;
|
|
24837
|
+
}
|
|
24838
|
+
discardStore(file2, backup);
|
|
23850
24839
|
return backup;
|
|
23851
24840
|
}
|
|
23852
24841
|
function openAndInitialize(file2) {
|
|
23853
24842
|
let db = openWithPragmas(file2);
|
|
23854
24843
|
try {
|
|
23855
24844
|
if (isForeignSqliteLineage(db)) {
|
|
23856
|
-
db
|
|
23857
|
-
const backup = backupLegacyStore(file2);
|
|
24845
|
+
const backup = backupLegacyStore(db, file2);
|
|
23858
24846
|
db = openWithPragmas(file2);
|
|
23859
24847
|
akaWarn(
|
|
23860
24848
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23898,7 +24886,7 @@ function openAndInitialize(file2) {
|
|
|
23898
24886
|
}
|
|
23899
24887
|
function openLocalDatabase(dir) {
|
|
23900
24888
|
ensureDataDirSync(dir);
|
|
23901
|
-
const file2 =
|
|
24889
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23902
24890
|
const {
|
|
23903
24891
|
db,
|
|
23904
24892
|
events,
|
|
@@ -24015,7 +25003,7 @@ function openLocalDatabase(dir) {
|
|
|
24015
25003
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24016
25004
|
if (!definitionId) continue;
|
|
24017
25005
|
inspectionFindings.insertFinding({
|
|
24018
|
-
id:
|
|
25006
|
+
id: randomUUID10(),
|
|
24019
25007
|
auditEventId: record2.scanEvent.id,
|
|
24020
25008
|
inspectionDefinitionId: definitionId,
|
|
24021
25009
|
span: finding.span,
|
|
@@ -24121,22 +25109,38 @@ function openLocalDatabase(dir) {
|
|
|
24121
25109
|
transaction,
|
|
24122
25110
|
close: () => {
|
|
24123
25111
|
db.close();
|
|
24124
|
-
}
|
|
25112
|
+
},
|
|
25113
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25114
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24125
25115
|
};
|
|
24126
25116
|
}
|
|
24127
25117
|
|
|
25118
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25119
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25120
|
+
import {
|
|
25121
|
+
closeSync,
|
|
25122
|
+
existsSync as existsSync2,
|
|
25123
|
+
openSync,
|
|
25124
|
+
readFileSync,
|
|
25125
|
+
rmSync as rmSync3,
|
|
25126
|
+
statSync as statSync2,
|
|
25127
|
+
writeFileSync as writeFileSync2
|
|
25128
|
+
} from "fs";
|
|
25129
|
+
import { hostname as hostname3 } from "os";
|
|
25130
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25131
|
+
|
|
24128
25132
|
// ../../packages/persistence/src/finding-key.ts
|
|
24129
25133
|
import { createHash as createHash3 } from "crypto";
|
|
24130
25134
|
|
|
24131
25135
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24132
25136
|
import { createHmac, randomBytes } from "crypto";
|
|
24133
|
-
import { existsSync as
|
|
24134
|
-
import { join as
|
|
25137
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25138
|
+
import { join as join3 } from "path";
|
|
24135
25139
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24136
|
-
var
|
|
25140
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24137
25141
|
var KEY_MATERIAL_BYTES = 32;
|
|
24138
25142
|
function keyFilePath(dataDir2) {
|
|
24139
|
-
return
|
|
25143
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24140
25144
|
}
|
|
24141
25145
|
function parseKeyFile(raw) {
|
|
24142
25146
|
const parsed = JSON.parse(raw);
|
|
@@ -24159,7 +25163,7 @@ function parseKeyFile(raw) {
|
|
|
24159
25163
|
function readFingerprintKey(dataDir2) {
|
|
24160
25164
|
let raw;
|
|
24161
25165
|
try {
|
|
24162
|
-
raw =
|
|
25166
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24163
25167
|
} catch (err) {
|
|
24164
25168
|
if (err.code === "ENOENT") return null;
|
|
24165
25169
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24171,18 +25175,18 @@ function readFingerprintKey(dataDir2) {
|
|
|
24171
25175
|
import { renameSync as renameSync3 } from "fs";
|
|
24172
25176
|
import { mkdir } from "fs/promises";
|
|
24173
25177
|
import { homedir } from "os";
|
|
24174
|
-
import { join as
|
|
25178
|
+
import { join as join4 } from "path";
|
|
24175
25179
|
function defaultDataDir() {
|
|
24176
|
-
return
|
|
25180
|
+
return join4(homedir(), ".aka");
|
|
24177
25181
|
}
|
|
24178
25182
|
function settingsDir(base = defaultDataDir()) {
|
|
24179
|
-
return
|
|
25183
|
+
return join4(base, "settings");
|
|
24180
25184
|
}
|
|
24181
25185
|
function dataDir(base = defaultDataDir()) {
|
|
24182
|
-
return
|
|
25186
|
+
return join4(base, "data");
|
|
24183
25187
|
}
|
|
24184
25188
|
function dbPath(base = defaultDataDir()) {
|
|
24185
|
-
return
|
|
25189
|
+
return join4(dataDir(base), "aka.db");
|
|
24186
25190
|
}
|
|
24187
25191
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24188
25192
|
ensureDataDirSync(dir);
|
|
@@ -24195,8 +25199,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24195
25199
|
for (const { name, dest } of moves) {
|
|
24196
25200
|
try {
|
|
24197
25201
|
ensureDataDirSync(dest);
|
|
24198
|
-
const moved =
|
|
24199
|
-
renameSync3(
|
|
25202
|
+
const moved = join4(dest, name);
|
|
25203
|
+
renameSync3(join4(base, name), moved);
|
|
24200
25204
|
tightenFile(moved);
|
|
24201
25205
|
} catch {
|
|
24202
25206
|
}
|
|
@@ -24204,10 +25208,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24204
25208
|
}
|
|
24205
25209
|
|
|
24206
25210
|
// ../../packages/persistence/src/settings.ts
|
|
24207
|
-
import { readFileSync as
|
|
24208
|
-
import { join as
|
|
25211
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25212
|
+
import { join as join5 } from "path";
|
|
25213
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24209
25214
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24210
|
-
const record2 = readJson(
|
|
25215
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24211
25216
|
if (!record2) return defaultWorkspaceSettings();
|
|
24212
25217
|
try {
|
|
24213
25218
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24218,7 +25223,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24218
25223
|
function readJson(file2) {
|
|
24219
25224
|
let text;
|
|
24220
25225
|
try {
|
|
24221
|
-
text =
|
|
25226
|
+
text = readFileSync3(file2, "utf8");
|
|
24222
25227
|
} catch {
|
|
24223
25228
|
return null;
|
|
24224
25229
|
}
|
|
@@ -24240,34 +25245,34 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24240
25245
|
import {
|
|
24241
25246
|
chmodSync as chmodSync2,
|
|
24242
25247
|
mkdirSync as mkdirSync2,
|
|
24243
|
-
readFileSync as
|
|
25248
|
+
readFileSync as readFileSync4,
|
|
24244
25249
|
renameSync as renameSync4,
|
|
24245
|
-
rmSync as
|
|
24246
|
-
statSync,
|
|
24247
|
-
writeFileSync as
|
|
25250
|
+
rmSync as rmSync4,
|
|
25251
|
+
statSync as statSync3,
|
|
25252
|
+
writeFileSync as writeFileSync3
|
|
24248
25253
|
} from "fs";
|
|
24249
|
-
import { join as
|
|
25254
|
+
import { join as join6 } from "path";
|
|
24250
25255
|
|
|
24251
25256
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24252
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25257
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24253
25258
|
|
|
24254
25259
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
24255
|
-
import { existsSync as
|
|
24256
|
-
import { join as
|
|
25260
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25261
|
+
import { join as join7 } from "path";
|
|
24257
25262
|
var MARKER = "warn-era-capped";
|
|
24258
25263
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
24259
25264
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
24260
|
-
const marker =
|
|
24261
|
-
if (
|
|
25265
|
+
const marker = join7(dataDir2, MARKER);
|
|
25266
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
24262
25267
|
const capped = db.policies.capCategoryActions();
|
|
24263
|
-
|
|
25268
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
24264
25269
|
`, { mode: DATA_FILE_MODE });
|
|
24265
25270
|
return { capped };
|
|
24266
25271
|
}
|
|
24267
25272
|
|
|
24268
25273
|
// ../../packages/plugin-sdk/src/config.ts
|
|
24269
|
-
import { existsSync as
|
|
24270
|
-
import { join as
|
|
25274
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25275
|
+
import { join as join8 } from "path";
|
|
24271
25276
|
|
|
24272
25277
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
24273
25278
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -24318,11 +25323,11 @@ function resolveProvider() {
|
|
|
24318
25323
|
}
|
|
24319
25324
|
|
|
24320
25325
|
// ../../packages/plugin-sdk/src/config.ts
|
|
24321
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25326
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
24322
25327
|
try {
|
|
24323
25328
|
ensureLayoutDirSync(base);
|
|
24324
|
-
const settingsFile =
|
|
24325
|
-
if (
|
|
25329
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25330
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
24326
25331
|
} catch {
|
|
24327
25332
|
}
|
|
24328
25333
|
migrateLegacyLayout(base);
|
|
@@ -24333,21 +25338,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
24333
25338
|
dbPath: dbPath(base),
|
|
24334
25339
|
settingsDir: settingsDir(base),
|
|
24335
25340
|
onboarded: settings.onboardedAt != null,
|
|
24336
|
-
provider: resolveProviderSafe()
|
|
25341
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
24337
25342
|
};
|
|
24338
25343
|
}
|
|
24339
|
-
function resolveProviderSafe() {
|
|
25344
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
24340
25345
|
try {
|
|
24341
|
-
return
|
|
25346
|
+
return resolveProviderFn();
|
|
24342
25347
|
} catch {
|
|
24343
25348
|
return { provider: "anthropic" };
|
|
24344
25349
|
}
|
|
24345
25350
|
}
|
|
24346
25351
|
|
|
24347
25352
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
24348
|
-
import { readdirSync, readFileSync as
|
|
25353
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
24349
25354
|
import { homedir as homedir2 } from "os";
|
|
24350
|
-
import { basename as
|
|
25355
|
+
import { basename as basename3, join as join10 } from "path";
|
|
24351
25356
|
|
|
24352
25357
|
// ../../packages/detections/src/egress/registry.ts
|
|
24353
25358
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -24921,6 +25926,40 @@ function escapeRegExp2(value) {
|
|
|
24921
25926
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24922
25927
|
}
|
|
24923
25928
|
|
|
25929
|
+
// ../../packages/detections/src/regex-cache.ts
|
|
25930
|
+
var singles = /* @__PURE__ */ new WeakMap();
|
|
25931
|
+
var keywordLists = /* @__PURE__ */ new WeakMap();
|
|
25932
|
+
var labelLists = /* @__PURE__ */ new WeakMap();
|
|
25933
|
+
function listCache(kind) {
|
|
25934
|
+
return kind === "keyword" ? keywordLists : labelLists;
|
|
25935
|
+
}
|
|
25936
|
+
function memoizedRegExp(owner, build) {
|
|
25937
|
+
const cached2 = singles.get(owner);
|
|
25938
|
+
if (cached2 !== void 0) {
|
|
25939
|
+
cached2.lastIndex = 0;
|
|
25940
|
+
return cached2;
|
|
25941
|
+
}
|
|
25942
|
+
const compiled = build();
|
|
25943
|
+
singles.set(owner, compiled);
|
|
25944
|
+
return compiled;
|
|
25945
|
+
}
|
|
25946
|
+
function memoizedRegExpList(kind, owner, build) {
|
|
25947
|
+
const cache = listCache(kind);
|
|
25948
|
+
const cached2 = cache.get(owner);
|
|
25949
|
+
if (cached2 !== void 0) {
|
|
25950
|
+
if (cached2.stateful) {
|
|
25951
|
+
for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
|
|
25952
|
+
}
|
|
25953
|
+
return cached2.entries;
|
|
25954
|
+
}
|
|
25955
|
+
const entries = build();
|
|
25956
|
+
cache.set(owner, {
|
|
25957
|
+
entries,
|
|
25958
|
+
stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
|
|
25959
|
+
});
|
|
25960
|
+
return entries;
|
|
25961
|
+
}
|
|
25962
|
+
|
|
24924
25963
|
// ../../packages/detections/src/matchers/limits.ts
|
|
24925
25964
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
24926
25965
|
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
@@ -24931,10 +25970,17 @@ var KeywordMatcher2 = class {
|
|
|
24931
25970
|
if (rule.matcher.type !== "keyword") return [];
|
|
24932
25971
|
const { keywords, caseSensitive } = rule.matcher;
|
|
24933
25972
|
const spans = [];
|
|
24934
|
-
|
|
24935
|
-
|
|
25973
|
+
const compiled = memoizedRegExpList(
|
|
25974
|
+
"keyword",
|
|
25975
|
+
rule.matcher,
|
|
25976
|
+
() => keywords.map((kw) => {
|
|
25977
|
+
if (kw.length === 0) return void 0;
|
|
25978
|
+
return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
25979
|
+
})
|
|
25980
|
+
);
|
|
25981
|
+
for (const re of compiled) {
|
|
25982
|
+
if (re === void 0) continue;
|
|
24936
25983
|
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
24937
|
-
const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
24938
25984
|
let m;
|
|
24939
25985
|
while ((m = re.exec(text)) !== null) {
|
|
24940
25986
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -24950,7 +25996,10 @@ var RegexMatcher2 = class {
|
|
|
24950
25996
|
match(text, rule) {
|
|
24951
25997
|
if (rule.matcher.type !== "regex") return [];
|
|
24952
25998
|
const { pattern, flags, captureGroup } = rule.matcher;
|
|
24953
|
-
const re =
|
|
25999
|
+
const re = memoizedRegExp(
|
|
26000
|
+
rule.matcher,
|
|
26001
|
+
() => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
|
|
26002
|
+
);
|
|
24954
26003
|
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
24955
26004
|
const spans = [];
|
|
24956
26005
|
let m;
|
|
@@ -26711,7 +27760,7 @@ var gcp_service_account_default = {
|
|
|
26711
27760
|
severity: "critical",
|
|
26712
27761
|
matcher: {
|
|
26713
27762
|
type: "regex",
|
|
26714
|
-
pattern: "
|
|
27763
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
26715
27764
|
flags: "g"
|
|
26716
27765
|
},
|
|
26717
27766
|
examples: [
|
|
@@ -27083,40 +28132,71 @@ function bundledDetections() {
|
|
|
27083
28132
|
}
|
|
27084
28133
|
|
|
27085
28134
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
27086
|
-
import { existsSync as
|
|
27087
|
-
import { basename, dirname, isAbsolute, join as
|
|
28135
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
28136
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
27088
28137
|
|
|
27089
28138
|
// ../../packages/plugin-sdk/src/events.ts
|
|
27090
|
-
import { createHash as createHash4, randomUUID as
|
|
28139
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28140
|
+
|
|
28141
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
28142
|
+
import { existsSync as existsSync7 } from "fs";
|
|
28143
|
+
import { fileURLToPath } from "url";
|
|
28144
|
+
import { Worker } from "worker_threads";
|
|
27091
28145
|
|
|
27092
28146
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
27093
|
-
import { arch, hostname as
|
|
28147
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
27094
28148
|
|
|
27095
28149
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
27096
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
27097
|
-
import { join as
|
|
28150
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
28151
|
+
import { join as join11 } from "path";
|
|
27098
28152
|
|
|
27099
28153
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
27100
|
-
import { readdirSync as
|
|
27101
|
-
import { basename as
|
|
28154
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
28155
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
27102
28156
|
|
|
27103
28157
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
27104
28158
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
27105
|
-
import { existsSync as
|
|
27106
|
-
import { basename as
|
|
28159
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
28160
|
+
import { basename as basename5, join as join12 } from "path";
|
|
28161
|
+
|
|
28162
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
28163
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
28164
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28165
|
+
return v;
|
|
28166
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28167
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
28168
|
+
if (typeof v !== "string") return false;
|
|
28169
|
+
const normalized = v.trim().toLowerCase();
|
|
28170
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
28171
|
+
}, external_exports.boolean()).catch(false);
|
|
28172
|
+
var antigravityProviderEnvShape = {
|
|
28173
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
28174
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
28175
|
+
};
|
|
28176
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
28177
|
+
|
|
28178
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
28179
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
28180
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28181
|
+
return v;
|
|
28182
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28183
|
+
var codexProviderEnvShape = {
|
|
28184
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
28185
|
+
};
|
|
28186
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
27107
28187
|
|
|
27108
28188
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
27109
|
-
import { randomUUID as
|
|
28189
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
27110
28190
|
|
|
27111
28191
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
27112
28192
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
27113
28193
|
|
|
27114
28194
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
27115
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
27116
|
-
import { join as
|
|
28195
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
28196
|
+
import { join as join13 } from "path";
|
|
27117
28197
|
|
|
27118
28198
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
27119
|
-
import { randomUUID as
|
|
28199
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
27120
28200
|
|
|
27121
28201
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
27122
28202
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -27278,7 +28358,7 @@ var StandaloneDataGateway = class {
|
|
|
27278
28358
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
27279
28359
|
const installed = this.installedScanRules();
|
|
27280
28360
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
27281
|
-
id:
|
|
28361
|
+
id: randomUUID15(),
|
|
27282
28362
|
scope: "global",
|
|
27283
28363
|
target: { ruleId },
|
|
27284
28364
|
action,
|
|
@@ -27431,16 +28511,16 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
27431
28511
|
}
|
|
27432
28512
|
|
|
27433
28513
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
27434
|
-
import { randomUUID as
|
|
28514
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
27435
28515
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
27436
28516
|
|
|
27437
28517
|
// src/command-registry.ts
|
|
27438
|
-
import { readdirSync as
|
|
27439
|
-
import { fileURLToPath } from "url";
|
|
28518
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
28519
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
27440
28520
|
var COMMAND_NAMESPACE = "aka";
|
|
27441
|
-
var COMMANDS_DIR =
|
|
28521
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
27442
28522
|
function readRegisteredCommands() {
|
|
27443
|
-
return
|
|
28523
|
+
return readdirSync5(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
27444
28524
|
}
|
|
27445
28525
|
function selectRegisteredCommands(curated, registry2) {
|
|
27446
28526
|
const registered = new Set(registry2);
|
|
@@ -27521,8 +28601,8 @@ function table(headers, rows, opts = {}) {
|
|
|
27521
28601
|
const widths = headers.map(
|
|
27522
28602
|
(h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
|
|
27523
28603
|
);
|
|
27524
|
-
const
|
|
27525
|
-
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(
|
|
28604
|
+
const sep4 = " ".repeat(gap);
|
|
28605
|
+
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep4);
|
|
27526
28606
|
const headerLine = fmt(headers.map((h) => h.toUpperCase()));
|
|
27527
28607
|
if (opts.rowSep === true) {
|
|
27528
28608
|
const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
@@ -27534,7 +28614,7 @@ function table(headers, rows, opts = {}) {
|
|
|
27534
28614
|
});
|
|
27535
28615
|
return [headerLine, rule, ...body].join("\n");
|
|
27536
28616
|
}
|
|
27537
|
-
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(
|
|
28617
|
+
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep4);
|
|
27538
28618
|
return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
|
|
27539
28619
|
}
|
|
27540
28620
|
function fenced(body) {
|
|
@@ -27546,6 +28626,54 @@ function show(body) {
|
|
|
27546
28626
|
return showBlock(body);
|
|
27547
28627
|
}
|
|
27548
28628
|
|
|
28629
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
28630
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
28631
|
+
import { join as join14 } from "path";
|
|
28632
|
+
|
|
28633
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
28634
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
28635
|
+
import { tmpdir } from "os";
|
|
28636
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
28637
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
28638
|
+
ruleId: external_exports.string(),
|
|
28639
|
+
category: DetectionCategory,
|
|
28640
|
+
valueFingerprint: external_exports.string(),
|
|
28641
|
+
keyVersion: external_exports.number(),
|
|
28642
|
+
maskedValue: external_exports.string(),
|
|
28643
|
+
justification: external_exports.string()
|
|
28644
|
+
});
|
|
28645
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
28646
|
+
category: DetectionCategory,
|
|
28647
|
+
action: BuiltinPolicyId,
|
|
28648
|
+
genuineCount: external_exports.number(),
|
|
28649
|
+
fpCount: external_exports.number(),
|
|
28650
|
+
reasoning: external_exports.string()
|
|
28651
|
+
});
|
|
28652
|
+
var JoinEntrySchema = external_exports.object({
|
|
28653
|
+
id: external_exports.string(),
|
|
28654
|
+
ruleId: external_exports.string(),
|
|
28655
|
+
category: DetectionCategory,
|
|
28656
|
+
valueFingerprint: external_exports.string().optional(),
|
|
28657
|
+
keyVersion: external_exports.number().optional(),
|
|
28658
|
+
maskedMatch: external_exports.string(),
|
|
28659
|
+
maskedContext: external_exports.string()
|
|
28660
|
+
});
|
|
28661
|
+
var PLAN_FILE_VERSION = 3;
|
|
28662
|
+
var PersistedPlanSchema = external_exports.object({
|
|
28663
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
28664
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
28665
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
28666
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
28667
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
28668
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
28669
|
+
join: external_exports.array(JoinEntrySchema),
|
|
28670
|
+
notes: external_exports.string(),
|
|
28671
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
28672
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
28673
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
28674
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
28675
|
+
});
|
|
28676
|
+
|
|
27549
28677
|
// src/render.ts
|
|
27550
28678
|
var STORE_UNAVAILABLE_NOTE = "I couldn't check my records just now \u2014 we can check again soon. Your Claude session keeps going, and I'll fill in as you work.";
|
|
27551
28679
|
var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
|