@akasecurity/ai-tc-claude-code 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -0
- package/package.json +4 -3
- package/scripts/apply-suppressions.js +1607 -534
- package/scripts/backfill.js +1969 -390
- package/scripts/filescan.js +1863 -346
- package/scripts/firstrun.js +1307 -223
- package/scripts/intro.js +370 -54
- package/scripts/message-display.js +1406 -297
- package/scripts/onboard.js +1532 -245
- package/scripts/post-tool-use.js +1885 -356
- package/scripts/pre-tool-use.js +1900 -365
- package/scripts/query.js +1306 -222
- package/scripts/reconcile.js +1435 -326
- package/scripts/remediate.js +2121 -546
- package/scripts/scan-worker.js +18006 -0
- package/scripts/session-start.js +1350 -312
- package/scripts/start-light.js +390 -74
- package/scripts/statusline.js +1306 -222
- package/scripts/stop.js +352 -79
- package/scripts/user-prompt-submit.js +1897 -368
package/scripts/post-tool-use.js
CHANGED
|
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
495
|
+
import { existsSync as existsSync5 } from "fs";
|
|
496
|
+
import { join as join8 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/database.ts
|
|
499
|
-
import { randomUUID as
|
|
500
|
-
import {
|
|
501
|
-
import { join, sep } from "path";
|
|
499
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
500
|
+
import { join as join2, sep } from "path";
|
|
502
501
|
import { DatabaseSync } from "node:sqlite";
|
|
503
502
|
|
|
504
503
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -578,6 +577,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
578
577
|
{
|
|
579
578
|
tag: "0018_serious_tana_nile",
|
|
580
579
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0019_audit_started_at_index",
|
|
583
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
587
|
+
sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
|
|
581
588
|
}
|
|
582
589
|
];
|
|
583
590
|
|
|
@@ -15315,7 +15322,17 @@ var Finding = external_exports.object({
|
|
|
15315
15322
|
}).meta({ id: "Finding" });
|
|
15316
15323
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15317
15324
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15318
|
-
var FindingProvider = external_exports.enum([
|
|
15325
|
+
var FindingProvider = external_exports.enum([
|
|
15326
|
+
"claudecode",
|
|
15327
|
+
"claudedesktop",
|
|
15328
|
+
"cursor",
|
|
15329
|
+
"copilot",
|
|
15330
|
+
"chatgpt",
|
|
15331
|
+
"claudeai",
|
|
15332
|
+
"codex",
|
|
15333
|
+
"antigravity",
|
|
15334
|
+
"api"
|
|
15335
|
+
]).meta({ id: "FindingProvider" });
|
|
15319
15336
|
var FindingCategory = external_exports.enum([
|
|
15320
15337
|
"secret",
|
|
15321
15338
|
"pii",
|
|
@@ -15369,7 +15386,16 @@ var FindingInstance = external_exports.object({
|
|
|
15369
15386
|
confidence: external_exports.number().min(0).max(1),
|
|
15370
15387
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15371
15388
|
// that predate the resolution feature stay valid.
|
|
15372
|
-
status: FindingStatus.optional()
|
|
15389
|
+
status: FindingStatus.optional(),
|
|
15390
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15391
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15392
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15393
|
+
// MOST RECENT detection event, not the first.
|
|
15394
|
+
eventId: external_exports.string().optional(),
|
|
15395
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15396
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15397
|
+
// outside a session.
|
|
15398
|
+
sessionId: external_exports.string().optional()
|
|
15373
15399
|
}).meta({ id: "FindingInstance" });
|
|
15374
15400
|
var FindingGroup = external_exports.object({
|
|
15375
15401
|
id: external_exports.string(),
|
|
@@ -15413,7 +15439,11 @@ var FindingFacets = external_exports.object({
|
|
|
15413
15439
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15414
15440
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15415
15441
|
// counted under no value.
|
|
15416
|
-
status: external_exports.array(FindingFacetItem)
|
|
15442
|
+
status: external_exports.array(FindingFacetItem),
|
|
15443
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15444
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15445
|
+
// because a group spans tools.
|
|
15446
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15417
15447
|
}).meta({ id: "FindingFacets" });
|
|
15418
15448
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15419
15449
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15431,6 +15461,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15431
15461
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15432
15462
|
// session → findings drilldown). Findings without a session never match.
|
|
15433
15463
|
sessionId: external_exports.string().optional(),
|
|
15464
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15465
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15466
|
+
// means all time — this list has no default window.
|
|
15467
|
+
from: external_exports.iso.datetime().optional(),
|
|
15468
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15469
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15470
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15471
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15472
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15473
|
+
includeId: external_exports.string().optional(),
|
|
15434
15474
|
groupBy: external_exports.literal("type").optional(),
|
|
15435
15475
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15436
15476
|
cursor: external_exports.string().optional()
|
|
@@ -15475,15 +15515,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15475
15515
|
detection: FindingDetectionRef,
|
|
15476
15516
|
policy: FindingPolicyRef
|
|
15477
15517
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15518
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15519
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15520
|
+
severity: external_exports.array(Severity).optional(),
|
|
15521
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15522
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15523
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15524
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15525
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15526
|
+
// the grouped query's group-level fold.
|
|
15527
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15528
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15529
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15530
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15531
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15532
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15533
|
+
repo: external_exports.string().optional(),
|
|
15534
|
+
file: external_exports.string().optional(),
|
|
15535
|
+
q: external_exports.string().optional(),
|
|
15536
|
+
sessionId: external_exports.string().optional(),
|
|
15537
|
+
from: external_exports.iso.datetime().optional(),
|
|
15538
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15539
|
+
cursor: external_exports.string().optional()
|
|
15540
|
+
});
|
|
15541
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15542
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15543
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15544
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15545
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15546
|
+
// dimension still excludes its own filter.
|
|
15547
|
+
facets: FindingFacets,
|
|
15548
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15549
|
+
nextCursor: external_exports.string().nullable()
|
|
15550
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15551
|
+
var FindingLocationFile = external_exports.object({
|
|
15552
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15553
|
+
// with no file attribution).
|
|
15554
|
+
file: external_exports.string(),
|
|
15555
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15556
|
+
maxSeverity: Severity,
|
|
15557
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15558
|
+
// Folded from the instances' derived statuses with the same
|
|
15559
|
+
// open-dominates precedence a group uses.
|
|
15560
|
+
status: FindingStatus.optional(),
|
|
15561
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15562
|
+
// chips, and the count is what conveys scale.
|
|
15563
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15564
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15565
|
+
var FindingLocationRepo = external_exports.object({
|
|
15566
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15567
|
+
repo: external_exports.string(),
|
|
15568
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15569
|
+
maxSeverity: Severity,
|
|
15570
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15571
|
+
status: FindingStatus.optional(),
|
|
15572
|
+
files: external_exports.array(FindingLocationFile)
|
|
15573
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15574
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15575
|
+
severity: external_exports.array(Severity).optional(),
|
|
15576
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15577
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15578
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15579
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15580
|
+
// instances that match, and folds its status from those.
|
|
15581
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15582
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15583
|
+
q: external_exports.string().optional(),
|
|
15584
|
+
sessionId: external_exports.string().optional(),
|
|
15585
|
+
from: external_exports.iso.datetime().optional(),
|
|
15586
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15587
|
+
});
|
|
15588
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15589
|
+
totals: external_exports.object({
|
|
15590
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15591
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15592
|
+
files: external_exports.number().int().nonnegative()
|
|
15593
|
+
}),
|
|
15594
|
+
/** Sorted by max severity, then most recent. */
|
|
15595
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15596
|
+
/** Whether `limit` truncated the repo list. */
|
|
15597
|
+
hasMore: external_exports.boolean()
|
|
15598
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15478
15599
|
|
|
15479
15600
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15480
|
-
var Harness = external_exports.enum([
|
|
15601
|
+
var Harness = external_exports.enum([
|
|
15602
|
+
"claudecode",
|
|
15603
|
+
"cursor",
|
|
15604
|
+
"copilot",
|
|
15605
|
+
"codex",
|
|
15606
|
+
"antigravity",
|
|
15607
|
+
"windsurf",
|
|
15608
|
+
"claudedesktop",
|
|
15609
|
+
"chatgpt",
|
|
15610
|
+
"claudeai",
|
|
15611
|
+
"api"
|
|
15612
|
+
]).meta({ id: "Harness" });
|
|
15481
15613
|
var TOOL_TO_HARNESS = {
|
|
15482
15614
|
"claude-code": "claudecode",
|
|
15483
15615
|
"claude-desktop": "claudedesktop",
|
|
15484
15616
|
"github-copilot": "copilot",
|
|
15485
15617
|
cursor: "cursor",
|
|
15486
|
-
chatgpt: "chatgpt"
|
|
15618
|
+
chatgpt: "chatgpt",
|
|
15619
|
+
codex: "codex",
|
|
15620
|
+
antigravity: "antigravity",
|
|
15621
|
+
"claude-ai": "claudeai"
|
|
15487
15622
|
};
|
|
15488
15623
|
|
|
15489
15624
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15941,7 +16076,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15941
16076
|
// ../../packages/schema/src/zod/event.ts
|
|
15942
16077
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15943
16078
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15944
|
-
var SourceTool = external_exports.enum([
|
|
16079
|
+
var SourceTool = external_exports.enum([
|
|
16080
|
+
"claude-code",
|
|
16081
|
+
"claude-desktop",
|
|
16082
|
+
"cursor",
|
|
16083
|
+
"chatgpt",
|
|
16084
|
+
"claude-ai",
|
|
16085
|
+
"github-copilot",
|
|
16086
|
+
"codex",
|
|
16087
|
+
"antigravity",
|
|
16088
|
+
"cli",
|
|
16089
|
+
"unknown"
|
|
16090
|
+
]).meta({ id: "SourceTool" });
|
|
15945
16091
|
var EventMetadata = external_exports.object({
|
|
15946
16092
|
sessionId: external_exports.string().optional(),
|
|
15947
16093
|
repo: external_exports.string().optional(),
|
|
@@ -16012,7 +16158,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16012
16158
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16013
16159
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16014
16160
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16015
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16161
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16016
16162
|
var AccessCounts = external_exports.object({
|
|
16017
16163
|
open: external_exports.number().int().nonnegative(),
|
|
16018
16164
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16281,6 +16427,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16281
16427
|
useCount: true,
|
|
16282
16428
|
conditions: true
|
|
16283
16429
|
});
|
|
16430
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16284
16431
|
|
|
16285
16432
|
// ../../packages/schema/src/zod/rule.ts
|
|
16286
16433
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17091,6 +17238,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17091
17238
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17092
17239
|
}).meta({ id: "EgressWriteSummary" });
|
|
17093
17240
|
|
|
17241
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17242
|
+
var confirmation = external_exports.string().optional();
|
|
17243
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17244
|
+
reference: external_exports.string(),
|
|
17245
|
+
scope: external_exports.string(),
|
|
17246
|
+
reason: external_exports.string(),
|
|
17247
|
+
confirmation
|
|
17248
|
+
});
|
|
17249
|
+
var AddExceptionInput = external_exports.object({
|
|
17250
|
+
ruleId: external_exports.string(),
|
|
17251
|
+
value: external_exports.string(),
|
|
17252
|
+
scope: external_exports.string(),
|
|
17253
|
+
reason: external_exports.string(),
|
|
17254
|
+
confirmation
|
|
17255
|
+
});
|
|
17256
|
+
var GrantRevealInput = external_exports.object({
|
|
17257
|
+
pointer: external_exports.string(),
|
|
17258
|
+
scope: external_exports.string(),
|
|
17259
|
+
justification: external_exports.string(),
|
|
17260
|
+
confirmation
|
|
17261
|
+
});
|
|
17262
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17263
|
+
id: external_exports.string(),
|
|
17264
|
+
reason: external_exports.string()
|
|
17265
|
+
});
|
|
17266
|
+
var RotateKeyInput = external_exports.object({
|
|
17267
|
+
confirmation: external_exports.string()
|
|
17268
|
+
});
|
|
17269
|
+
|
|
17094
17270
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17095
17271
|
function toApiAction(dbVal) {
|
|
17096
17272
|
const map2 = {
|
|
@@ -17146,6 +17322,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17146
17322
|
repo: r.repo,
|
|
17147
17323
|
file: r.file,
|
|
17148
17324
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17325
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17326
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17149
17327
|
action: toApiAction(effectiveDbAction),
|
|
17150
17328
|
detectedAt: r.occurredAt,
|
|
17151
17329
|
confidence: r.confidence,
|
|
@@ -17277,14 +17455,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17277
17455
|
}
|
|
17278
17456
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17279
17457
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17458
|
+
function compareFindingGroupOrder(a, b) {
|
|
17459
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17460
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17461
|
+
const severityDiff = rankA - rankB;
|
|
17462
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17463
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17464
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17465
|
+
return a.id.localeCompare(b.id);
|
|
17466
|
+
}
|
|
17280
17467
|
function sortFindingGroups(groups) {
|
|
17281
|
-
return [...groups].sort(
|
|
17282
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17283
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17284
|
-
const severityDiff = rankA - rankB;
|
|
17285
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17286
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17287
|
-
});
|
|
17468
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17288
17469
|
}
|
|
17289
17470
|
function computeFindingFacets(allGroups, opts) {
|
|
17290
17471
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17340,15 +17521,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17340
17521
|
for (const g of forStatus) {
|
|
17341
17522
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17342
17523
|
}
|
|
17343
|
-
const
|
|
17524
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17525
|
+
return {
|
|
17526
|
+
severity: toItems2(severityMap),
|
|
17527
|
+
provider: toItems2(providerMap),
|
|
17528
|
+
action: toItems2(actionMap),
|
|
17529
|
+
subtype: toItems2(subtypeMap),
|
|
17530
|
+
status: toItems2(statusMap)
|
|
17531
|
+
};
|
|
17532
|
+
}
|
|
17533
|
+
|
|
17534
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17535
|
+
function rowHaystack(row) {
|
|
17536
|
+
return [
|
|
17537
|
+
row.ruleId,
|
|
17538
|
+
row.category,
|
|
17539
|
+
row.maskedMatch,
|
|
17540
|
+
row.repo,
|
|
17541
|
+
row.file,
|
|
17542
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17543
|
+
row.id
|
|
17544
|
+
].join(" ").toLowerCase();
|
|
17545
|
+
}
|
|
17546
|
+
function matchesDimension(row, opts, dimension) {
|
|
17547
|
+
switch (dimension) {
|
|
17548
|
+
case "severity":
|
|
17549
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17550
|
+
case "subtype":
|
|
17551
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17552
|
+
case "providers":
|
|
17553
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17554
|
+
case "actions":
|
|
17555
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17556
|
+
case "statuses":
|
|
17557
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17558
|
+
case "tools":
|
|
17559
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17560
|
+
case "repo":
|
|
17561
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17562
|
+
case "file":
|
|
17563
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17564
|
+
case "q":
|
|
17565
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17566
|
+
}
|
|
17567
|
+
}
|
|
17568
|
+
var DIMENSIONS = [
|
|
17569
|
+
"severity",
|
|
17570
|
+
"subtype",
|
|
17571
|
+
"providers",
|
|
17572
|
+
"actions",
|
|
17573
|
+
"statuses",
|
|
17574
|
+
"tools",
|
|
17575
|
+
"repo",
|
|
17576
|
+
"file",
|
|
17577
|
+
"q"
|
|
17578
|
+
];
|
|
17579
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17580
|
+
for (const dimension of DIMENSIONS) {
|
|
17581
|
+
if (dimension === except) continue;
|
|
17582
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17583
|
+
}
|
|
17584
|
+
return true;
|
|
17585
|
+
}
|
|
17586
|
+
function toItems(counts) {
|
|
17587
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17588
|
+
}
|
|
17589
|
+
function bump(counts, value) {
|
|
17590
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17591
|
+
}
|
|
17592
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17593
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17594
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17595
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17596
|
+
const action = /* @__PURE__ */ new Map();
|
|
17597
|
+
const status = /* @__PURE__ */ new Map();
|
|
17598
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17344
17599
|
return {
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17600
|
+
add(row) {
|
|
17601
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17603
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17604
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17605
|
+
}
|
|
17606
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17607
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17608
|
+
bump(status, row.status);
|
|
17609
|
+
}
|
|
17610
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17611
|
+
bump(tool, row.toolName);
|
|
17612
|
+
}
|
|
17613
|
+
},
|
|
17614
|
+
facets: () => ({
|
|
17615
|
+
severity: toItems(severity),
|
|
17616
|
+
subtype: toItems(subtype),
|
|
17617
|
+
provider: toItems(provider),
|
|
17618
|
+
action: toItems(action),
|
|
17619
|
+
status: toItems(status),
|
|
17620
|
+
tool: toItems(tool)
|
|
17621
|
+
})
|
|
17622
|
+
};
|
|
17623
|
+
}
|
|
17624
|
+
function toInstanceDetail(row) {
|
|
17625
|
+
const category = toApiCategory(row.category);
|
|
17626
|
+
return {
|
|
17627
|
+
id: row.id,
|
|
17628
|
+
provider: toApiProvider(row.sourceTool),
|
|
17629
|
+
repo: row.repo,
|
|
17630
|
+
file: row.file,
|
|
17631
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17632
|
+
eventId: row.eventId,
|
|
17633
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17634
|
+
action: toApiAction(row.actionTaken),
|
|
17635
|
+
detectedAt: row.occurredAt,
|
|
17636
|
+
confidence: row.confidence,
|
|
17637
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17638
|
+
groupId: row.ruleId,
|
|
17639
|
+
category,
|
|
17640
|
+
subtype: row.ruleId,
|
|
17641
|
+
severity: row.severity,
|
|
17642
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17643
|
+
detection: { id: row.ruleId, name: null },
|
|
17644
|
+
policy: { id: `category:${category}`, name: category }
|
|
17645
|
+
};
|
|
17646
|
+
}
|
|
17647
|
+
var SEVERITY_ORDER2 = {
|
|
17648
|
+
critical: 0,
|
|
17649
|
+
high: 1,
|
|
17650
|
+
medium: 2,
|
|
17651
|
+
low: 3
|
|
17652
|
+
};
|
|
17653
|
+
function newLocationAccumulator() {
|
|
17654
|
+
return {
|
|
17655
|
+
instanceCount: 0,
|
|
17656
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17657
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17658
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17659
|
+
maxSeverity: "low",
|
|
17660
|
+
latestDetectedAt: "",
|
|
17661
|
+
statuses: [],
|
|
17662
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17350
17663
|
};
|
|
17351
17664
|
}
|
|
17665
|
+
function addToLocation(acc, row) {
|
|
17666
|
+
acc.instanceCount += 1;
|
|
17667
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17668
|
+
if (rank < acc.maxSeverityRank) {
|
|
17669
|
+
acc.maxSeverityRank = rank;
|
|
17670
|
+
acc.maxSeverity = row.severity;
|
|
17671
|
+
}
|
|
17672
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17673
|
+
acc.statuses.push(row.status);
|
|
17674
|
+
acc.ruleIds.add(row.ruleId);
|
|
17675
|
+
}
|
|
17352
17676
|
|
|
17353
17677
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17354
17678
|
var InstalledPack = external_exports.object({
|
|
@@ -17489,6 +17813,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17489
17813
|
revealGrantId: external_exports.string().nullable(),
|
|
17490
17814
|
sightings: external_exports.array(VaultSighting)
|
|
17491
17815
|
});
|
|
17816
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17817
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17818
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17819
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17820
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17821
|
+
// Opaque; names the last row of the page just served.
|
|
17822
|
+
cursor: external_exports.string().optional()
|
|
17823
|
+
});
|
|
17824
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17825
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17826
|
+
// independent, so paging never changes what the count claims.
|
|
17827
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17828
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17829
|
+
// `null` once the last page is reached.
|
|
17830
|
+
nextCursor: external_exports.string().nullable()
|
|
17831
|
+
});
|
|
17832
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17833
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17834
|
+
cursor: external_exports.string().optional()
|
|
17835
|
+
});
|
|
17836
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17837
|
+
// Reused values across the whole store — the number the section's claim
|
|
17838
|
+
// ("values detected in more than one place") is about.
|
|
17839
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17840
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17841
|
+
nextCursor: external_exports.string().nullable()
|
|
17842
|
+
});
|
|
17843
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17844
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17845
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17846
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17847
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17848
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17849
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17850
|
+
cursor: external_exports.string().optional()
|
|
17851
|
+
});
|
|
17852
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17853
|
+
items: external_exports.array(VaultDeref),
|
|
17854
|
+
nextCursor: external_exports.string().nullable(),
|
|
17855
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17856
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17857
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17858
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17859
|
+
});
|
|
17492
17860
|
var VaultKeyCustody = external_exports.string();
|
|
17493
17861
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17494
17862
|
var VAULT_EVENT_NOTE_MAX_POINTERS = 8;
|
|
@@ -17866,7 +18234,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17866
18234
|
// Omit for both kinds.
|
|
17867
18235
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17868
18236
|
});
|
|
17869
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18237
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17870
18238
|
var ScanCoverageProvider = external_exports.object({
|
|
17871
18239
|
provider: Provider,
|
|
17872
18240
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18119,6 +18487,195 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18119
18487
|
);
|
|
18120
18488
|
}
|
|
18121
18489
|
|
|
18490
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18491
|
+
import { randomUUID } from "crypto";
|
|
18492
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18493
|
+
import { basename, dirname, join } from "path";
|
|
18494
|
+
|
|
18495
|
+
// ../../packages/persistence/src/paths.ts
|
|
18496
|
+
import {
|
|
18497
|
+
chmodSync,
|
|
18498
|
+
linkSync,
|
|
18499
|
+
lstatSync,
|
|
18500
|
+
mkdirSync,
|
|
18501
|
+
renameSync,
|
|
18502
|
+
rmSync,
|
|
18503
|
+
writeFileSync
|
|
18504
|
+
} from "fs";
|
|
18505
|
+
import { threadId } from "worker_threads";
|
|
18506
|
+
var DATA_DIR_MODE = 448;
|
|
18507
|
+
var DATA_FILE_MODE = 384;
|
|
18508
|
+
var DB_FILENAME = "aka.db";
|
|
18509
|
+
function isSymlink(path) {
|
|
18510
|
+
try {
|
|
18511
|
+
return lstatSync(path).isSymbolicLink();
|
|
18512
|
+
} catch {
|
|
18513
|
+
return false;
|
|
18514
|
+
}
|
|
18515
|
+
}
|
|
18516
|
+
function chmodBestEffort(path, mode) {
|
|
18517
|
+
if (isSymlink(path)) return;
|
|
18518
|
+
try {
|
|
18519
|
+
chmodSync(path, mode);
|
|
18520
|
+
} catch {
|
|
18521
|
+
}
|
|
18522
|
+
}
|
|
18523
|
+
function tightenDir(dir) {
|
|
18524
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18525
|
+
}
|
|
18526
|
+
function ensureDataDirSync(dir) {
|
|
18527
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18528
|
+
tightenDir(dir);
|
|
18529
|
+
}
|
|
18530
|
+
function dbSidecars(file2) {
|
|
18531
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18532
|
+
}
|
|
18533
|
+
function tightenFile(file2) {
|
|
18534
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18535
|
+
}
|
|
18536
|
+
function tightenPerms(file2) {
|
|
18537
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18538
|
+
}
|
|
18539
|
+
function classifyOccupant(file2) {
|
|
18540
|
+
try {
|
|
18541
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
18542
|
+
return { kind: "gone" };
|
|
18543
|
+
} catch (err) {
|
|
18544
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
18545
|
+
return { kind: "unknown", cause: err };
|
|
18546
|
+
}
|
|
18547
|
+
}
|
|
18548
|
+
var KeyUnclaimableError = class extends Error {
|
|
18549
|
+
code = "key-unclaimable";
|
|
18550
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
18551
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
18552
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
18553
|
+
// captured and then lost, which is worse than its plain absence.
|
|
18554
|
+
constructor(message, cause) {
|
|
18555
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
18556
|
+
this.name = "KeyUnclaimableError";
|
|
18557
|
+
}
|
|
18558
|
+
};
|
|
18559
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
18560
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
18561
|
+
try {
|
|
18562
|
+
rmSync(tmp, { force: true });
|
|
18563
|
+
} catch {
|
|
18564
|
+
}
|
|
18565
|
+
let created;
|
|
18566
|
+
try {
|
|
18567
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18568
|
+
created = publishByLink(tmp, file2, data);
|
|
18569
|
+
} finally {
|
|
18570
|
+
try {
|
|
18571
|
+
rmSync(tmp, { force: true });
|
|
18572
|
+
} catch {
|
|
18573
|
+
}
|
|
18574
|
+
}
|
|
18575
|
+
if (created) tightenFile(file2);
|
|
18576
|
+
return created;
|
|
18577
|
+
}
|
|
18578
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
18579
|
+
function publishByLink(tmp, file2, data) {
|
|
18580
|
+
try {
|
|
18581
|
+
linkSync(tmp, file2);
|
|
18582
|
+
return true;
|
|
18583
|
+
} catch (err) {
|
|
18584
|
+
const code = err.code;
|
|
18585
|
+
if (code === "EEXIST") return false;
|
|
18586
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18587
|
+
}
|
|
18588
|
+
try {
|
|
18589
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18590
|
+
return true;
|
|
18591
|
+
} catch (err) {
|
|
18592
|
+
if (err.code === "EEXIST") return false;
|
|
18593
|
+
throw err;
|
|
18594
|
+
}
|
|
18595
|
+
}
|
|
18596
|
+
|
|
18597
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18598
|
+
function backupPath(file2, tag) {
|
|
18599
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18600
|
+
}
|
|
18601
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18602
|
+
function reapStalePartials(file2) {
|
|
18603
|
+
const dir = dirname(file2);
|
|
18604
|
+
const prefix = `${basename(file2)}.`;
|
|
18605
|
+
let entries;
|
|
18606
|
+
try {
|
|
18607
|
+
entries = readdirSync(dir);
|
|
18608
|
+
} catch {
|
|
18609
|
+
return;
|
|
18610
|
+
}
|
|
18611
|
+
for (const name of entries) {
|
|
18612
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18613
|
+
const partial2 = join(dir, name);
|
|
18614
|
+
try {
|
|
18615
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18616
|
+
rmSync2(partial2, { force: true });
|
|
18617
|
+
}
|
|
18618
|
+
} catch {
|
|
18619
|
+
}
|
|
18620
|
+
}
|
|
18621
|
+
}
|
|
18622
|
+
function snapshotStore(db, backup) {
|
|
18623
|
+
const partial2 = `${backup}.partial`;
|
|
18624
|
+
try {
|
|
18625
|
+
rmSync2(partial2, { force: true });
|
|
18626
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18627
|
+
tightenFile(partial2);
|
|
18628
|
+
renameSync2(partial2, backup);
|
|
18629
|
+
} catch (error51) {
|
|
18630
|
+
try {
|
|
18631
|
+
rmSync2(partial2, { force: true });
|
|
18632
|
+
} catch {
|
|
18633
|
+
}
|
|
18634
|
+
throw error51;
|
|
18635
|
+
}
|
|
18636
|
+
}
|
|
18637
|
+
function moveStoreAside(file2, backup) {
|
|
18638
|
+
const undo = [];
|
|
18639
|
+
renameSync2(file2, backup);
|
|
18640
|
+
undo.push([backup, file2]);
|
|
18641
|
+
try {
|
|
18642
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18643
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18644
|
+
try {
|
|
18645
|
+
renameSync2(sidecar, moved);
|
|
18646
|
+
undo.push([moved, sidecar]);
|
|
18647
|
+
} catch {
|
|
18648
|
+
rmSync2(sidecar, { force: true });
|
|
18649
|
+
}
|
|
18650
|
+
}
|
|
18651
|
+
} catch (error51) {
|
|
18652
|
+
for (const [from, to] of undo.reverse()) {
|
|
18653
|
+
try {
|
|
18654
|
+
renameSync2(from, to);
|
|
18655
|
+
} catch {
|
|
18656
|
+
}
|
|
18657
|
+
}
|
|
18658
|
+
throw error51;
|
|
18659
|
+
}
|
|
18660
|
+
tightenPerms(backup);
|
|
18661
|
+
}
|
|
18662
|
+
function discardStore(file2, backup) {
|
|
18663
|
+
try {
|
|
18664
|
+
rmSync2(file2, { force: true });
|
|
18665
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18666
|
+
rmSync2(sidecar, { force: true });
|
|
18667
|
+
}
|
|
18668
|
+
} catch (error51) {
|
|
18669
|
+
if (existsSync(file2)) {
|
|
18670
|
+
try {
|
|
18671
|
+
rmSync2(backup, { force: true });
|
|
18672
|
+
} catch {
|
|
18673
|
+
}
|
|
18674
|
+
}
|
|
18675
|
+
throw error51;
|
|
18676
|
+
}
|
|
18677
|
+
}
|
|
18678
|
+
|
|
18122
18679
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18123
18680
|
function escapeLikePattern(s) {
|
|
18124
18681
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18260,55 +18817,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18260
18817
|
return out;
|
|
18261
18818
|
}
|
|
18262
18819
|
|
|
18263
|
-
// ../../packages/persistence/src/paths.ts
|
|
18264
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18265
|
-
var DATA_DIR_MODE = 448;
|
|
18266
|
-
var DATA_FILE_MODE = 384;
|
|
18267
|
-
var DB_FILENAME = "aka.db";
|
|
18268
|
-
function chmodBestEffort(path, mode) {
|
|
18269
|
-
try {
|
|
18270
|
-
chmodSync(path, mode);
|
|
18271
|
-
} catch {
|
|
18272
|
-
}
|
|
18273
|
-
}
|
|
18274
|
-
function tightenDir(dir) {
|
|
18275
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18276
|
-
}
|
|
18277
|
-
function ensureDataDirSync(dir) {
|
|
18278
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18279
|
-
tightenDir(dir);
|
|
18280
|
-
}
|
|
18281
|
-
function dbSidecars(file2) {
|
|
18282
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18283
|
-
}
|
|
18284
|
-
function tightenFile(file2) {
|
|
18285
|
-
try {
|
|
18286
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18287
|
-
} catch {
|
|
18288
|
-
}
|
|
18289
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18290
|
-
}
|
|
18291
|
-
function tightenPerms(file2) {
|
|
18292
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18293
|
-
}
|
|
18294
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18295
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18296
|
-
try {
|
|
18297
|
-
rmSync(tmp, { force: true });
|
|
18298
|
-
} catch {
|
|
18299
|
-
}
|
|
18300
|
-
try {
|
|
18301
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18302
|
-
renameSync(tmp, file2);
|
|
18303
|
-
} finally {
|
|
18304
|
-
try {
|
|
18305
|
-
rmSync(tmp, { force: true });
|
|
18306
|
-
} catch {
|
|
18307
|
-
}
|
|
18308
|
-
}
|
|
18309
|
-
tightenFile(file2);
|
|
18310
|
-
}
|
|
18311
|
-
|
|
18312
18820
|
// ../../packages/persistence/src/migrations.ts
|
|
18313
18821
|
function describeObject(object2) {
|
|
18314
18822
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18424,9 +18932,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18424
18932
|
}
|
|
18425
18933
|
}
|
|
18426
18934
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18935
|
+
reapStalePartials(file2);
|
|
18936
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18937
|
+
snapshotStore(db, backup);
|
|
18430
18938
|
return backup;
|
|
18431
18939
|
}
|
|
18432
18940
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18770,6 +19278,25 @@ function parseJsonObject(s) {
|
|
|
18770
19278
|
return void 0;
|
|
18771
19279
|
}
|
|
18772
19280
|
|
|
19281
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19282
|
+
function encodeKeysetCursor(payload) {
|
|
19283
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19284
|
+
}
|
|
19285
|
+
function decodeKeysetCursor(cursor) {
|
|
19286
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19287
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19288
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19289
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19290
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19291
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19292
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19293
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19294
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19295
|
+
return parsed;
|
|
19296
|
+
}
|
|
19297
|
+
return null;
|
|
19298
|
+
}
|
|
19299
|
+
|
|
18773
19300
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18774
19301
|
var DAY_MS = 864e5;
|
|
18775
19302
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18815,16 +19342,6 @@ function utcWindow(nowMs) {
|
|
|
18815
19342
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18816
19343
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18817
19344
|
}
|
|
18818
|
-
function encodeCursor(payload) {
|
|
18819
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18820
|
-
}
|
|
18821
|
-
function decodeCursor(cursor) {
|
|
18822
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18823
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18824
|
-
return parsed;
|
|
18825
|
-
}
|
|
18826
|
-
return null;
|
|
18827
|
-
}
|
|
18828
19345
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18829
19346
|
session: "session",
|
|
18830
19347
|
prompt: "prompt",
|
|
@@ -18969,7 +19486,7 @@ var SqliteActivityRepository = class {
|
|
|
18969
19486
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18970
19487
|
}
|
|
18971
19488
|
listSessions(query) {
|
|
18972
|
-
const cursor = query.cursor ?
|
|
19489
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18973
19490
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18974
19491
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18975
19492
|
const conditions = [SESSION_ROOT];
|
|
@@ -19043,7 +19560,7 @@ var SqliteActivityRepository = class {
|
|
|
19043
19560
|
)
|
|
19044
19561
|
);
|
|
19045
19562
|
const last = page[page.length - 1];
|
|
19046
|
-
const nextCursor = hasMore && last ?
|
|
19563
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19047
19564
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19048
19565
|
}
|
|
19049
19566
|
getSession(sessionId) {
|
|
@@ -19916,7 +20433,7 @@ var SqliteEventsRepository = class {
|
|
|
19916
20433
|
};
|
|
19917
20434
|
|
|
19918
20435
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19919
|
-
import { randomUUID } from "crypto";
|
|
20436
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19920
20437
|
|
|
19921
20438
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19922
20439
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19952,8 +20469,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19952
20469
|
AND conditions IS NULL
|
|
19953
20470
|
AND ${ACTIVE_PREDICATE}`;
|
|
19954
20471
|
var SqliteExceptionsRepository = class {
|
|
19955
|
-
constructor(db) {
|
|
20472
|
+
constructor(db, now = () => Date.now()) {
|
|
19956
20473
|
this.db = db;
|
|
20474
|
+
this.now = now;
|
|
19957
20475
|
this.consumeStmt = db.prepare(
|
|
19958
20476
|
`UPDATE exceptions
|
|
19959
20477
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19971,6 +20489,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19971
20489
|
);
|
|
19972
20490
|
}
|
|
19973
20491
|
db;
|
|
20492
|
+
now;
|
|
19974
20493
|
consumeStmt;
|
|
19975
20494
|
insertBlockedStmt;
|
|
19976
20495
|
sweepBlockedStmt;
|
|
@@ -19997,8 +20516,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19997
20516
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19998
20517
|
);
|
|
19999
20518
|
}
|
|
20000
|
-
const id =
|
|
20001
|
-
const now =
|
|
20519
|
+
const id = randomUUID2();
|
|
20520
|
+
const now = this.now();
|
|
20002
20521
|
try {
|
|
20003
20522
|
this.insertExceptionRow(id, input, now);
|
|
20004
20523
|
} catch (err) {
|
|
@@ -20076,7 +20595,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20076
20595
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20077
20596
|
const rows = allRows(
|
|
20078
20597
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20079
|
-
opts?.includeTerminal ? {} : { now:
|
|
20598
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20080
20599
|
);
|
|
20081
20600
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20082
20601
|
return Promise.resolve(exceptions);
|
|
@@ -20111,7 +20630,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20111
20630
|
* already revoked.
|
|
20112
20631
|
*/
|
|
20113
20632
|
revoke(id, revokedBy, reason) {
|
|
20114
|
-
const now =
|
|
20633
|
+
const now = this.now();
|
|
20115
20634
|
const result = this.db.prepare(
|
|
20116
20635
|
`UPDATE exceptions
|
|
20117
20636
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20125,7 +20644,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20125
20644
|
* callers must treat identically — means it does not and the detection is
|
|
20126
20645
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20127
20646
|
*/
|
|
20128
|
-
consume(id, now =
|
|
20647
|
+
consume(id, now = this.now()) {
|
|
20129
20648
|
const result = this.consumeStmt.run({ id, now });
|
|
20130
20649
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20131
20650
|
}
|
|
@@ -20134,7 +20653,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20134
20653
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20135
20654
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20136
20655
|
*/
|
|
20137
|
-
activeBundleEntries(keyVersion, now =
|
|
20656
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20138
20657
|
const rows = allRows(
|
|
20139
20658
|
this.db.prepare(
|
|
20140
20659
|
`SELECT * FROM exceptions
|
|
@@ -20166,7 +20685,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20166
20685
|
* than the retention window on every write, so the ledger self-limits.
|
|
20167
20686
|
*/
|
|
20168
20687
|
recordBlocked(entry) {
|
|
20169
|
-
const now =
|
|
20688
|
+
const now = this.now();
|
|
20170
20689
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20171
20690
|
this.insertBlockedStmt.run({
|
|
20172
20691
|
reference: entry.reference,
|
|
@@ -20189,7 +20708,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20189
20708
|
WHERE blocked_at > :cutoff
|
|
20190
20709
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20191
20710
|
),
|
|
20192
|
-
{ cutoff:
|
|
20711
|
+
{ cutoff: this.now() - windowMs }
|
|
20193
20712
|
);
|
|
20194
20713
|
return Promise.resolve(
|
|
20195
20714
|
rows.map((row) => ({
|
|
@@ -20217,8 +20736,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20217
20736
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20218
20737
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20219
20738
|
*/
|
|
20220
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20739
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20221
20740
|
try {
|
|
20741
|
+
const at = now ?? this.now();
|
|
20222
20742
|
const row = getRow(
|
|
20223
20743
|
this.db.prepare(
|
|
20224
20744
|
`SELECT id FROM exceptions
|
|
@@ -20227,7 +20747,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20227
20747
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20228
20748
|
LIMIT 1`
|
|
20229
20749
|
),
|
|
20230
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20750
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20231
20751
|
);
|
|
20232
20752
|
return Promise.resolve(row ?? null);
|
|
20233
20753
|
} catch (err) {
|
|
@@ -20241,7 +20761,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20241
20761
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20242
20762
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20243
20763
|
*/
|
|
20244
|
-
sweepTerminal(retentionMs, now =
|
|
20764
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20245
20765
|
const result = this.db.prepare(
|
|
20246
20766
|
`DELETE FROM exceptions
|
|
20247
20767
|
WHERE updated_at < :cutoff
|
|
@@ -20304,6 +20824,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20304
20824
|
|
|
20305
20825
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20306
20826
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20827
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20828
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20829
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20830
|
+
function compareLocationOrder(a, b) {
|
|
20831
|
+
return compareFindingGroupOrder(
|
|
20832
|
+
{
|
|
20833
|
+
severity: a.maxSeverity,
|
|
20834
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20835
|
+
id: ""
|
|
20836
|
+
},
|
|
20837
|
+
{
|
|
20838
|
+
severity: b.maxSeverity,
|
|
20839
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20840
|
+
id: ""
|
|
20841
|
+
}
|
|
20842
|
+
);
|
|
20843
|
+
}
|
|
20307
20844
|
var CONCAT_SEP = ",";
|
|
20308
20845
|
var TUPLE_SEP = "|";
|
|
20309
20846
|
function splitConcat(value) {
|
|
@@ -20316,6 +20853,33 @@ function deriveInstanceStatus(row) {
|
|
|
20316
20853
|
latestResolutionStatus: row.latest_status
|
|
20317
20854
|
});
|
|
20318
20855
|
}
|
|
20856
|
+
function encodeGroupCursor(group) {
|
|
20857
|
+
const payload = {
|
|
20858
|
+
sev: group.severity,
|
|
20859
|
+
t: group.latestDetectedAt,
|
|
20860
|
+
id: group.id
|
|
20861
|
+
};
|
|
20862
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20863
|
+
}
|
|
20864
|
+
function decodeGroupCursor(cursor) {
|
|
20865
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20866
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20867
|
+
return {
|
|
20868
|
+
severity: parsed.sev,
|
|
20869
|
+
latestDetectedAt: parsed.t,
|
|
20870
|
+
id: parsed.id
|
|
20871
|
+
};
|
|
20872
|
+
}
|
|
20873
|
+
return null;
|
|
20874
|
+
}
|
|
20875
|
+
function firstAfter(sorted, cursor) {
|
|
20876
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20877
|
+
return index === -1 ? sorted.length : index;
|
|
20878
|
+
}
|
|
20879
|
+
function findDeepLinked(sorted, page, id) {
|
|
20880
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20881
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20882
|
+
}
|
|
20319
20883
|
var DAY_MS3 = 864e5;
|
|
20320
20884
|
var SqliteFindingsRepository = class {
|
|
20321
20885
|
constructor(db) {
|
|
@@ -20425,8 +20989,13 @@ var SqliteFindingsRepository = class {
|
|
|
20425
20989
|
*/
|
|
20426
20990
|
listGroupedFindings(query) {
|
|
20427
20991
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20428
|
-
const
|
|
20429
|
-
const
|
|
20992
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20993
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20994
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20995
|
+
const sessionParams = {
|
|
20996
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20997
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20998
|
+
};
|
|
20430
20999
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20431
21000
|
predicate,
|
|
20432
21001
|
params: sessionParams
|
|
@@ -20434,7 +21003,8 @@ var SqliteFindingsRepository = class {
|
|
|
20434
21003
|
const rows = allRows(
|
|
20435
21004
|
this.db.prepare(
|
|
20436
21005
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20437
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
21006
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
21007
|
+
kind, finding_key, latest_status
|
|
20438
21008
|
FROM (
|
|
20439
21009
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20440
21010
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20444,6 +21014,7 @@ var SqliteFindingsRepository = class {
|
|
|
20444
21014
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20445
21015
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20446
21016
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21017
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20447
21018
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20448
21019
|
latest.status AS latest_status,
|
|
20449
21020
|
ROW_NUMBER() OVER (
|
|
@@ -20475,6 +21046,8 @@ var SqliteFindingsRepository = class {
|
|
|
20475
21046
|
repo: r.repo ?? "",
|
|
20476
21047
|
file: r.file ?? "",
|
|
20477
21048
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21049
|
+
eventId: r.event_id,
|
|
21050
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20478
21051
|
status: deriveInstanceStatus(r)
|
|
20479
21052
|
}));
|
|
20480
21053
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20498,18 +21071,23 @@ var SqliteFindingsRepository = class {
|
|
|
20498
21071
|
groups: sorted.length
|
|
20499
21072
|
};
|
|
20500
21073
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21074
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21075
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21076
|
+
const page = sorted.slice(start, start + limit);
|
|
21077
|
+
const lastOnPage = page.at(-1);
|
|
21078
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21079
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20501
21080
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20502
|
-
const
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
);
|
|
21081
|
+
const narrow = (g) => statusSet ? {
|
|
21082
|
+
...g,
|
|
21083
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21084
|
+
} : g;
|
|
21085
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20508
21086
|
return Promise.resolve({
|
|
20509
21087
|
totals,
|
|
20510
21088
|
facets,
|
|
20511
21089
|
items,
|
|
20512
|
-
nextCursor
|
|
21090
|
+
nextCursor,
|
|
20513
21091
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20514
21092
|
});
|
|
20515
21093
|
}
|
|
@@ -20541,6 +21119,266 @@ var SqliteFindingsRepository = class {
|
|
|
20541
21119
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20542
21120
|
* path repeating across tuples.)
|
|
20543
21121
|
*/
|
|
21122
|
+
/**
|
|
21123
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21124
|
+
* paged by keyset.
|
|
21125
|
+
*
|
|
21126
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21127
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21128
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21129
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21130
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21131
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21132
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21133
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21134
|
+
* none of the mappers names", which no IN-list can say.
|
|
21135
|
+
*
|
|
21136
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21137
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21138
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21139
|
+
* while the counting runs, and only the page itself is retained.
|
|
21140
|
+
*/
|
|
21141
|
+
listFindingInstances(query) {
|
|
21142
|
+
const opts = {
|
|
21143
|
+
severity: query.severity,
|
|
21144
|
+
subtype: query.subtype,
|
|
21145
|
+
providers: query.provider,
|
|
21146
|
+
actions: query.action,
|
|
21147
|
+
statuses: query.status,
|
|
21148
|
+
tools: query.tool,
|
|
21149
|
+
repo: query.repo,
|
|
21150
|
+
file: query.file,
|
|
21151
|
+
q: query.q
|
|
21152
|
+
};
|
|
21153
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21154
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21155
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21156
|
+
const items = [];
|
|
21157
|
+
let total = 0;
|
|
21158
|
+
let last;
|
|
21159
|
+
let hasMore = false;
|
|
21160
|
+
for (const row of this.scanFindingRows({
|
|
21161
|
+
sessionId: query.sessionId,
|
|
21162
|
+
from: query.from
|
|
21163
|
+
})) {
|
|
21164
|
+
accumulator.add(row);
|
|
21165
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21166
|
+
total += 1;
|
|
21167
|
+
if (items.length < limit) {
|
|
21168
|
+
items.push(toInstanceDetail(row));
|
|
21169
|
+
last = row;
|
|
21170
|
+
} else {
|
|
21171
|
+
hasMore = true;
|
|
21172
|
+
}
|
|
21173
|
+
}
|
|
21174
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21175
|
+
if (cursor !== null) {
|
|
21176
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21177
|
+
return Promise.resolve({
|
|
21178
|
+
totals: { findings: total },
|
|
21179
|
+
facets: accumulator.facets(),
|
|
21180
|
+
items: resumed.items,
|
|
21181
|
+
nextCursor: resumed.nextCursor
|
|
21182
|
+
});
|
|
21183
|
+
}
|
|
21184
|
+
return Promise.resolve({
|
|
21185
|
+
totals: { findings: total },
|
|
21186
|
+
facets: accumulator.facets(),
|
|
21187
|
+
items,
|
|
21188
|
+
nextCursor
|
|
21189
|
+
});
|
|
21190
|
+
}
|
|
21191
|
+
/**
|
|
21192
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21193
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21194
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21195
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21196
|
+
*/
|
|
21197
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21198
|
+
const items = [];
|
|
21199
|
+
let last;
|
|
21200
|
+
let hasMore = false;
|
|
21201
|
+
for (const row of this.scanFindingRows({
|
|
21202
|
+
sessionId: query.sessionId,
|
|
21203
|
+
from: query.from,
|
|
21204
|
+
after: cursor
|
|
21205
|
+
})) {
|
|
21206
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21207
|
+
if (items.length < limit) {
|
|
21208
|
+
items.push(toInstanceDetail(row));
|
|
21209
|
+
last = row;
|
|
21210
|
+
} else {
|
|
21211
|
+
hasMore = true;
|
|
21212
|
+
break;
|
|
21213
|
+
}
|
|
21214
|
+
}
|
|
21215
|
+
return {
|
|
21216
|
+
items,
|
|
21217
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21218
|
+
};
|
|
21219
|
+
}
|
|
21220
|
+
/**
|
|
21221
|
+
* The same findings folded by location: repository, then file within it.
|
|
21222
|
+
*
|
|
21223
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21224
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21225
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21226
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21227
|
+
* filter can name it.
|
|
21228
|
+
*/
|
|
21229
|
+
listFindingLocations(query) {
|
|
21230
|
+
const opts = {
|
|
21231
|
+
severity: query.severity,
|
|
21232
|
+
subtype: query.subtype,
|
|
21233
|
+
providers: query.provider,
|
|
21234
|
+
actions: query.action,
|
|
21235
|
+
statuses: query.status,
|
|
21236
|
+
tools: query.tool,
|
|
21237
|
+
q: query.q
|
|
21238
|
+
};
|
|
21239
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21240
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21241
|
+
let total = 0;
|
|
21242
|
+
for (const row of this.scanFindingRows({
|
|
21243
|
+
sessionId: query.sessionId,
|
|
21244
|
+
from: query.from
|
|
21245
|
+
})) {
|
|
21246
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21247
|
+
total += 1;
|
|
21248
|
+
let files = byRepo.get(row.repo);
|
|
21249
|
+
if (files === void 0) {
|
|
21250
|
+
files = /* @__PURE__ */ new Map();
|
|
21251
|
+
byRepo.set(row.repo, files);
|
|
21252
|
+
}
|
|
21253
|
+
let acc = files.get(row.file);
|
|
21254
|
+
if (acc === void 0) {
|
|
21255
|
+
acc = newLocationAccumulator();
|
|
21256
|
+
files.set(row.file, acc);
|
|
21257
|
+
}
|
|
21258
|
+
addToLocation(acc, row);
|
|
21259
|
+
}
|
|
21260
|
+
let fileCount = 0;
|
|
21261
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21262
|
+
fileCount += files.size;
|
|
21263
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21264
|
+
file: file2,
|
|
21265
|
+
instanceCount: acc.instanceCount,
|
|
21266
|
+
maxSeverity: acc.maxSeverity,
|
|
21267
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21268
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21269
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21270
|
+
})).sort(compareLocationOrder);
|
|
21271
|
+
const rollup = fileRows.reduce(
|
|
21272
|
+
(a, f) => ({
|
|
21273
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21274
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21275
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21276
|
+
}),
|
|
21277
|
+
{
|
|
21278
|
+
instanceCount: 0,
|
|
21279
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21280
|
+
latestDetectedAt: ""
|
|
21281
|
+
}
|
|
21282
|
+
);
|
|
21283
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21284
|
+
const folded = foldGroupStatus(statuses);
|
|
21285
|
+
return {
|
|
21286
|
+
repo,
|
|
21287
|
+
instanceCount: rollup.instanceCount,
|
|
21288
|
+
maxSeverity: rollup.maxSeverity,
|
|
21289
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21290
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21291
|
+
files: fileRows
|
|
21292
|
+
};
|
|
21293
|
+
});
|
|
21294
|
+
repos.sort(compareLocationOrder);
|
|
21295
|
+
return Promise.resolve({
|
|
21296
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21297
|
+
items: repos.slice(0, limit),
|
|
21298
|
+
hasMore: repos.length > limit
|
|
21299
|
+
});
|
|
21300
|
+
}
|
|
21301
|
+
/**
|
|
21302
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21303
|
+
*
|
|
21304
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21305
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21306
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21307
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21308
|
+
* rather than one unbounded result set.
|
|
21309
|
+
*
|
|
21310
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21311
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21312
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21313
|
+
* a window over the whole resolution table once per batch.
|
|
21314
|
+
*
|
|
21315
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21316
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21317
|
+
* dimension — see listFindingInstances.
|
|
21318
|
+
*/
|
|
21319
|
+
*scanFindingRows(scope) {
|
|
21320
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21321
|
+
const params = [];
|
|
21322
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21323
|
+
conditions.push("e.root_session_id = ?");
|
|
21324
|
+
params.push(scope.sessionId);
|
|
21325
|
+
}
|
|
21326
|
+
if (scope.from !== void 0) {
|
|
21327
|
+
conditions.push("e.started_at >= ?");
|
|
21328
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21329
|
+
}
|
|
21330
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21331
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21332
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21333
|
+
e.started_at AS occurred_at,
|
|
21334
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21335
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21336
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21337
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21338
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21339
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21340
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21341
|
+
FROM inspection_findings f
|
|
21342
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21343
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21344
|
+
WHERE ${conditions.join(" AND ")}
|
|
21345
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21346
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21347
|
+
LIMIT ?`;
|
|
21348
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21349
|
+
for (; ; ) {
|
|
21350
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21351
|
+
...params,
|
|
21352
|
+
after.startedAtMs,
|
|
21353
|
+
after.startedAtMs,
|
|
21354
|
+
after.id,
|
|
21355
|
+
SCAN_BATCH_ROWS
|
|
21356
|
+
]);
|
|
21357
|
+
for (const r of rows) {
|
|
21358
|
+
yield {
|
|
21359
|
+
id: r.id,
|
|
21360
|
+
ruleId: r.rule_id,
|
|
21361
|
+
category: r.category,
|
|
21362
|
+
severity: r.severity,
|
|
21363
|
+
maskedMatch: r.masked_match,
|
|
21364
|
+
actionTaken: r.action_taken,
|
|
21365
|
+
confidence: r.confidence,
|
|
21366
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21367
|
+
sourceTool: r.source_tool,
|
|
21368
|
+
repo: r.repo ?? "",
|
|
21369
|
+
file: r.file ?? "",
|
|
21370
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21371
|
+
eventId: r.event_id,
|
|
21372
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21373
|
+
status: deriveInstanceStatus(r)
|
|
21374
|
+
};
|
|
21375
|
+
}
|
|
21376
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21377
|
+
const lastRow = rows[rows.length - 1];
|
|
21378
|
+
if (lastRow === void 0) return;
|
|
21379
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21380
|
+
}
|
|
21381
|
+
}
|
|
20544
21382
|
groupAggregates(withSearchText, scope) {
|
|
20545
21383
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20546
21384
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20801,7 +21639,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20801
21639
|
};
|
|
20802
21640
|
|
|
20803
21641
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20804
|
-
import { createHash as createHash2, randomUUID as
|
|
21642
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20805
21643
|
|
|
20806
21644
|
// ../../packages/persistence/src/semver.ts
|
|
20807
21645
|
function parse3(version2) {
|
|
@@ -20952,7 +21790,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20952
21790
|
let behind = false;
|
|
20953
21791
|
for (const row of rows) {
|
|
20954
21792
|
const params = {
|
|
20955
|
-
id:
|
|
21793
|
+
id: randomUUID3(),
|
|
20956
21794
|
namespace: row.namespace,
|
|
20957
21795
|
packId: row.packId,
|
|
20958
21796
|
version: row.version,
|
|
@@ -20964,7 +21802,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20964
21802
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20965
21803
|
this.upsertAvailableStmt.run({
|
|
20966
21804
|
...params,
|
|
20967
|
-
id:
|
|
21805
|
+
id: randomUUID3(),
|
|
20968
21806
|
recordedBy: meta3?.recordedBy ?? null
|
|
20969
21807
|
});
|
|
20970
21808
|
} else {
|
|
@@ -21287,14 +22125,15 @@ var SqliteInventoryRepository = class {
|
|
|
21287
22125
|
};
|
|
21288
22126
|
|
|
21289
22127
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21290
|
-
import { randomUUID as
|
|
22128
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21291
22129
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21292
22130
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21293
22131
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21294
22132
|
var HARNESS_LABELS = {
|
|
21295
22133
|
claudecode: "Claude Code",
|
|
21296
22134
|
cursor: "Cursor",
|
|
21297
|
-
codex: "Codex"
|
|
22135
|
+
codex: "Codex",
|
|
22136
|
+
antigravity: "Antigravity"
|
|
21298
22137
|
};
|
|
21299
22138
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21300
22139
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21309,6 +22148,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21309
22148
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21310
22149
|
if (t.includes("cursor")) return "cursor";
|
|
21311
22150
|
if (t.includes("codex")) return "codex";
|
|
22151
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21312
22152
|
return null;
|
|
21313
22153
|
}
|
|
21314
22154
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21767,7 +22607,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21767
22607
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21768
22608
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21769
22609
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21770
|
-
).run({ id:
|
|
22610
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21771
22611
|
}
|
|
21772
22612
|
return true;
|
|
21773
22613
|
}
|
|
@@ -21788,7 +22628,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21788
22628
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21789
22629
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21790
22630
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21791
|
-
).run({ id:
|
|
22631
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21792
22632
|
}
|
|
21793
22633
|
this.configRowsCache = void 0;
|
|
21794
22634
|
return "ok";
|
|
@@ -22085,7 +22925,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22085
22925
|
};
|
|
22086
22926
|
|
|
22087
22927
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22088
|
-
import { randomUUID as
|
|
22928
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22089
22929
|
var SqlitePoliciesRepository = class {
|
|
22090
22930
|
constructor(db) {
|
|
22091
22931
|
this.db = db;
|
|
@@ -22120,7 +22960,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22120
22960
|
failOpenTransaction(this.db, () => {
|
|
22121
22961
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22122
22962
|
stmt.run({
|
|
22123
|
-
id:
|
|
22963
|
+
id: randomUUID5(),
|
|
22124
22964
|
target: JSON.stringify({ category }),
|
|
22125
22965
|
action,
|
|
22126
22966
|
now: Date.now()
|
|
@@ -22140,7 +22980,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22140
22980
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22141
22981
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22142
22982
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22143
|
-
).run({ id:
|
|
22983
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22144
22984
|
}
|
|
22145
22985
|
// Caps every global per-category policy currently set to block/redact down
|
|
22146
22986
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22208,7 +23048,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22208
23048
|
};
|
|
22209
23049
|
|
|
22210
23050
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22211
|
-
import { randomUUID as
|
|
23051
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22212
23052
|
var SqliteProjectFilesRepository = class {
|
|
22213
23053
|
constructor(db) {
|
|
22214
23054
|
this.db = db;
|
|
@@ -22240,7 +23080,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22240
23080
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22241
23081
|
for (const file2 of scan2.files) {
|
|
22242
23082
|
this.upsertStmt.run({
|
|
22243
|
-
id:
|
|
23083
|
+
id: randomUUID6(),
|
|
22244
23084
|
projectId,
|
|
22245
23085
|
path: file2.path,
|
|
22246
23086
|
name: file2.name,
|
|
@@ -22254,7 +23094,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22254
23094
|
};
|
|
22255
23095
|
|
|
22256
23096
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22257
|
-
import { randomUUID as
|
|
23097
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22258
23098
|
var SqliteResolutionsRepository = class {
|
|
22259
23099
|
constructor(db, now = () => Date.now()) {
|
|
22260
23100
|
this.db = db;
|
|
@@ -22308,7 +23148,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22308
23148
|
*/
|
|
22309
23149
|
insertResolution(r) {
|
|
22310
23150
|
this.insertStmt.run({
|
|
22311
|
-
id:
|
|
23151
|
+
id: randomUUID7(),
|
|
22312
23152
|
findingKey: r.findingKey,
|
|
22313
23153
|
status: FindingStatus.parse(r.status),
|
|
22314
23154
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22367,13 +23207,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22367
23207
|
this.readStmt = db.prepare(
|
|
22368
23208
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22369
23209
|
);
|
|
23210
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23211
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23212
|
+
);
|
|
23213
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23214
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23215
|
+
);
|
|
22370
23216
|
}
|
|
22371
23217
|
db;
|
|
22372
23218
|
upsertStmt;
|
|
22373
23219
|
readStmt;
|
|
23220
|
+
countQuarantinedStmt;
|
|
23221
|
+
clearQuarantinedStmt;
|
|
22374
23222
|
getVerdict(ruleKey) {
|
|
22375
23223
|
return getRow(this.readStmt, { ruleKey });
|
|
22376
23224
|
}
|
|
23225
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23226
|
+
countQuarantined() {
|
|
23227
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23228
|
+
}
|
|
23229
|
+
/**
|
|
23230
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23231
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23232
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23233
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23234
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23235
|
+
*
|
|
23236
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23237
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23238
|
+
*
|
|
23239
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23240
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23241
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23242
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23243
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23244
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23245
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23246
|
+
* to restore are silently still disabled.
|
|
23247
|
+
*/
|
|
23248
|
+
clearQuarantined() {
|
|
23249
|
+
const before = this.countQuarantined();
|
|
23250
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23251
|
+
this.clearQuarantinedStmt.run();
|
|
23252
|
+
});
|
|
23253
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23254
|
+
}
|
|
22377
23255
|
setVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
22378
23256
|
failOpenTransaction(this.db, () => {
|
|
22379
23257
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs: worstProbeMs2, checkedAt: Date.now() });
|
|
@@ -22428,7 +23306,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22428
23306
|
};
|
|
22429
23307
|
|
|
22430
23308
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22431
|
-
import { randomUUID as
|
|
23309
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23310
|
+
function pageLimit(requested, fallback) {
|
|
23311
|
+
if (requested === void 0) return fallback;
|
|
23312
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23313
|
+
}
|
|
23314
|
+
function encodeReuseCursor(payload) {
|
|
23315
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23316
|
+
}
|
|
23317
|
+
function decodeReuseCursor(cursor) {
|
|
23318
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23319
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23320
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23321
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23322
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23323
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23324
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23325
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23326
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23327
|
+
}
|
|
23328
|
+
return null;
|
|
23329
|
+
}
|
|
23330
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23331
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23332
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23333
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23334
|
+
function toSighting(row) {
|
|
23335
|
+
return {
|
|
23336
|
+
location: row.location,
|
|
23337
|
+
kind: row.kind,
|
|
23338
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23339
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23340
|
+
};
|
|
23341
|
+
}
|
|
22432
23342
|
var SELECT_COLUMNS = `
|
|
22433
23343
|
pointer_id AS pointerId,
|
|
22434
23344
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22612,39 +23522,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22612
23522
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22613
23523
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22614
23524
|
).run({
|
|
22615
|
-
id:
|
|
23525
|
+
id: randomUUID8(),
|
|
22616
23526
|
pointerId: entry.pointerId,
|
|
22617
23527
|
location: entry.location,
|
|
22618
23528
|
kind: entry.kind,
|
|
22619
23529
|
now
|
|
22620
23530
|
});
|
|
22621
23531
|
}
|
|
22622
|
-
|
|
23532
|
+
/**
|
|
23533
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23534
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23535
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23536
|
+
*
|
|
23537
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23538
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23539
|
+
*/
|
|
23540
|
+
sightingsFor(pointerIds) {
|
|
23541
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23542
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22623
23543
|
const rows = allRows(
|
|
22624
23544
|
this.db.prepare(
|
|
22625
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22626
|
-
|
|
23545
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23546
|
+
FROM secret_vault_sighting
|
|
23547
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23548
|
+
ORDER BY last_seen DESC`
|
|
22627
23549
|
),
|
|
22628
|
-
|
|
23550
|
+
pointerIds
|
|
22629
23551
|
);
|
|
23552
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23553
|
+
return byPointer;
|
|
23554
|
+
}
|
|
23555
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23556
|
+
toInventoryEntries(rows) {
|
|
23557
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22630
23558
|
return rows.map((r) => ({
|
|
22631
|
-
|
|
22632
|
-
|
|
23559
|
+
pointerId: r.pointer_id,
|
|
23560
|
+
category: r.category,
|
|
23561
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23562
|
+
maskedMatch: r.masked_match,
|
|
23563
|
+
occurrences: r.occurrence_count,
|
|
22633
23564
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22634
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23565
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23566
|
+
revealGrantId: r.grant_id,
|
|
23567
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22635
23568
|
}));
|
|
22636
23569
|
}
|
|
22637
23570
|
/**
|
|
22638
|
-
* The dashboard inventory
|
|
22639
|
-
* its sightings and the active
|
|
22640
|
-
* Raw-free by construction — neither
|
|
22641
|
-
* columns are selected.
|
|
23571
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23572
|
+
* value's descriptor data joined with its sightings and the active
|
|
23573
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23574
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23575
|
+
*
|
|
23576
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23577
|
+
* sees never depends on how far they have paged.
|
|
22642
23578
|
*/
|
|
22643
|
-
listInventory(now = Date.now()) {
|
|
23579
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23580
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23581
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23582
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22644
23583
|
const rows = allRows(
|
|
22645
23584
|
this.db.prepare(
|
|
22646
|
-
`SELECT
|
|
22647
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23585
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22648
23586
|
(SELECT e.id FROM exceptions e
|
|
22649
23587
|
WHERE e.rule_id = v.rule_id
|
|
22650
23588
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22652,45 +23590,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22652
23590
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22653
23591
|
LIMIT 1) AS grant_id
|
|
22654
23592
|
FROM secret_vault v
|
|
22655
|
-
|
|
23593
|
+
${where}
|
|
23594
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23595
|
+
LIMIT :limit`
|
|
22656
23596
|
),
|
|
22657
|
-
{
|
|
23597
|
+
bindParams({
|
|
23598
|
+
now,
|
|
23599
|
+
limit: limit + 1,
|
|
23600
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23601
|
+
})
|
|
22658
23602
|
);
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
23603
|
+
const hasMore = rows.length > limit;
|
|
23604
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23605
|
+
const last = page[page.length - 1];
|
|
23606
|
+
return {
|
|
23607
|
+
totals: { values: this.countEntries() },
|
|
23608
|
+
items: this.toInventoryEntries(page),
|
|
23609
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23610
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23611
|
+
};
|
|
23612
|
+
}
|
|
23613
|
+
/**
|
|
23614
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23615
|
+
* than one location — most-reused first, one page at a time.
|
|
23616
|
+
*
|
|
23617
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23618
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23619
|
+
* under-report exactly the values a reader most needs to see.
|
|
23620
|
+
*/
|
|
23621
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23622
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23623
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23624
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23625
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23626
|
+
const rows = allRows(
|
|
23627
|
+
this.db.prepare(
|
|
23628
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23629
|
+
(SELECT e.id FROM exceptions e
|
|
23630
|
+
WHERE e.rule_id = v.rule_id
|
|
23631
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23632
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23633
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23634
|
+
LIMIT 1) AS grant_id
|
|
23635
|
+
FROM secret_vault v
|
|
23636
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23637
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23638
|
+
LIMIT :limit`
|
|
23639
|
+
),
|
|
23640
|
+
bindParams({
|
|
23641
|
+
now,
|
|
23642
|
+
limit: limit + 1,
|
|
23643
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23644
|
+
})
|
|
23645
|
+
);
|
|
23646
|
+
const hasMore = rows.length > limit;
|
|
23647
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23648
|
+
const last = page[page.length - 1];
|
|
23649
|
+
return {
|
|
23650
|
+
totals: { reused: this.countReused() },
|
|
23651
|
+
items: this.toInventoryEntries(page),
|
|
23652
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23653
|
+
};
|
|
22670
23654
|
}
|
|
22671
23655
|
/**
|
|
22672
|
-
* The de-reference trail, newest first. By default the
|
|
22673
|
-
* reasons (display, view-render) are hidden and counted
|
|
22674
|
-
* that matter as a signal are the model crossings, and
|
|
22675
|
-
* render noise would defeat the audit's purpose.
|
|
23656
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23657
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23658
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23659
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23660
|
+
*
|
|
23661
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23662
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23663
|
+
* the reader pages.
|
|
22676
23664
|
*/
|
|
22677
|
-
listDerefs(
|
|
22678
|
-
const limit =
|
|
22679
|
-
const
|
|
23665
|
+
listDerefs(query = {}) {
|
|
23666
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23667
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23668
|
+
const conditions = [];
|
|
23669
|
+
if (query.includeBatched !== true) {
|
|
23670
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23671
|
+
}
|
|
23672
|
+
if (cursor !== null) {
|
|
23673
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23674
|
+
}
|
|
23675
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22680
23676
|
const rows = allRows(
|
|
22681
23677
|
this.db.prepare(
|
|
22682
23678
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22683
23679
|
FROM secret_vault_deref ${where}
|
|
22684
|
-
ORDER BY at DESC,
|
|
23680
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22685
23681
|
),
|
|
22686
|
-
{
|
|
23682
|
+
bindParams({
|
|
23683
|
+
limit: limit + 1,
|
|
23684
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23685
|
+
})
|
|
22687
23686
|
);
|
|
22688
|
-
const
|
|
23687
|
+
const hasMore = rows.length > limit;
|
|
23688
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23689
|
+
const last = page[page.length - 1];
|
|
23690
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22689
23691
|
this.db,
|
|
22690
23692
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22691
23693
|
);
|
|
22692
23694
|
return {
|
|
22693
|
-
|
|
23695
|
+
items: page.map((r) => ({
|
|
22694
23696
|
id: r.id,
|
|
22695
23697
|
pointerId: r.pointer_id,
|
|
22696
23698
|
at: new Date(r.at).toISOString(),
|
|
@@ -22700,12 +23702,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22700
23702
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22701
23703
|
pointerCount: r.pointer_count
|
|
22702
23704
|
})),
|
|
23705
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22703
23706
|
hiddenBatched
|
|
22704
23707
|
};
|
|
22705
23708
|
}
|
|
22706
23709
|
countEntries() {
|
|
22707
23710
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22708
23711
|
}
|
|
23712
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23713
|
+
countReused() {
|
|
23714
|
+
return countScalar(
|
|
23715
|
+
this.db,
|
|
23716
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23717
|
+
);
|
|
23718
|
+
}
|
|
22709
23719
|
};
|
|
22710
23720
|
|
|
22711
23721
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22720,7 +23730,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22720
23730
|
var SCAN_COVERAGE = [
|
|
22721
23731
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22722
23732
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22723
|
-
{ provider: "codex", coverage:
|
|
23733
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23734
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23735
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22724
23736
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22725
23737
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22726
23738
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23053,7 +24065,7 @@ var SqliteSecurityRepository = class {
|
|
|
23053
24065
|
};
|
|
23054
24066
|
|
|
23055
24067
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23056
|
-
import { randomUUID as
|
|
24068
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23057
24069
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23058
24070
|
var IN_CHUNK = 500;
|
|
23059
24071
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23309,7 +24321,7 @@ var SqliteSharesRepository = class {
|
|
|
23309
24321
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23310
24322
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23311
24323
|
).run({
|
|
23312
|
-
id:
|
|
24324
|
+
id: randomUUID9(),
|
|
23313
24325
|
destinationId,
|
|
23314
24326
|
host: dest.host,
|
|
23315
24327
|
decision,
|
|
@@ -23458,7 +24470,7 @@ var SqliteSharesRepository = class {
|
|
|
23458
24470
|
let destinationId = destIds.get(hit.host);
|
|
23459
24471
|
if (destinationId === void 0) {
|
|
23460
24472
|
destStmt.run({
|
|
23461
|
-
id:
|
|
24473
|
+
id: randomUUID9(),
|
|
23462
24474
|
kind: hit.kind,
|
|
23463
24475
|
name: hit.name,
|
|
23464
24476
|
host: hit.host,
|
|
@@ -23474,7 +24486,7 @@ var SqliteSharesRepository = class {
|
|
|
23474
24486
|
let endpointId = endpointIds.get(endpointKey);
|
|
23475
24487
|
if (endpointId === void 0) {
|
|
23476
24488
|
endpointStmt.run({
|
|
23477
|
-
id:
|
|
24489
|
+
id: randomUUID9(),
|
|
23478
24490
|
destinationId,
|
|
23479
24491
|
method: hit.method,
|
|
23480
24492
|
transport: hit.transport,
|
|
@@ -23487,7 +24499,7 @@ var SqliteSharesRepository = class {
|
|
|
23487
24499
|
endpointIds.set(endpointKey, endpointId);
|
|
23488
24500
|
}
|
|
23489
24501
|
siteStmt.run({
|
|
23490
|
-
id:
|
|
24502
|
+
id: randomUUID9(),
|
|
23491
24503
|
endpointId,
|
|
23492
24504
|
project: input.project,
|
|
23493
24505
|
projectKey: input.projectKey,
|
|
@@ -23852,6 +24864,9 @@ function purgeSampleData(db) {
|
|
|
23852
24864
|
}
|
|
23853
24865
|
|
|
23854
24866
|
// ../../packages/persistence/src/database.ts
|
|
24867
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24868
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24869
|
+
);
|
|
23855
24870
|
function linkHost(input, hostId) {
|
|
23856
24871
|
return hostId ? { ...input, hostId } : input;
|
|
23857
24872
|
}
|
|
@@ -23873,21 +24888,34 @@ function openWithPragmas(file2) {
|
|
|
23873
24888
|
}
|
|
23874
24889
|
return db;
|
|
23875
24890
|
}
|
|
23876
|
-
function backupLegacyStore(file2) {
|
|
23877
|
-
|
|
23878
|
-
|
|
23879
|
-
|
|
23880
|
-
|
|
23881
|
-
|
|
24891
|
+
function backupLegacyStore(db, file2) {
|
|
24892
|
+
reapStalePartials(file2);
|
|
24893
|
+
const backup = backupPath(file2, "legacy");
|
|
24894
|
+
let snapshotted = false;
|
|
24895
|
+
let snapshotError;
|
|
24896
|
+
try {
|
|
24897
|
+
snapshotStore(db, backup);
|
|
24898
|
+
snapshotted = true;
|
|
24899
|
+
} catch (error51) {
|
|
24900
|
+
snapshotError = error51;
|
|
24901
|
+
} finally {
|
|
24902
|
+
db.close();
|
|
23882
24903
|
}
|
|
24904
|
+
if (!snapshotted) {
|
|
24905
|
+
akaWarn(
|
|
24906
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24907
|
+
);
|
|
24908
|
+
moveStoreAside(file2, backup);
|
|
24909
|
+
return backup;
|
|
24910
|
+
}
|
|
24911
|
+
discardStore(file2, backup);
|
|
23883
24912
|
return backup;
|
|
23884
24913
|
}
|
|
23885
24914
|
function openAndInitialize(file2) {
|
|
23886
24915
|
let db = openWithPragmas(file2);
|
|
23887
24916
|
try {
|
|
23888
24917
|
if (isForeignSqliteLineage(db)) {
|
|
23889
|
-
db
|
|
23890
|
-
const backup = backupLegacyStore(file2);
|
|
24918
|
+
const backup = backupLegacyStore(db, file2);
|
|
23891
24919
|
db = openWithPragmas(file2);
|
|
23892
24920
|
akaWarn(
|
|
23893
24921
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23931,7 +24959,7 @@ function openAndInitialize(file2) {
|
|
|
23931
24959
|
}
|
|
23932
24960
|
function openLocalDatabase(dir) {
|
|
23933
24961
|
ensureDataDirSync(dir);
|
|
23934
|
-
const file2 =
|
|
24962
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23935
24963
|
const {
|
|
23936
24964
|
db,
|
|
23937
24965
|
events,
|
|
@@ -24048,7 +25076,7 @@ function openLocalDatabase(dir) {
|
|
|
24048
25076
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24049
25077
|
if (!definitionId) continue;
|
|
24050
25078
|
inspectionFindings.insertFinding({
|
|
24051
|
-
id:
|
|
25079
|
+
id: randomUUID10(),
|
|
24052
25080
|
auditEventId: record2.scanEvent.id,
|
|
24053
25081
|
inspectionDefinitionId: definitionId,
|
|
24054
25082
|
span: finding.span,
|
|
@@ -24154,7 +25182,9 @@ function openLocalDatabase(dir) {
|
|
|
24154
25182
|
transaction,
|
|
24155
25183
|
close: () => {
|
|
24156
25184
|
db.close();
|
|
24157
|
-
}
|
|
25185
|
+
},
|
|
25186
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25187
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24158
25188
|
};
|
|
24159
25189
|
}
|
|
24160
25190
|
|
|
@@ -24178,6 +25208,20 @@ var UserGrantPolicyProvider = class {
|
|
|
24178
25208
|
}
|
|
24179
25209
|
};
|
|
24180
25210
|
|
|
25211
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25212
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25213
|
+
import {
|
|
25214
|
+
closeSync,
|
|
25215
|
+
existsSync as existsSync2,
|
|
25216
|
+
openSync,
|
|
25217
|
+
readFileSync,
|
|
25218
|
+
rmSync as rmSync3,
|
|
25219
|
+
statSync as statSync2,
|
|
25220
|
+
writeFileSync as writeFileSync2
|
|
25221
|
+
} from "fs";
|
|
25222
|
+
import { hostname as hostname3 } from "os";
|
|
25223
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25224
|
+
|
|
24181
25225
|
// ../../packages/persistence/src/finding-key.ts
|
|
24182
25226
|
import { createHash as createHash3 } from "crypto";
|
|
24183
25227
|
function normalizeFilePath(filePath) {
|
|
@@ -24190,13 +25234,13 @@ function computeFindingKey(input) {
|
|
|
24190
25234
|
|
|
24191
25235
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24192
25236
|
import { createHmac, randomBytes } from "crypto";
|
|
24193
|
-
import { existsSync as
|
|
24194
|
-
import { join as
|
|
25237
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25238
|
+
import { join as join3 } from "path";
|
|
24195
25239
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24196
|
-
var
|
|
25240
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24197
25241
|
var KEY_MATERIAL_BYTES = 32;
|
|
24198
25242
|
function keyFilePath(dataDir2) {
|
|
24199
|
-
return
|
|
25243
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24200
25244
|
}
|
|
24201
25245
|
function parseKeyFile(raw) {
|
|
24202
25246
|
const parsed = JSON.parse(raw);
|
|
@@ -24234,8 +25278,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
24234
25278
|
}
|
|
24235
25279
|
};
|
|
24236
25280
|
function storedKeyVersionFloor(dataDir2) {
|
|
24237
|
-
const file2 =
|
|
24238
|
-
if (!
|
|
25281
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25282
|
+
if (!existsSync3(file2)) return 0;
|
|
24239
25283
|
let db;
|
|
24240
25284
|
try {
|
|
24241
25285
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -24260,18 +25304,36 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
24260
25304
|
db?.close();
|
|
24261
25305
|
}
|
|
24262
25306
|
}
|
|
24263
|
-
function
|
|
25307
|
+
function serializeKey(key) {
|
|
25308
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
25309
|
+
}
|
|
25310
|
+
function createKeyFile(dataDir2, key) {
|
|
24264
25311
|
ensureDataDirSync(dataDir2);
|
|
24265
25312
|
const file2 = keyFilePath(dataDir2);
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
25313
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
25314
|
+
`)) return key;
|
|
25315
|
+
const winner = readFingerprintKey(dataDir2);
|
|
25316
|
+
if (winner) {
|
|
25317
|
+
tightenFile(file2);
|
|
25318
|
+
return winner;
|
|
25319
|
+
}
|
|
25320
|
+
const occupant = classifyOccupant(file2);
|
|
25321
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
25322
|
+
}
|
|
25323
|
+
function occupantMessage(file2, kind) {
|
|
25324
|
+
switch (kind) {
|
|
25325
|
+
case "symlink":
|
|
25326
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
25327
|
+
case "gone":
|
|
25328
|
+
return "exception key file was removed while it was being created";
|
|
25329
|
+
case "unknown":
|
|
25330
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
25331
|
+
}
|
|
24270
25332
|
}
|
|
24271
25333
|
function readFingerprintKey(dataDir2) {
|
|
24272
25334
|
let raw;
|
|
24273
25335
|
try {
|
|
24274
|
-
raw =
|
|
25336
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24275
25337
|
} catch (err) {
|
|
24276
25338
|
if (err.code === "ENOENT") return null;
|
|
24277
25339
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24284,7 +25346,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
24284
25346
|
tightenFile(keyFilePath(dataDir2));
|
|
24285
25347
|
return existing;
|
|
24286
25348
|
}
|
|
24287
|
-
return
|
|
25349
|
+
return createKeyFile(dataDir2, {
|
|
24288
25350
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
24289
25351
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
24290
25352
|
});
|
|
@@ -24297,21 +25359,21 @@ function fingerprintValue(key, raw) {
|
|
|
24297
25359
|
import { renameSync as renameSync3 } from "fs";
|
|
24298
25360
|
import { mkdir } from "fs/promises";
|
|
24299
25361
|
import { homedir } from "os";
|
|
24300
|
-
import { join as
|
|
25362
|
+
import { join as join4 } from "path";
|
|
24301
25363
|
function defaultDataDir() {
|
|
24302
|
-
return
|
|
25364
|
+
return join4(homedir(), ".aka");
|
|
24303
25365
|
}
|
|
24304
25366
|
function settingsDir(base = defaultDataDir()) {
|
|
24305
|
-
return
|
|
25367
|
+
return join4(base, "settings");
|
|
24306
25368
|
}
|
|
24307
25369
|
function dataDir(base = defaultDataDir()) {
|
|
24308
|
-
return
|
|
25370
|
+
return join4(base, "data");
|
|
24309
25371
|
}
|
|
24310
25372
|
function dbPath(base = defaultDataDir()) {
|
|
24311
|
-
return
|
|
25373
|
+
return join4(dataDir(base), "aka.db");
|
|
24312
25374
|
}
|
|
24313
25375
|
function keysDir(base = defaultDataDir()) {
|
|
24314
|
-
return
|
|
25376
|
+
return join4(base, "keys");
|
|
24315
25377
|
}
|
|
24316
25378
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24317
25379
|
ensureDataDirSync(dir);
|
|
@@ -24324,8 +25386,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24324
25386
|
for (const { name, dest } of moves) {
|
|
24325
25387
|
try {
|
|
24326
25388
|
ensureDataDirSync(dest);
|
|
24327
|
-
const moved =
|
|
24328
|
-
renameSync3(
|
|
25389
|
+
const moved = join4(dest, name);
|
|
25390
|
+
renameSync3(join4(base, name), moved);
|
|
24329
25391
|
tightenFile(moved);
|
|
24330
25392
|
} catch {
|
|
24331
25393
|
}
|
|
@@ -24333,10 +25395,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24333
25395
|
}
|
|
24334
25396
|
|
|
24335
25397
|
// ../../packages/persistence/src/settings.ts
|
|
24336
|
-
import { readFileSync as
|
|
24337
|
-
import { join as
|
|
25398
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25399
|
+
import { join as join5 } from "path";
|
|
25400
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24338
25401
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24339
|
-
const record2 = readJson(
|
|
25402
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24340
25403
|
if (!record2) return defaultWorkspaceSettings();
|
|
24341
25404
|
try {
|
|
24342
25405
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24347,7 +25410,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24347
25410
|
function readJson(file2) {
|
|
24348
25411
|
let text;
|
|
24349
25412
|
try {
|
|
24350
|
-
text =
|
|
25413
|
+
text = readFileSync3(file2, "utf8");
|
|
24351
25414
|
} catch {
|
|
24352
25415
|
return null;
|
|
24353
25416
|
}
|
|
@@ -24468,13 +25531,18 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24468
25531
|
import {
|
|
24469
25532
|
chmodSync as chmodSync2,
|
|
24470
25533
|
mkdirSync as mkdirSync2,
|
|
24471
|
-
readFileSync as
|
|
25534
|
+
readFileSync as readFileSync4,
|
|
24472
25535
|
renameSync as renameSync4,
|
|
24473
|
-
rmSync as
|
|
24474
|
-
statSync,
|
|
24475
|
-
writeFileSync as
|
|
25536
|
+
rmSync as rmSync4,
|
|
25537
|
+
statSync as statSync3,
|
|
25538
|
+
writeFileSync as writeFileSync3
|
|
24476
25539
|
} from "fs";
|
|
24477
|
-
import { join as
|
|
25540
|
+
import { join as join6 } from "path";
|
|
25541
|
+
var VAULT_OCCUPANT_REASON = {
|
|
25542
|
+
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25543
|
+
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
25544
|
+
unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
|
|
25545
|
+
};
|
|
24478
25546
|
var VaultKeyEpochMissingError = class extends Error {
|
|
24479
25547
|
version;
|
|
24480
25548
|
constructor(version2) {
|
|
@@ -24567,28 +25635,28 @@ function claimRotationLock(lock, owner) {
|
|
|
24567
25635
|
throw asError(err);
|
|
24568
25636
|
}
|
|
24569
25637
|
try {
|
|
24570
|
-
|
|
25638
|
+
writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
|
|
24571
25639
|
`, { mode: DATA_FILE_MODE });
|
|
24572
25640
|
return true;
|
|
24573
25641
|
} catch (err) {
|
|
24574
|
-
|
|
25642
|
+
rmSync4(lock, { recursive: true, force: true });
|
|
24575
25643
|
throw asError(err);
|
|
24576
25644
|
}
|
|
24577
25645
|
}
|
|
24578
25646
|
function acquireRotationLock(keysDir2) {
|
|
24579
|
-
const lock =
|
|
25647
|
+
const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
24580
25648
|
const owner = randomBytes2(16).toString("hex");
|
|
24581
25649
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
24582
25650
|
let held;
|
|
24583
25651
|
try {
|
|
24584
|
-
held =
|
|
25652
|
+
held = statSync3(lock);
|
|
24585
25653
|
} catch {
|
|
24586
25654
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24587
25655
|
}
|
|
24588
25656
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
24589
25657
|
const aside = `${lock}.stale.${owner}`;
|
|
24590
25658
|
try {
|
|
24591
|
-
const now =
|
|
25659
|
+
const now = statSync3(lock);
|
|
24592
25660
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
24593
25661
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24594
25662
|
}
|
|
@@ -24597,17 +25665,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
24597
25665
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
24598
25666
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
24599
25667
|
}
|
|
24600
|
-
|
|
25668
|
+
rmSync4(aside, { recursive: true, force: true });
|
|
24601
25669
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
24602
25670
|
return { lock, owner };
|
|
24603
25671
|
}
|
|
24604
25672
|
function releaseRotationLock(lease) {
|
|
24605
25673
|
try {
|
|
24606
|
-
if (
|
|
25674
|
+
if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
24607
25675
|
} catch {
|
|
24608
25676
|
return;
|
|
24609
25677
|
}
|
|
24610
|
-
|
|
25678
|
+
rmSync4(lease.lock, { recursive: true, force: true });
|
|
24611
25679
|
}
|
|
24612
25680
|
function withRotationLock(keysDir2, work) {
|
|
24613
25681
|
ensureDataDirSync(keysDir2);
|
|
@@ -24624,7 +25692,7 @@ var FileKeyProvider = class {
|
|
|
24624
25692
|
this.#keysDir = keysDir2;
|
|
24625
25693
|
}
|
|
24626
25694
|
get filePath() {
|
|
24627
|
-
return
|
|
25695
|
+
return join6(this.#keysDir, VAULT_KEY_FILENAME);
|
|
24628
25696
|
}
|
|
24629
25697
|
loadOrCreate() {
|
|
24630
25698
|
return asAsync(() => {
|
|
@@ -24654,7 +25722,7 @@ var FileKeyProvider = class {
|
|
|
24654
25722
|
#read() {
|
|
24655
25723
|
let raw;
|
|
24656
25724
|
try {
|
|
24657
|
-
raw =
|
|
25725
|
+
raw = readFileSync4(this.filePath, "utf8");
|
|
24658
25726
|
} catch (err) {
|
|
24659
25727
|
if (err.code === "ENOENT") return null;
|
|
24660
25728
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24662,34 +25730,32 @@ var FileKeyProvider = class {
|
|
|
24662
25730
|
return parseKeyring(raw);
|
|
24663
25731
|
}
|
|
24664
25732
|
/**
|
|
24665
|
-
* First mint: the keyring is
|
|
24666
|
-
*
|
|
24667
|
-
*
|
|
24668
|
-
*
|
|
24669
|
-
*
|
|
24670
|
-
*
|
|
24671
|
-
*
|
|
24672
|
-
*
|
|
25733
|
+
* First mint: the keyring is CREATED, never replaced, so two processes racing
|
|
25734
|
+
* a fresh machine cannot each mint a different epoch 1 — with tmp + rename
|
|
25735
|
+
* the loser's replace would orphan everything the winner had already sealed.
|
|
25736
|
+
* The loser re-reads and adopts the winner's keyring; it minted nothing.
|
|
25737
|
+
*
|
|
25738
|
+
* `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
|
|
25739
|
+
* at the final path, so the keyring never exists at zero length: a reader —
|
|
25740
|
+
* including the loser, re-reading in order to adopt — sees the file absent or
|
|
25741
|
+
* whole, and never mistakes a live keyring for a corrupt one. A corrupt file
|
|
25742
|
+
* still throws from the parse and is never re-minted over.
|
|
24673
25743
|
*/
|
|
24674
25744
|
#createExclusive() {
|
|
24675
25745
|
ensureDataDirSync(this.#keysDir);
|
|
24676
25746
|
const keyring = mintKeyring();
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
|
|
24686
|
-
if (!winner) {
|
|
24687
|
-
throw new Error("vault: key file vanished during first mint", { cause: err });
|
|
24688
|
-
}
|
|
24689
|
-
return winner;
|
|
25747
|
+
if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
25748
|
+
`)) return keyring;
|
|
25749
|
+
const winner = this.#read();
|
|
25750
|
+
if (!winner) {
|
|
25751
|
+
const occupant = classifyOccupant(this.filePath);
|
|
25752
|
+
throw new KeyUnclaimableError(
|
|
25753
|
+
`vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
|
|
25754
|
+
occupant.cause
|
|
25755
|
+
);
|
|
24690
25756
|
}
|
|
24691
25757
|
tightenFileMode(this.filePath);
|
|
24692
|
-
return
|
|
25758
|
+
return winner;
|
|
24693
25759
|
}
|
|
24694
25760
|
/**
|
|
24695
25761
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
@@ -24700,7 +25766,7 @@ var FileKeyProvider = class {
|
|
|
24700
25766
|
ensureDataDirSync(this.#keysDir);
|
|
24701
25767
|
const file2 = this.filePath;
|
|
24702
25768
|
const tmp = `${file2}.tmp`;
|
|
24703
|
-
|
|
25769
|
+
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
24704
25770
|
`, { mode: DATA_FILE_MODE });
|
|
24705
25771
|
renameSync4(tmp, file2);
|
|
24706
25772
|
tightenFileMode(file2);
|
|
@@ -24826,7 +25892,7 @@ function createKeyProvider(custody, keysDir2) {
|
|
|
24826
25892
|
}
|
|
24827
25893
|
|
|
24828
25894
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24829
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25895
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24830
25896
|
var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
|
|
24831
25897
|
var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
|
|
24832
25898
|
var VAULT_PURGE_POINTER_ID = "*";
|
|
@@ -24853,14 +25919,12 @@ function parsePointer(token) {
|
|
|
24853
25919
|
var SecretVault = class {
|
|
24854
25920
|
#repo;
|
|
24855
25921
|
#keys;
|
|
24856
|
-
#fingerprintKey;
|
|
24857
25922
|
#isConsented;
|
|
24858
25923
|
#verifyGrant;
|
|
24859
25924
|
#now;
|
|
24860
25925
|
constructor(deps) {
|
|
24861
25926
|
this.#repo = deps.repo;
|
|
24862
25927
|
this.#keys = deps.keys;
|
|
24863
|
-
this.#fingerprintKey = deps.fingerprintKey;
|
|
24864
25928
|
this.#isConsented = deps.isConsented;
|
|
24865
25929
|
this.#verifyGrant = deps.verifyGrant;
|
|
24866
25930
|
this.#now = deps.now ?? (() => Date.now());
|
|
@@ -24869,10 +25933,23 @@ var SecretVault = class {
|
|
|
24869
25933
|
* Store a value and return the pointer that stands for it. The same value
|
|
24870
25934
|
* always yields the same pointer on this machine — one row, one pointer id,
|
|
24871
25935
|
* one category — which is what makes dedup and reuse counting work.
|
|
25936
|
+
*
|
|
25937
|
+
* `fingerprintKey` is the exception-key epoch this value's fingerprint is
|
|
25938
|
+
* derived under — a different key from the vault's, with different rotation
|
|
25939
|
+
* semantics. It is a parameter of the WRITE rather than a constructor dep,
|
|
25940
|
+
* and a thunk rather than a value, so that the only way to reach a key is to
|
|
25941
|
+
* store something: a read-only caller never names it, and a caller whose
|
|
25942
|
+
* source mints on absence mints only once consent has actually opened the
|
|
25943
|
+
* write. `refreshFingerprints` takes its key the same way, for the same
|
|
25944
|
+
* reason.
|
|
25945
|
+
*
|
|
25946
|
+
* Resolved once per call, so the fingerprint and the version it is recorded
|
|
25947
|
+
* under can never come from two different epochs.
|
|
24872
25948
|
*/
|
|
24873
|
-
async tokenize(raw, meta3) {
|
|
25949
|
+
async tokenize(raw, meta3, fingerprintKey) {
|
|
24874
25950
|
if (!this.#isConsented()) return CONSENT_ABSENT;
|
|
24875
|
-
const
|
|
25951
|
+
const fpKey = fingerprintKey();
|
|
25952
|
+
const valueFingerprint = fingerprintValue(fpKey, raw);
|
|
24876
25953
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
24877
25954
|
const now = this.#now();
|
|
24878
25955
|
if (existing) {
|
|
@@ -24888,7 +25965,7 @@ var SecretVault = class {
|
|
|
24888
25965
|
{
|
|
24889
25966
|
pointerId: base32Encode(pointerId),
|
|
24890
25967
|
valueFingerprint,
|
|
24891
|
-
fingerprintKeyVersion:
|
|
25968
|
+
fingerprintKeyVersion: fpKey.version,
|
|
24892
25969
|
keyVersion: version2,
|
|
24893
25970
|
// Recorded so the row stays OPENABLE if the wire-format constant ever
|
|
24894
25971
|
// moves: it is part of this row's AEAD AAD. It is not a tag input —
|
|
@@ -25132,7 +26209,7 @@ var SecretVault = class {
|
|
|
25132
26209
|
purgeVault() {
|
|
25133
26210
|
const destroyed = this.#repo.purgeAll();
|
|
25134
26211
|
this.#repo.recordDeref({
|
|
25135
|
-
id:
|
|
26212
|
+
id: randomUUID12(),
|
|
25136
26213
|
pointerId: VAULT_PURGE_POINTER_ID,
|
|
25137
26214
|
at: this.#now(),
|
|
25138
26215
|
target: "human",
|
|
@@ -25201,7 +26278,7 @@ var SecretVault = class {
|
|
|
25201
26278
|
}
|
|
25202
26279
|
#audit(pointerId, opts, outcome) {
|
|
25203
26280
|
this.#repo.recordDeref({
|
|
25204
|
-
id:
|
|
26281
|
+
id: randomUUID12(),
|
|
25205
26282
|
pointerId,
|
|
25206
26283
|
at: this.#now(),
|
|
25207
26284
|
target: opts.target,
|
|
@@ -25216,15 +26293,15 @@ var SecretVault = class {
|
|
|
25216
26293
|
};
|
|
25217
26294
|
|
|
25218
26295
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
25219
|
-
import { existsSync as
|
|
25220
|
-
import { join as
|
|
26296
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
26297
|
+
import { join as join7 } from "path";
|
|
25221
26298
|
var MARKER = "warn-era-capped";
|
|
25222
26299
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
25223
26300
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
25224
|
-
const marker =
|
|
25225
|
-
if (
|
|
26301
|
+
const marker = join7(dataDir2, MARKER);
|
|
26302
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
25226
26303
|
const capped = db.policies.capCategoryActions();
|
|
25227
|
-
|
|
26304
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
25228
26305
|
`, { mode: DATA_FILE_MODE });
|
|
25229
26306
|
return { capped };
|
|
25230
26307
|
}
|
|
@@ -25278,11 +26355,11 @@ function resolveProvider() {
|
|
|
25278
26355
|
}
|
|
25279
26356
|
|
|
25280
26357
|
// ../../packages/plugin-sdk/src/config.ts
|
|
25281
|
-
function loadConfig(base = defaultDataDir()) {
|
|
26358
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
25282
26359
|
try {
|
|
25283
26360
|
ensureLayoutDirSync(base);
|
|
25284
|
-
const settingsFile =
|
|
25285
|
-
if (
|
|
26361
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
26362
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
25286
26363
|
} catch {
|
|
25287
26364
|
}
|
|
25288
26365
|
migrateLegacyLayout(base);
|
|
@@ -25293,21 +26370,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
25293
26370
|
dbPath: dbPath(base),
|
|
25294
26371
|
settingsDir: settingsDir(base),
|
|
25295
26372
|
onboarded: settings.onboardedAt != null,
|
|
25296
|
-
provider: resolveProviderSafe()
|
|
26373
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
25297
26374
|
};
|
|
25298
26375
|
}
|
|
25299
|
-
function resolveProviderSafe() {
|
|
26376
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
25300
26377
|
try {
|
|
25301
|
-
return
|
|
26378
|
+
return resolveProviderFn();
|
|
25302
26379
|
} catch {
|
|
25303
26380
|
return { provider: "anthropic" };
|
|
25304
26381
|
}
|
|
25305
26382
|
}
|
|
25306
26383
|
|
|
25307
26384
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
25308
|
-
import { readdirSync, readFileSync as
|
|
26385
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
25309
26386
|
import { homedir as homedir2 } from "os";
|
|
25310
|
-
import { basename as
|
|
26387
|
+
import { basename as basename3, join as join10 } from "path";
|
|
25311
26388
|
|
|
25312
26389
|
// ../../packages/detections/src/egress/registry.ts
|
|
25313
26390
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -27964,7 +29041,7 @@ var gcp_service_account_default = {
|
|
|
27964
29041
|
severity: "critical",
|
|
27965
29042
|
matcher: {
|
|
27966
29043
|
type: "regex",
|
|
27967
|
-
pattern: "
|
|
29044
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27968
29045
|
flags: "g"
|
|
27969
29046
|
},
|
|
27970
29047
|
examples: [
|
|
@@ -28346,15 +29423,15 @@ function uniqueRuleIds(findings) {
|
|
|
28346
29423
|
}
|
|
28347
29424
|
|
|
28348
29425
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
28349
|
-
import { existsSync as
|
|
28350
|
-
import { basename, dirname, isAbsolute, join as
|
|
29426
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29427
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
28351
29428
|
function resolveRepo(cwd) {
|
|
28352
29429
|
try {
|
|
28353
29430
|
const root = findGitRoot(cwd);
|
|
28354
29431
|
if (!root) return void 0;
|
|
28355
29432
|
const ctx = resolveGitContext(root);
|
|
28356
29433
|
const url2 = ctx ? remoteUrl(ctx) : void 0;
|
|
28357
|
-
return (url2 ? slugFromUrl(url2) : void 0) ??
|
|
29434
|
+
return (url2 ? slugFromUrl(url2) : void 0) ?? basename2(ctx?.headRoot ?? root);
|
|
28358
29435
|
} catch {
|
|
28359
29436
|
return void 0;
|
|
28360
29437
|
}
|
|
@@ -28362,36 +29439,36 @@ function resolveRepo(cwd) {
|
|
|
28362
29439
|
function findGitRoot(start) {
|
|
28363
29440
|
let dir = start;
|
|
28364
29441
|
for (; ; ) {
|
|
28365
|
-
if (
|
|
28366
|
-
const parent =
|
|
29442
|
+
if (existsSync6(join9(dir, ".git"))) return dir;
|
|
29443
|
+
const parent = dirname2(dir);
|
|
28367
29444
|
if (parent === dir) return void 0;
|
|
28368
29445
|
dir = parent;
|
|
28369
29446
|
}
|
|
28370
29447
|
}
|
|
28371
29448
|
function resolveGitContext(root) {
|
|
28372
|
-
const dotGit =
|
|
29449
|
+
const dotGit = join9(root, ".git");
|
|
28373
29450
|
try {
|
|
28374
|
-
if (
|
|
28375
|
-
return { configPath:
|
|
29451
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
29452
|
+
return { configPath: join9(dotGit, "config"), headRoot: root };
|
|
28376
29453
|
}
|
|
28377
29454
|
} catch {
|
|
28378
29455
|
return void 0;
|
|
28379
29456
|
}
|
|
28380
29457
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
28381
29458
|
if (!target) return void 0;
|
|
28382
|
-
const gitdir = isAbsolute(target) ? target :
|
|
28383
|
-
if (
|
|
28384
|
-
return { configPath:
|
|
29459
|
+
const gitdir = isAbsolute(target) ? target : join9(root, target);
|
|
29460
|
+
if (existsSync6(join9(gitdir, "config"))) {
|
|
29461
|
+
return { configPath: join9(gitdir, "config"), headRoot: root };
|
|
28385
29462
|
}
|
|
28386
|
-
const commonRaw = safeRead(
|
|
29463
|
+
const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
|
|
28387
29464
|
if (!commonRaw) return void 0;
|
|
28388
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
28389
|
-
const headRoot =
|
|
28390
|
-
return { configPath:
|
|
29465
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
|
|
29466
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
|
|
29467
|
+
return { configPath: join9(commonGitDir, "config"), headRoot };
|
|
28391
29468
|
}
|
|
28392
29469
|
function safeRead(path) {
|
|
28393
29470
|
try {
|
|
28394
|
-
return
|
|
29471
|
+
return readFileSync5(path, "utf8");
|
|
28395
29472
|
} catch {
|
|
28396
29473
|
return void 0;
|
|
28397
29474
|
}
|
|
@@ -28429,13 +29506,13 @@ function slugFromUrl(url2) {
|
|
|
28429
29506
|
}
|
|
28430
29507
|
|
|
28431
29508
|
// ../../packages/plugin-sdk/src/events.ts
|
|
28432
|
-
import { createHash as createHash4, randomUUID as
|
|
29509
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28433
29510
|
function contentHashOf(text) {
|
|
28434
29511
|
return createHash4("sha256").update(text).digest("hex");
|
|
28435
29512
|
}
|
|
28436
29513
|
function buildIngestEvent(input) {
|
|
28437
29514
|
return {
|
|
28438
|
-
id:
|
|
29515
|
+
id: randomUUID13(),
|
|
28439
29516
|
sourceTool: input.sourceTool,
|
|
28440
29517
|
kind: input.kind,
|
|
28441
29518
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -28446,82 +29523,507 @@ function buildIngestEvent(input) {
|
|
|
28446
29523
|
// SDK boot in the fail-open hook path). Preserve any id the caller already set.
|
|
28447
29524
|
metadata: {
|
|
28448
29525
|
...input.metadata,
|
|
28449
|
-
correlationId: input.metadata?.correlationId ??
|
|
29526
|
+
correlationId: input.metadata?.correlationId ?? randomUUID13()
|
|
28450
29527
|
}
|
|
28451
29528
|
};
|
|
28452
29529
|
}
|
|
28453
29530
|
|
|
28454
|
-
// ../../packages/plugin-sdk/src/
|
|
28455
|
-
import {
|
|
28456
|
-
|
|
28457
|
-
|
|
28458
|
-
|
|
28459
|
-
|
|
28460
|
-
|
|
28461
|
-
|
|
28462
|
-
|
|
28463
|
-
|
|
28464
|
-
|
|
28465
|
-
|
|
28466
|
-
|
|
28467
|
-
|
|
28468
|
-
|
|
29531
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29532
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29533
|
+
import { fileURLToPath } from "url";
|
|
29534
|
+
import { Worker } from "worker_threads";
|
|
29535
|
+
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
29536
|
+
var ISOLATED_PROBE_BUDGET_MS = 1e3;
|
|
29537
|
+
var ISOLATED_START_BUDGET_MS = 5e3;
|
|
29538
|
+
var ATTRIBUTION_MIN_RULE_MS = 500;
|
|
29539
|
+
var ATTRIBUTION_MIN_SHARE = 0.5;
|
|
29540
|
+
var resolvedWorkerUrl;
|
|
29541
|
+
function resolveWorkerUrl() {
|
|
29542
|
+
if (resolvedWorkerUrl !== void 0) return resolvedWorkerUrl ?? void 0;
|
|
29543
|
+
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
29544
|
+
const candidate = new URL(name, import.meta.url);
|
|
29545
|
+
try {
|
|
29546
|
+
if (existsSync7(fileURLToPath(candidate))) {
|
|
29547
|
+
resolvedWorkerUrl = candidate;
|
|
29548
|
+
return candidate;
|
|
29549
|
+
}
|
|
29550
|
+
} catch {
|
|
29551
|
+
}
|
|
29552
|
+
}
|
|
29553
|
+
resolvedWorkerUrl = null;
|
|
29554
|
+
return void 0;
|
|
29555
|
+
}
|
|
29556
|
+
function messageOf(error51) {
|
|
29557
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
29558
|
+
}
|
|
29559
|
+
function createIsolatedScanner(data, opts = {}) {
|
|
29560
|
+
const budgetMs = opts.budgetMs ?? ISOLATED_SCAN_BUDGET_MS;
|
|
29561
|
+
const probeBudgetMs = opts.probeBudgetMs ?? ISOLATED_PROBE_BUDGET_MS;
|
|
29562
|
+
const startBudgetMs = opts.startBudgetMs ?? ISOLATED_START_BUDGET_MS;
|
|
29563
|
+
const minAttributionMs = opts.minAttributionMs ?? ATTRIBUTION_MIN_RULE_MS;
|
|
29564
|
+
let worker;
|
|
29565
|
+
let readyWorker;
|
|
29566
|
+
let broken;
|
|
29567
|
+
let closed = false;
|
|
29568
|
+
let nextJobId = 1;
|
|
29569
|
+
let pending;
|
|
29570
|
+
const terminating = /* @__PURE__ */ new Set();
|
|
29571
|
+
let chain = Promise.resolve();
|
|
29572
|
+
function clearTimers(job) {
|
|
29573
|
+
if (job.startupTimer !== void 0) clearTimeout(job.startupTimer);
|
|
29574
|
+
if (job.timer !== void 0) clearTimeout(job.timer);
|
|
29575
|
+
}
|
|
29576
|
+
function take() {
|
|
29577
|
+
const job = pending;
|
|
29578
|
+
if (!job) return void 0;
|
|
29579
|
+
pending = void 0;
|
|
29580
|
+
clearTimers(job);
|
|
29581
|
+
worker?.unref();
|
|
29582
|
+
return job;
|
|
29583
|
+
}
|
|
29584
|
+
function failPending(outcome) {
|
|
29585
|
+
take()?.fail(outcome);
|
|
29586
|
+
}
|
|
29587
|
+
function kill(dead) {
|
|
29588
|
+
if (worker === dead) worker = void 0;
|
|
29589
|
+
if (readyWorker === dead) readyWorker = void 0;
|
|
29590
|
+
const done = dead.terminate().catch(() => void 0);
|
|
29591
|
+
terminating.add(done);
|
|
29592
|
+
void done.finally(() => terminating.delete(done));
|
|
29593
|
+
}
|
|
29594
|
+
function onDeadline(job) {
|
|
29595
|
+
if (pending !== job) return;
|
|
29596
|
+
const now = performance.now();
|
|
29597
|
+
const runningMs = now - job.progressAt;
|
|
29598
|
+
const elapsedMs = now - job.startedAt;
|
|
29599
|
+
const blamed = job.progressIndex >= 0 && runningMs >= minAttributionMs && runningMs >= elapsedMs * ATTRIBUTION_MIN_SHARE;
|
|
29600
|
+
const culpritIndex = blamed ? job.progressIndex : void 0;
|
|
29601
|
+
kill(job.worker);
|
|
29602
|
+
failPending({ status: "timeout", culpritIndex, elapsedMs });
|
|
29603
|
+
}
|
|
29604
|
+
function ensureWorker() {
|
|
29605
|
+
if (worker) return worker;
|
|
29606
|
+
const url2 = opts.workerUrl ?? resolveWorkerUrl();
|
|
29607
|
+
if (!url2) {
|
|
29608
|
+
return {
|
|
29609
|
+
error: "the scan worker script was not found next to this bundle"
|
|
29610
|
+
};
|
|
29611
|
+
}
|
|
29612
|
+
let started;
|
|
29613
|
+
try {
|
|
29614
|
+
started = new Worker(url2, { workerData: data });
|
|
29615
|
+
} catch (error51) {
|
|
29616
|
+
return { error: `could not start the scan worker: ${messageOf(error51)}` };
|
|
29617
|
+
}
|
|
29618
|
+
opts.onWorkerStart?.(started.threadId);
|
|
29619
|
+
started.on("message", (message) => {
|
|
29620
|
+
if (worker !== started) return;
|
|
29621
|
+
if (message.kind === "ready") {
|
|
29622
|
+
readyWorker = started;
|
|
29623
|
+
if (pending?.worker === started) beginDeadline(pending);
|
|
29624
|
+
return;
|
|
29625
|
+
}
|
|
29626
|
+
if (message.kind === "progress") {
|
|
29627
|
+
if (pending?.worker === started) {
|
|
29628
|
+
pending.progressIndex = message.index;
|
|
29629
|
+
pending.progressAt = performance.now();
|
|
29630
|
+
}
|
|
29631
|
+
return;
|
|
29632
|
+
}
|
|
29633
|
+
if (pending?.id !== message.id) return;
|
|
29634
|
+
if (message.kind === "failed") {
|
|
29635
|
+
failPending({
|
|
29636
|
+
status: "unavailable",
|
|
29637
|
+
reason: `the scan worker failed: ${message.message}`
|
|
29638
|
+
});
|
|
29639
|
+
return;
|
|
29640
|
+
}
|
|
29641
|
+
const job = take();
|
|
29642
|
+
if (job && !job.reply(message)) {
|
|
29643
|
+
job.fail({ status: "unavailable", reason: "the scan worker answered the wrong job" });
|
|
29644
|
+
}
|
|
29645
|
+
});
|
|
29646
|
+
started.on("error", (error51) => {
|
|
29647
|
+
if (worker !== started) return;
|
|
29648
|
+
broken = messageOf(error51);
|
|
29649
|
+
worker = void 0;
|
|
29650
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29651
|
+
failPending({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29652
|
+
});
|
|
29653
|
+
started.on("exit", () => {
|
|
29654
|
+
if (worker !== started) return;
|
|
29655
|
+
broken ??= "the scan worker exited before answering";
|
|
29656
|
+
worker = void 0;
|
|
29657
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29658
|
+
failPending({ status: "unavailable", reason: "the scan worker exited before answering" });
|
|
29659
|
+
});
|
|
29660
|
+
started.unref();
|
|
29661
|
+
worker = started;
|
|
29662
|
+
return started;
|
|
29663
|
+
}
|
|
29664
|
+
function beginDeadline(job) {
|
|
29665
|
+
if (job.startupTimer !== void 0) {
|
|
29666
|
+
clearTimeout(job.startupTimer);
|
|
29667
|
+
job.startupTimer = void 0;
|
|
29668
|
+
}
|
|
29669
|
+
if (job.timer !== void 0) return;
|
|
29670
|
+
job.startedAt = performance.now();
|
|
29671
|
+
job.progressAt = job.startedAt;
|
|
29672
|
+
job.timer = setTimeout(() => {
|
|
29673
|
+
onDeadline(job);
|
|
29674
|
+
}, job.budgetMs);
|
|
29675
|
+
}
|
|
29676
|
+
function runOne(spec, fail) {
|
|
29677
|
+
if (closed) {
|
|
29678
|
+
fail({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29679
|
+
return;
|
|
29680
|
+
}
|
|
29681
|
+
if (broken !== void 0) {
|
|
29682
|
+
fail({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29683
|
+
return;
|
|
29684
|
+
}
|
|
29685
|
+
const started = ensureWorker();
|
|
29686
|
+
if (!(started instanceof Worker)) {
|
|
29687
|
+
broken = started.error;
|
|
29688
|
+
fail({ status: "unavailable", reason: started.error });
|
|
29689
|
+
return;
|
|
29690
|
+
}
|
|
29691
|
+
const id = nextJobId++;
|
|
29692
|
+
const now = performance.now();
|
|
29693
|
+
const job = {
|
|
29694
|
+
id,
|
|
29695
|
+
worker: started,
|
|
29696
|
+
budgetMs: spec.budgetMs,
|
|
29697
|
+
startedAt: now,
|
|
29698
|
+
progressIndex: -1,
|
|
29699
|
+
progressAt: now,
|
|
29700
|
+
startupTimer: void 0,
|
|
29701
|
+
timer: void 0,
|
|
29702
|
+
reply: spec.reply,
|
|
29703
|
+
fail
|
|
29704
|
+
};
|
|
29705
|
+
pending = job;
|
|
29706
|
+
started.ref();
|
|
29707
|
+
if (readyWorker === started) {
|
|
29708
|
+
beginDeadline(job);
|
|
29709
|
+
} else {
|
|
29710
|
+
job.startupTimer = setTimeout(() => {
|
|
29711
|
+
if (pending !== job) return;
|
|
29712
|
+
kill(job.worker);
|
|
29713
|
+
failPending({
|
|
29714
|
+
status: "unavailable",
|
|
29715
|
+
reason: `the scan worker did not start within ${String(startBudgetMs)}ms`
|
|
29716
|
+
});
|
|
29717
|
+
}, startBudgetMs);
|
|
29718
|
+
}
|
|
29719
|
+
try {
|
|
29720
|
+
started.postMessage(spec.build(id));
|
|
29721
|
+
} catch (error51) {
|
|
29722
|
+
failPending({
|
|
29723
|
+
// The thread went away between the ref and the post.
|
|
29724
|
+
status: "unavailable",
|
|
29725
|
+
reason: `could not reach the scan worker: ${messageOf(error51)}`
|
|
29726
|
+
});
|
|
29727
|
+
}
|
|
29728
|
+
}
|
|
29729
|
+
function enqueue(spec) {
|
|
29730
|
+
const next = chain.then(
|
|
29731
|
+
() => new Promise((resolve) => {
|
|
29732
|
+
spec(resolve);
|
|
29733
|
+
})
|
|
29734
|
+
);
|
|
29735
|
+
chain = next.then(
|
|
29736
|
+
() => void 0,
|
|
29737
|
+
() => void 0
|
|
29738
|
+
);
|
|
29739
|
+
return next;
|
|
29740
|
+
}
|
|
29741
|
+
return {
|
|
29742
|
+
scan(text, context, scanOpts) {
|
|
29743
|
+
return enqueue((resolve) => {
|
|
29744
|
+
runOne(
|
|
29745
|
+
{
|
|
29746
|
+
budgetMs,
|
|
29747
|
+
build: (id) => ({
|
|
29748
|
+
kind: "scan",
|
|
29749
|
+
id,
|
|
29750
|
+
text,
|
|
29751
|
+
filePath: context?.filePath,
|
|
29752
|
+
attribute: scanOpts?.attribute === true
|
|
29753
|
+
}),
|
|
29754
|
+
reply: (message) => {
|
|
29755
|
+
if (message.kind !== "result") return false;
|
|
29756
|
+
resolve({ status: "ok", findings: message.findings });
|
|
29757
|
+
return true;
|
|
29758
|
+
}
|
|
29759
|
+
},
|
|
29760
|
+
resolve
|
|
29761
|
+
);
|
|
29762
|
+
});
|
|
29763
|
+
},
|
|
29764
|
+
probe(rule) {
|
|
29765
|
+
return enqueue((resolve) => {
|
|
29766
|
+
runOne(
|
|
29767
|
+
{
|
|
29768
|
+
budgetMs: probeBudgetMs,
|
|
29769
|
+
build: (id) => ({ kind: "probe", id, rule }),
|
|
29770
|
+
reply: (message) => {
|
|
29771
|
+
if (message.kind !== "probed") return false;
|
|
29772
|
+
resolve({ status: "ok", safe: message.safe, worstMs: message.worstMs });
|
|
29773
|
+
return true;
|
|
29774
|
+
}
|
|
29775
|
+
},
|
|
29776
|
+
resolve
|
|
29777
|
+
);
|
|
29778
|
+
});
|
|
29779
|
+
},
|
|
29780
|
+
async close() {
|
|
29781
|
+
closed = true;
|
|
29782
|
+
const live = worker;
|
|
29783
|
+
worker = void 0;
|
|
29784
|
+
readyWorker = void 0;
|
|
29785
|
+
failPending({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29786
|
+
if (live) kill(live);
|
|
29787
|
+
await Promise.all([...terminating]);
|
|
29788
|
+
}
|
|
29789
|
+
};
|
|
29790
|
+
}
|
|
28469
29791
|
|
|
28470
29792
|
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
28471
29793
|
var PASS_BUDGET_MS = 2e3;
|
|
29794
|
+
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
28472
29795
|
function ruleProbeKey(rule) {
|
|
28473
29796
|
if (rule.matcher.type !== "regex") return void 0;
|
|
28474
29797
|
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
28475
29798
|
}
|
|
28476
|
-
function
|
|
28477
|
-
const
|
|
29799
|
+
function warn(rule, verb, detail, recoverable) {
|
|
29800
|
+
const hint = recoverable ? ` (${UNQUARANTINE_HINT})` : "";
|
|
29801
|
+
process.stderr.write(`[aka] ${verb} rule "${rule.id}": ${detail}${hint}
|
|
29802
|
+
`);
|
|
29803
|
+
}
|
|
29804
|
+
function warnQuarantined(rule, worstMs, cached2) {
|
|
29805
|
+
warn(
|
|
29806
|
+
rule,
|
|
29807
|
+
"quarantined",
|
|
29808
|
+
Number.isFinite(worstMs) ? `regex matcher exceeded the ReDoS timing budget (${worstMs.toFixed(1)}ms); excluded from this scan.` : "the timing battery failed while measuring its regex matcher; excluded from this scan.",
|
|
29809
|
+
cached2
|
|
29810
|
+
);
|
|
29811
|
+
}
|
|
29812
|
+
function warnUnmeasured(rule) {
|
|
29813
|
+
warn(
|
|
29814
|
+
rule,
|
|
29815
|
+
"skipped",
|
|
29816
|
+
"the timing pre-flight ran out of time before this rule could be measured; excluded for the rest of this run, and measured again next time.",
|
|
29817
|
+
false
|
|
29818
|
+
);
|
|
29819
|
+
}
|
|
29820
|
+
function warnUnmeasurable(reason, count) {
|
|
28478
29821
|
process.stderr.write(
|
|
28479
|
-
`[aka]
|
|
29822
|
+
`[aka] ${String(count)} pulled/custom-pack rule(s) could not be time-checked: ${reason}. That is a problem with this install, not with the rules \u2014 until it is fixed they are excluded from every scan on this machine. Nothing was quarantined, so reinstalling AKA brings them straight back.
|
|
28480
29823
|
`
|
|
28481
29824
|
);
|
|
28482
29825
|
}
|
|
29826
|
+
async function quarantineRule(gateway, rule, worstMs, detail) {
|
|
29827
|
+
const key = ruleProbeKey(rule);
|
|
29828
|
+
let cached2 = false;
|
|
29829
|
+
if (key !== void 0) {
|
|
29830
|
+
try {
|
|
29831
|
+
await gateway.setRuleProbeVerdict(key, "quarantined", worstMs);
|
|
29832
|
+
cached2 = true;
|
|
29833
|
+
} catch {
|
|
29834
|
+
}
|
|
29835
|
+
}
|
|
29836
|
+
warn(rule, "quarantined", detail, cached2);
|
|
29837
|
+
}
|
|
28483
29838
|
async function filterUnsafeRules(rules, gateway, opts) {
|
|
28484
29839
|
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
29840
|
+
const prober = opts?.prober;
|
|
28485
29841
|
const passStart = performance.now();
|
|
28486
29842
|
const safe = [];
|
|
28487
|
-
|
|
28488
|
-
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
29843
|
+
const unmeasurable = /* @__PURE__ */ new Map();
|
|
29844
|
+
try {
|
|
29845
|
+
for (const rule of rules) {
|
|
29846
|
+
const key = ruleProbeKey(rule);
|
|
29847
|
+
if (key === void 0) {
|
|
29848
|
+
safe.push(rule);
|
|
29849
|
+
continue;
|
|
29850
|
+
}
|
|
29851
|
+
let cached2;
|
|
29852
|
+
try {
|
|
29853
|
+
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
29854
|
+
} catch {
|
|
29855
|
+
cached2 = void 0;
|
|
29856
|
+
}
|
|
29857
|
+
if (cached2) {
|
|
29858
|
+
if (cached2.verdict === "safe") safe.push(rule);
|
|
29859
|
+
else warnQuarantined(rule, cached2.worstProbeMs, true);
|
|
29860
|
+
continue;
|
|
29861
|
+
}
|
|
29862
|
+
if (performance.now() - passStart >= passBudgetMs) {
|
|
29863
|
+
warnUnmeasured(rule);
|
|
29864
|
+
continue;
|
|
29865
|
+
}
|
|
29866
|
+
let isSafe;
|
|
29867
|
+
let worstMs;
|
|
29868
|
+
if (prober) {
|
|
29869
|
+
const outcome = await prober.probe(rule);
|
|
29870
|
+
if (outcome.status === "unavailable") {
|
|
29871
|
+
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29872
|
+
continue;
|
|
29873
|
+
}
|
|
29874
|
+
isSafe = outcome.status === "ok" ? outcome.safe : false;
|
|
29875
|
+
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
29876
|
+
} else {
|
|
29877
|
+
try {
|
|
29878
|
+
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
29879
|
+
} catch {
|
|
29880
|
+
isSafe = false;
|
|
29881
|
+
worstMs = Number.POSITIVE_INFINITY;
|
|
29882
|
+
}
|
|
29883
|
+
}
|
|
29884
|
+
let persisted = false;
|
|
29885
|
+
try {
|
|
29886
|
+
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
29887
|
+
persisted = true;
|
|
29888
|
+
} catch {
|
|
29889
|
+
}
|
|
29890
|
+
if (isSafe) safe.push(rule);
|
|
29891
|
+
else warnQuarantined(rule, worstMs, persisted);
|
|
28492
29892
|
}
|
|
28493
|
-
|
|
29893
|
+
} finally {
|
|
29894
|
+
for (const [reason, count] of unmeasurable) warnUnmeasurable(reason, count);
|
|
29895
|
+
}
|
|
29896
|
+
return safe;
|
|
29897
|
+
}
|
|
29898
|
+
|
|
29899
|
+
// ../../packages/plugin-sdk/src/guarded-scan.ts
|
|
29900
|
+
var DEFAULT_DEGRADE_SCOPE = "the rest of this process";
|
|
29901
|
+
function warnDegraded(scope, dropped, detail) {
|
|
29902
|
+
process.stderr.write(
|
|
29903
|
+
`[aka] isolated scanning is off for ${scope}: ${detail}. ${String(dropped)} pulled/custom-pack rule(s) are excluded; the built-in packs still run.
|
|
29904
|
+
`
|
|
29905
|
+
);
|
|
29906
|
+
}
|
|
29907
|
+
function createGuardedScanner(partition, gateway, opts) {
|
|
29908
|
+
const degradeScope = opts?.degradeScope ?? DEFAULT_DEGRADE_SCOPE;
|
|
29909
|
+
const verified = partition.verified;
|
|
29910
|
+
let unverified = partition.unverified;
|
|
29911
|
+
let isolated = unverified.length > 0 ? createIsolatedScanner({ verified, unverified }, opts) : void 0;
|
|
29912
|
+
let retired = false;
|
|
29913
|
+
function inProcess(text, context) {
|
|
29914
|
+
return scan(text, verified, context);
|
|
29915
|
+
}
|
|
29916
|
+
async function retire() {
|
|
29917
|
+
const live = isolated;
|
|
29918
|
+
isolated = void 0;
|
|
29919
|
+
unverified = [];
|
|
29920
|
+
if (live) await live.close();
|
|
29921
|
+
}
|
|
29922
|
+
async function degrade() {
|
|
29923
|
+
retired = true;
|
|
29924
|
+
await retire();
|
|
29925
|
+
}
|
|
29926
|
+
async function attempt(active, text, context, attribute) {
|
|
28494
29927
|
try {
|
|
28495
|
-
|
|
28496
|
-
} catch {
|
|
28497
|
-
|
|
28498
|
-
|
|
28499
|
-
|
|
28500
|
-
|
|
28501
|
-
else warnQuarantined(rule, cached2.worstProbeMs);
|
|
28502
|
-
continue;
|
|
28503
|
-
}
|
|
28504
|
-
if (performance.now() - passStart >= passBudgetMs) {
|
|
28505
|
-
warnQuarantined(rule, void 0);
|
|
28506
|
-
continue;
|
|
29928
|
+
return await active.scan(text, context, { attribute });
|
|
29929
|
+
} catch (error51) {
|
|
29930
|
+
return {
|
|
29931
|
+
status: "unavailable",
|
|
29932
|
+
reason: error51 instanceof Error ? error51.message : "the scan worker failed unexpectedly"
|
|
29933
|
+
};
|
|
28507
29934
|
}
|
|
28508
|
-
|
|
28509
|
-
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
29935
|
+
}
|
|
29936
|
+
async function guardedScan(text, context) {
|
|
29937
|
+
const active = isolated;
|
|
29938
|
+
if (!active) return inProcess(text, context);
|
|
29939
|
+
let outcome = await attempt(active, text, context, false);
|
|
29940
|
+
if (outcome.status === "ok") return outcome.findings;
|
|
29941
|
+
if (outcome.status === "timeout") outcome = await attempt(active, text, context, true);
|
|
29942
|
+
const dropped = unverified.length;
|
|
29943
|
+
if (outcome.status === "ok") {
|
|
29944
|
+
warnDegraded(
|
|
29945
|
+
degradeScope,
|
|
29946
|
+
dropped,
|
|
29947
|
+
"a scan overran its bound once and no rule could be held responsible"
|
|
29948
|
+
);
|
|
29949
|
+
const findings = outcome.findings;
|
|
29950
|
+
await degrade();
|
|
29951
|
+
return findings;
|
|
29952
|
+
}
|
|
29953
|
+
if (outcome.status === "timeout") {
|
|
29954
|
+
const culprit = outcome.culpritIndex === void 0 ? void 0 : unverified[outcome.culpritIndex];
|
|
29955
|
+
if (culprit) {
|
|
29956
|
+
await quarantineRule(
|
|
29957
|
+
gateway,
|
|
29958
|
+
culprit,
|
|
29959
|
+
outcome.elapsedMs,
|
|
29960
|
+
`it did not finish within the ${outcome.elapsedMs.toFixed(0)}ms isolated-scan bound and was terminated; excluded from every later scan.`
|
|
29961
|
+
);
|
|
29962
|
+
}
|
|
29963
|
+
warnDegraded(
|
|
29964
|
+
degradeScope,
|
|
29965
|
+
dropped,
|
|
29966
|
+
culprit ? `rule "${culprit.id}" had to be terminated mid-scan` : `a scan was terminated at the ${outcome.elapsedMs.toFixed(0)}ms bound and no single rule could be held responsible, so nothing was quarantined and the next process will try these rules again`
|
|
29967
|
+
);
|
|
29968
|
+
} else {
|
|
29969
|
+
warnDegraded(degradeScope, dropped, outcome.reason);
|
|
28515
29970
|
}
|
|
28516
|
-
await
|
|
28517
|
-
|
|
28518
|
-
else warnQuarantined(rule, worstMs);
|
|
29971
|
+
await degrade();
|
|
29972
|
+
return inProcess(text, context);
|
|
28519
29973
|
}
|
|
28520
|
-
return
|
|
29974
|
+
return {
|
|
29975
|
+
scan: guardedScan,
|
|
29976
|
+
degraded: () => retired,
|
|
29977
|
+
async close() {
|
|
29978
|
+
await retire();
|
|
29979
|
+
}
|
|
29980
|
+
};
|
|
28521
29981
|
}
|
|
28522
29982
|
|
|
29983
|
+
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
29984
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
29985
|
+
|
|
29986
|
+
// ../../packages/plugin-sdk/src/nudge.ts
|
|
29987
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
29988
|
+
import { join as join11 } from "path";
|
|
29989
|
+
|
|
29990
|
+
// ../../packages/plugin-sdk/src/paths.ts
|
|
29991
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
29992
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
29993
|
+
|
|
29994
|
+
// ../../packages/plugin-sdk/src/project-files.ts
|
|
29995
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
29996
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
29997
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
29998
|
+
|
|
29999
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30000
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
30001
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30002
|
+
return v;
|
|
30003
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30004
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
30005
|
+
if (typeof v !== "string") return false;
|
|
30006
|
+
const normalized = v.trim().toLowerCase();
|
|
30007
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
30008
|
+
}, external_exports.boolean()).catch(false);
|
|
30009
|
+
var antigravityProviderEnvShape = {
|
|
30010
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
30011
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
30012
|
+
};
|
|
30013
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
30014
|
+
|
|
30015
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
30016
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
30017
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30018
|
+
return v;
|
|
30019
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30020
|
+
var codexProviderEnvShape = {
|
|
30021
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
30022
|
+
};
|
|
30023
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
30024
|
+
|
|
28523
30025
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28524
|
-
import { randomUUID as
|
|
30026
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28525
30027
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
28526
30028
|
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
28527
30029
|
function entryIsActive(entry, now) {
|
|
@@ -28546,6 +30048,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28546
30048
|
const dataDir2 = opts?.dataDir;
|
|
28547
30049
|
let policies = [];
|
|
28548
30050
|
let rules = [];
|
|
30051
|
+
let scanner;
|
|
28549
30052
|
let bundleExceptions = [];
|
|
28550
30053
|
let initialized = false;
|
|
28551
30054
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
@@ -28571,8 +30074,24 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28571
30074
|
return key !== void 0 && bundledProbeKeys.has(key);
|
|
28572
30075
|
});
|
|
28573
30076
|
const needsGate = incoming.filter((rule) => !ciVerified.includes(rule));
|
|
28574
|
-
|
|
28575
|
-
|
|
30077
|
+
let prober;
|
|
30078
|
+
const gated = await filterUnsafeRules(needsGate, gateway, {
|
|
30079
|
+
prober: {
|
|
30080
|
+
probe: (rule) => {
|
|
30081
|
+
prober ??= createIsolatedScanner({ verified: [], unverified: [] }, opts?.scanIsolation);
|
|
30082
|
+
return prober.probe(rule);
|
|
30083
|
+
}
|
|
30084
|
+
}
|
|
30085
|
+
});
|
|
30086
|
+
await prober?.close();
|
|
30087
|
+
const verified = bundle.rulesComplete ? [...ciVerified] : [...getLoadedRules(), ...ciVerified];
|
|
30088
|
+
const unverified = [];
|
|
30089
|
+
for (const rule of gated) {
|
|
30090
|
+
if (rule.matcher.type === "regex") unverified.push(rule);
|
|
30091
|
+
else verified.push(rule);
|
|
30092
|
+
}
|
|
30093
|
+
rules = [...verified, ...unverified];
|
|
30094
|
+
scanner = createGuardedScanner({ verified, unverified }, gateway, opts?.scanIsolation);
|
|
28576
30095
|
bundleExceptions = bundle.exceptions ?? [];
|
|
28577
30096
|
initialized = true;
|
|
28578
30097
|
}
|
|
@@ -28712,7 +30231,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28712
30231
|
const pair = `${finding.ruleId}:${fp}`;
|
|
28713
30232
|
if (seen.has(pair)) continue;
|
|
28714
30233
|
seen.add(pair);
|
|
28715
|
-
const reference =
|
|
30234
|
+
const reference = randomUUID14().replaceAll("-", "").slice(0, 6);
|
|
28716
30235
|
const maskedValue = maskMatch(finding.rawMatch);
|
|
28717
30236
|
try {
|
|
28718
30237
|
await gateway.recordBlockedDetection({
|
|
@@ -28736,8 +30255,10 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28736
30255
|
async function evaluate(text, context, ctx) {
|
|
28737
30256
|
try {
|
|
28738
30257
|
await ensureInitialized();
|
|
30258
|
+
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
28739
30259
|
const shielded = shieldPointers(text);
|
|
28740
|
-
const
|
|
30260
|
+
const matched = await scanner.scan(shielded.text, context);
|
|
30261
|
+
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
28741
30262
|
const fpCache = /* @__PURE__ */ new Map();
|
|
28742
30263
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
28743
30264
|
const decision = decide(findings, text, excepted);
|
|
@@ -28794,7 +30315,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28794
30315
|
valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
|
|
28795
30316
|
}) : void 0;
|
|
28796
30317
|
return {
|
|
28797
|
-
id:
|
|
30318
|
+
id: randomUUID14(),
|
|
28798
30319
|
eventId: event.id,
|
|
28799
30320
|
ruleId: match.ruleId,
|
|
28800
30321
|
category: match.category,
|
|
@@ -28820,21 +30341,28 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28820
30341
|
const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
28821
30342
|
return contentHashOf(JSON.stringify(sorted));
|
|
28822
30343
|
} catch {
|
|
28823
|
-
return `unresolved-${
|
|
30344
|
+
return `unresolved-${randomUUID14()}`;
|
|
28824
30345
|
}
|
|
28825
30346
|
}
|
|
30347
|
+
function scanIsolationDegraded() {
|
|
30348
|
+
return scanner?.degraded() ?? false;
|
|
30349
|
+
}
|
|
28826
30350
|
async function close() {
|
|
30351
|
+
try {
|
|
30352
|
+
await scanner?.close();
|
|
30353
|
+
} catch {
|
|
30354
|
+
}
|
|
28827
30355
|
await gateway.close();
|
|
28828
30356
|
}
|
|
28829
|
-
return { processText, capture, rulesetFingerprint, close };
|
|
30357
|
+
return { processText, capture, rulesetFingerprint, scanIsolationDegraded, close };
|
|
28830
30358
|
}
|
|
28831
30359
|
|
|
28832
30360
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28833
30361
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28834
30362
|
|
|
28835
30363
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28836
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
28837
|
-
import { join as
|
|
30364
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
30365
|
+
import { join as join13 } from "path";
|
|
28838
30366
|
|
|
28839
30367
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
28840
30368
|
function redactedPlaceholder(category) {
|
|
@@ -29076,7 +30604,6 @@ function createVaultGlue(options) {
|
|
|
29076
30604
|
const vault = new SecretVault({
|
|
29077
30605
|
repo: db.secretVault,
|
|
29078
30606
|
keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
|
|
29079
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
29080
30607
|
// Read live so a revocation applies to the very next call, not the next
|
|
29081
30608
|
// process.
|
|
29082
30609
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
|
|
@@ -29097,8 +30624,10 @@ function createVaultGlue(options) {
|
|
|
29097
30624
|
return decision.allow;
|
|
29098
30625
|
}
|
|
29099
30626
|
});
|
|
30627
|
+
let fingerprintKey;
|
|
30628
|
+
const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
|
|
29100
30629
|
const vaultWithSightings = {
|
|
29101
|
-
tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
|
|
30630
|
+
tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
|
|
29102
30631
|
detokenize: (token, opts) => vault.detokenize(token, opts),
|
|
29103
30632
|
describePointer: (token) => vault.describePointer(token),
|
|
29104
30633
|
resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
|
|
@@ -29132,7 +30661,7 @@ var UNOPENABLE_VAULT = {
|
|
|
29132
30661
|
};
|
|
29133
30662
|
|
|
29134
30663
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
29135
|
-
import { randomUUID as
|
|
30664
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
29136
30665
|
|
|
29137
30666
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
29138
30667
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -29294,7 +30823,7 @@ var StandaloneDataGateway = class {
|
|
|
29294
30823
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
29295
30824
|
const installed = this.installedScanRules();
|
|
29296
30825
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
29297
|
-
id:
|
|
30826
|
+
id: randomUUID15(),
|
|
29298
30827
|
scope: "global",
|
|
29299
30828
|
target: { ruleId },
|
|
29300
30829
|
action,
|
|
@@ -29447,22 +30976,22 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
29447
30976
|
}
|
|
29448
30977
|
|
|
29449
30978
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
29450
|
-
import { randomUUID as
|
|
30979
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
29451
30980
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
29452
30981
|
|
|
29453
30982
|
// src/protocol/marker.ts
|
|
29454
30983
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
29455
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
29456
|
-
import { join as
|
|
30984
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
30985
|
+
import { join as join14 } from "path";
|
|
29457
30986
|
var MARKER_FILE = "protocol-marker";
|
|
29458
30987
|
function mintMarker() {
|
|
29459
30988
|
return randomBytes4(8).toString("hex");
|
|
29460
30989
|
}
|
|
29461
30990
|
function sessionProtocolMarker(dataDir2, sessionId) {
|
|
29462
30991
|
if (!sessionId) return mintMarker();
|
|
29463
|
-
const path =
|
|
30992
|
+
const path = join14(dataDir2, MARKER_FILE);
|
|
29464
30993
|
try {
|
|
29465
|
-
const stored = JSON.parse(
|
|
30994
|
+
const stored = JSON.parse(readFileSync9(path, "utf8"));
|
|
29466
30995
|
if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
|
|
29467
30996
|
return stored.marker;
|
|
29468
30997
|
}
|
|
@@ -29471,8 +31000,8 @@ function sessionProtocolMarker(dataDir2, sessionId) {
|
|
|
29471
31000
|
const marker = mintMarker();
|
|
29472
31001
|
try {
|
|
29473
31002
|
mkdirSync5(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
|
|
29474
|
-
const tmp =
|
|
29475
|
-
|
|
31003
|
+
const tmp = join14(dataDir2, `${MARKER_FILE}.tmp`);
|
|
31004
|
+
writeFileSync7(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
|
|
29476
31005
|
renameSync5(tmp, path);
|
|
29477
31006
|
} catch {
|
|
29478
31007
|
}
|