@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/reconcile.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
|
function harnessFromTool(tool) {
|
|
15489
15624
|
return TOOL_TO_HARNESS[tool] ?? tool;
|
|
@@ -15944,7 +16079,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15944
16079
|
// ../../packages/schema/src/zod/event.ts
|
|
15945
16080
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15946
16081
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15947
|
-
var SourceTool = external_exports.enum([
|
|
16082
|
+
var SourceTool = external_exports.enum([
|
|
16083
|
+
"claude-code",
|
|
16084
|
+
"claude-desktop",
|
|
16085
|
+
"cursor",
|
|
16086
|
+
"chatgpt",
|
|
16087
|
+
"claude-ai",
|
|
16088
|
+
"github-copilot",
|
|
16089
|
+
"codex",
|
|
16090
|
+
"antigravity",
|
|
16091
|
+
"cli",
|
|
16092
|
+
"unknown"
|
|
16093
|
+
]).meta({ id: "SourceTool" });
|
|
15948
16094
|
var EventMetadata = external_exports.object({
|
|
15949
16095
|
sessionId: external_exports.string().optional(),
|
|
15950
16096
|
repo: external_exports.string().optional(),
|
|
@@ -16015,7 +16161,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16015
16161
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16016
16162
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16017
16163
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16018
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16164
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16019
16165
|
var AccessCounts = external_exports.object({
|
|
16020
16166
|
open: external_exports.number().int().nonnegative(),
|
|
16021
16167
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16284,6 +16430,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16284
16430
|
useCount: true,
|
|
16285
16431
|
conditions: true
|
|
16286
16432
|
});
|
|
16433
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16287
16434
|
|
|
16288
16435
|
// ../../packages/schema/src/zod/rule.ts
|
|
16289
16436
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17094,6 +17241,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17094
17241
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17095
17242
|
}).meta({ id: "EgressWriteSummary" });
|
|
17096
17243
|
|
|
17244
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17245
|
+
var confirmation = external_exports.string().optional();
|
|
17246
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17247
|
+
reference: external_exports.string(),
|
|
17248
|
+
scope: external_exports.string(),
|
|
17249
|
+
reason: external_exports.string(),
|
|
17250
|
+
confirmation
|
|
17251
|
+
});
|
|
17252
|
+
var AddExceptionInput = external_exports.object({
|
|
17253
|
+
ruleId: external_exports.string(),
|
|
17254
|
+
value: external_exports.string(),
|
|
17255
|
+
scope: external_exports.string(),
|
|
17256
|
+
reason: external_exports.string(),
|
|
17257
|
+
confirmation
|
|
17258
|
+
});
|
|
17259
|
+
var GrantRevealInput = external_exports.object({
|
|
17260
|
+
pointer: external_exports.string(),
|
|
17261
|
+
scope: external_exports.string(),
|
|
17262
|
+
justification: external_exports.string(),
|
|
17263
|
+
confirmation
|
|
17264
|
+
});
|
|
17265
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17266
|
+
id: external_exports.string(),
|
|
17267
|
+
reason: external_exports.string()
|
|
17268
|
+
});
|
|
17269
|
+
var RotateKeyInput = external_exports.object({
|
|
17270
|
+
confirmation: external_exports.string()
|
|
17271
|
+
});
|
|
17272
|
+
|
|
17097
17273
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17098
17274
|
function toApiAction(dbVal) {
|
|
17099
17275
|
const map2 = {
|
|
@@ -17149,6 +17325,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17149
17325
|
repo: r.repo,
|
|
17150
17326
|
file: r.file,
|
|
17151
17327
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17328
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17329
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17152
17330
|
action: toApiAction(effectiveDbAction),
|
|
17153
17331
|
detectedAt: r.occurredAt,
|
|
17154
17332
|
confidence: r.confidence,
|
|
@@ -17280,14 +17458,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17280
17458
|
}
|
|
17281
17459
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17282
17460
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17461
|
+
function compareFindingGroupOrder(a, b) {
|
|
17462
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17463
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17464
|
+
const severityDiff = rankA - rankB;
|
|
17465
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17466
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17467
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17468
|
+
return a.id.localeCompare(b.id);
|
|
17469
|
+
}
|
|
17283
17470
|
function sortFindingGroups(groups) {
|
|
17284
|
-
return [...groups].sort(
|
|
17285
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17286
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17287
|
-
const severityDiff = rankA - rankB;
|
|
17288
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17289
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17290
|
-
});
|
|
17471
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17291
17472
|
}
|
|
17292
17473
|
function computeFindingFacets(allGroups, opts) {
|
|
17293
17474
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17343,15 +17524,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17343
17524
|
for (const g of forStatus) {
|
|
17344
17525
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17345
17526
|
}
|
|
17346
|
-
const
|
|
17527
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17528
|
+
return {
|
|
17529
|
+
severity: toItems2(severityMap),
|
|
17530
|
+
provider: toItems2(providerMap),
|
|
17531
|
+
action: toItems2(actionMap),
|
|
17532
|
+
subtype: toItems2(subtypeMap),
|
|
17533
|
+
status: toItems2(statusMap)
|
|
17534
|
+
};
|
|
17535
|
+
}
|
|
17536
|
+
|
|
17537
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17538
|
+
function rowHaystack(row) {
|
|
17539
|
+
return [
|
|
17540
|
+
row.ruleId,
|
|
17541
|
+
row.category,
|
|
17542
|
+
row.maskedMatch,
|
|
17543
|
+
row.repo,
|
|
17544
|
+
row.file,
|
|
17545
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17546
|
+
row.id
|
|
17547
|
+
].join(" ").toLowerCase();
|
|
17548
|
+
}
|
|
17549
|
+
function matchesDimension(row, opts, dimension) {
|
|
17550
|
+
switch (dimension) {
|
|
17551
|
+
case "severity":
|
|
17552
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17553
|
+
case "subtype":
|
|
17554
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17555
|
+
case "providers":
|
|
17556
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17557
|
+
case "actions":
|
|
17558
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17559
|
+
case "statuses":
|
|
17560
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17561
|
+
case "tools":
|
|
17562
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17563
|
+
case "repo":
|
|
17564
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17565
|
+
case "file":
|
|
17566
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17567
|
+
case "q":
|
|
17568
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17569
|
+
}
|
|
17570
|
+
}
|
|
17571
|
+
var DIMENSIONS = [
|
|
17572
|
+
"severity",
|
|
17573
|
+
"subtype",
|
|
17574
|
+
"providers",
|
|
17575
|
+
"actions",
|
|
17576
|
+
"statuses",
|
|
17577
|
+
"tools",
|
|
17578
|
+
"repo",
|
|
17579
|
+
"file",
|
|
17580
|
+
"q"
|
|
17581
|
+
];
|
|
17582
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17583
|
+
for (const dimension of DIMENSIONS) {
|
|
17584
|
+
if (dimension === except) continue;
|
|
17585
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17586
|
+
}
|
|
17587
|
+
return true;
|
|
17588
|
+
}
|
|
17589
|
+
function toItems(counts) {
|
|
17590
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17591
|
+
}
|
|
17592
|
+
function bump(counts, value) {
|
|
17593
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17594
|
+
}
|
|
17595
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17596
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17597
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17598
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17599
|
+
const action = /* @__PURE__ */ new Map();
|
|
17600
|
+
const status = /* @__PURE__ */ new Map();
|
|
17601
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17347
17602
|
return {
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17603
|
+
add(row) {
|
|
17604
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17605
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17606
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17607
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17608
|
+
}
|
|
17609
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17610
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17611
|
+
bump(status, row.status);
|
|
17612
|
+
}
|
|
17613
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17614
|
+
bump(tool, row.toolName);
|
|
17615
|
+
}
|
|
17616
|
+
},
|
|
17617
|
+
facets: () => ({
|
|
17618
|
+
severity: toItems(severity),
|
|
17619
|
+
subtype: toItems(subtype),
|
|
17620
|
+
provider: toItems(provider),
|
|
17621
|
+
action: toItems(action),
|
|
17622
|
+
status: toItems(status),
|
|
17623
|
+
tool: toItems(tool)
|
|
17624
|
+
})
|
|
17625
|
+
};
|
|
17626
|
+
}
|
|
17627
|
+
function toInstanceDetail(row) {
|
|
17628
|
+
const category = toApiCategory(row.category);
|
|
17629
|
+
return {
|
|
17630
|
+
id: row.id,
|
|
17631
|
+
provider: toApiProvider(row.sourceTool),
|
|
17632
|
+
repo: row.repo,
|
|
17633
|
+
file: row.file,
|
|
17634
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17635
|
+
eventId: row.eventId,
|
|
17636
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17637
|
+
action: toApiAction(row.actionTaken),
|
|
17638
|
+
detectedAt: row.occurredAt,
|
|
17639
|
+
confidence: row.confidence,
|
|
17640
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17641
|
+
groupId: row.ruleId,
|
|
17642
|
+
category,
|
|
17643
|
+
subtype: row.ruleId,
|
|
17644
|
+
severity: row.severity,
|
|
17645
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17646
|
+
detection: { id: row.ruleId, name: null },
|
|
17647
|
+
policy: { id: `category:${category}`, name: category }
|
|
17648
|
+
};
|
|
17649
|
+
}
|
|
17650
|
+
var SEVERITY_ORDER2 = {
|
|
17651
|
+
critical: 0,
|
|
17652
|
+
high: 1,
|
|
17653
|
+
medium: 2,
|
|
17654
|
+
low: 3
|
|
17655
|
+
};
|
|
17656
|
+
function newLocationAccumulator() {
|
|
17657
|
+
return {
|
|
17658
|
+
instanceCount: 0,
|
|
17659
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17660
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17661
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17662
|
+
maxSeverity: "low",
|
|
17663
|
+
latestDetectedAt: "",
|
|
17664
|
+
statuses: [],
|
|
17665
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17353
17666
|
};
|
|
17354
17667
|
}
|
|
17668
|
+
function addToLocation(acc, row) {
|
|
17669
|
+
acc.instanceCount += 1;
|
|
17670
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17671
|
+
if (rank < acc.maxSeverityRank) {
|
|
17672
|
+
acc.maxSeverityRank = rank;
|
|
17673
|
+
acc.maxSeverity = row.severity;
|
|
17674
|
+
}
|
|
17675
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17676
|
+
acc.statuses.push(row.status);
|
|
17677
|
+
acc.ruleIds.add(row.ruleId);
|
|
17678
|
+
}
|
|
17355
17679
|
|
|
17356
17680
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17357
17681
|
var InstalledPack = external_exports.object({
|
|
@@ -17492,6 +17816,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17492
17816
|
revealGrantId: external_exports.string().nullable(),
|
|
17493
17817
|
sightings: external_exports.array(VaultSighting)
|
|
17494
17818
|
});
|
|
17819
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17820
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17821
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17822
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17823
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17824
|
+
// Opaque; names the last row of the page just served.
|
|
17825
|
+
cursor: external_exports.string().optional()
|
|
17826
|
+
});
|
|
17827
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17828
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17829
|
+
// independent, so paging never changes what the count claims.
|
|
17830
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17831
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17832
|
+
// `null` once the last page is reached.
|
|
17833
|
+
nextCursor: external_exports.string().nullable()
|
|
17834
|
+
});
|
|
17835
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17836
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17837
|
+
cursor: external_exports.string().optional()
|
|
17838
|
+
});
|
|
17839
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17840
|
+
// Reused values across the whole store — the number the section's claim
|
|
17841
|
+
// ("values detected in more than one place") is about.
|
|
17842
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17843
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17844
|
+
nextCursor: external_exports.string().nullable()
|
|
17845
|
+
});
|
|
17846
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17847
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17848
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17849
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17850
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17851
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17852
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17853
|
+
cursor: external_exports.string().optional()
|
|
17854
|
+
});
|
|
17855
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17856
|
+
items: external_exports.array(VaultDeref),
|
|
17857
|
+
nextCursor: external_exports.string().nullable(),
|
|
17858
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17859
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17860
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17861
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17862
|
+
});
|
|
17495
17863
|
var VaultKeyCustody = external_exports.string();
|
|
17496
17864
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17497
17865
|
var VAULT_CONSENT_VERSION = 1;
|
|
@@ -17868,7 +18236,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17868
18236
|
// Omit for both kinds.
|
|
17869
18237
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17870
18238
|
});
|
|
17871
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18239
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17872
18240
|
var ScanCoverageProvider = external_exports.object({
|
|
17873
18241
|
provider: Provider,
|
|
17874
18242
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18121,6 +18489,195 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18121
18489
|
);
|
|
18122
18490
|
}
|
|
18123
18491
|
|
|
18492
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18493
|
+
import { randomUUID } from "crypto";
|
|
18494
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18495
|
+
import { basename, dirname, join } from "path";
|
|
18496
|
+
|
|
18497
|
+
// ../../packages/persistence/src/paths.ts
|
|
18498
|
+
import {
|
|
18499
|
+
chmodSync,
|
|
18500
|
+
linkSync,
|
|
18501
|
+
lstatSync,
|
|
18502
|
+
mkdirSync,
|
|
18503
|
+
renameSync,
|
|
18504
|
+
rmSync,
|
|
18505
|
+
writeFileSync
|
|
18506
|
+
} from "fs";
|
|
18507
|
+
import { threadId } from "worker_threads";
|
|
18508
|
+
var DATA_DIR_MODE = 448;
|
|
18509
|
+
var DATA_FILE_MODE = 384;
|
|
18510
|
+
var DB_FILENAME = "aka.db";
|
|
18511
|
+
function isSymlink(path) {
|
|
18512
|
+
try {
|
|
18513
|
+
return lstatSync(path).isSymbolicLink();
|
|
18514
|
+
} catch {
|
|
18515
|
+
return false;
|
|
18516
|
+
}
|
|
18517
|
+
}
|
|
18518
|
+
function chmodBestEffort(path, mode) {
|
|
18519
|
+
if (isSymlink(path)) return;
|
|
18520
|
+
try {
|
|
18521
|
+
chmodSync(path, mode);
|
|
18522
|
+
} catch {
|
|
18523
|
+
}
|
|
18524
|
+
}
|
|
18525
|
+
function tightenDir(dir) {
|
|
18526
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18527
|
+
}
|
|
18528
|
+
function ensureDataDirSync(dir) {
|
|
18529
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18530
|
+
tightenDir(dir);
|
|
18531
|
+
}
|
|
18532
|
+
function dbSidecars(file2) {
|
|
18533
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18534
|
+
}
|
|
18535
|
+
function tightenFile(file2) {
|
|
18536
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18537
|
+
}
|
|
18538
|
+
function tightenPerms(file2) {
|
|
18539
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18540
|
+
}
|
|
18541
|
+
function classifyOccupant(file2) {
|
|
18542
|
+
try {
|
|
18543
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
18544
|
+
return { kind: "gone" };
|
|
18545
|
+
} catch (err) {
|
|
18546
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
18547
|
+
return { kind: "unknown", cause: err };
|
|
18548
|
+
}
|
|
18549
|
+
}
|
|
18550
|
+
var KeyUnclaimableError = class extends Error {
|
|
18551
|
+
code = "key-unclaimable";
|
|
18552
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
18553
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
18554
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
18555
|
+
// captured and then lost, which is worse than its plain absence.
|
|
18556
|
+
constructor(message, cause) {
|
|
18557
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
18558
|
+
this.name = "KeyUnclaimableError";
|
|
18559
|
+
}
|
|
18560
|
+
};
|
|
18561
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
18562
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
18563
|
+
try {
|
|
18564
|
+
rmSync(tmp, { force: true });
|
|
18565
|
+
} catch {
|
|
18566
|
+
}
|
|
18567
|
+
let created;
|
|
18568
|
+
try {
|
|
18569
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18570
|
+
created = publishByLink(tmp, file2, data);
|
|
18571
|
+
} finally {
|
|
18572
|
+
try {
|
|
18573
|
+
rmSync(tmp, { force: true });
|
|
18574
|
+
} catch {
|
|
18575
|
+
}
|
|
18576
|
+
}
|
|
18577
|
+
if (created) tightenFile(file2);
|
|
18578
|
+
return created;
|
|
18579
|
+
}
|
|
18580
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
18581
|
+
function publishByLink(tmp, file2, data) {
|
|
18582
|
+
try {
|
|
18583
|
+
linkSync(tmp, file2);
|
|
18584
|
+
return true;
|
|
18585
|
+
} catch (err) {
|
|
18586
|
+
const code = err.code;
|
|
18587
|
+
if (code === "EEXIST") return false;
|
|
18588
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18589
|
+
}
|
|
18590
|
+
try {
|
|
18591
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18592
|
+
return true;
|
|
18593
|
+
} catch (err) {
|
|
18594
|
+
if (err.code === "EEXIST") return false;
|
|
18595
|
+
throw err;
|
|
18596
|
+
}
|
|
18597
|
+
}
|
|
18598
|
+
|
|
18599
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18600
|
+
function backupPath(file2, tag) {
|
|
18601
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18602
|
+
}
|
|
18603
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18604
|
+
function reapStalePartials(file2) {
|
|
18605
|
+
const dir = dirname(file2);
|
|
18606
|
+
const prefix = `${basename(file2)}.`;
|
|
18607
|
+
let entries;
|
|
18608
|
+
try {
|
|
18609
|
+
entries = readdirSync(dir);
|
|
18610
|
+
} catch {
|
|
18611
|
+
return;
|
|
18612
|
+
}
|
|
18613
|
+
for (const name of entries) {
|
|
18614
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18615
|
+
const partial2 = join(dir, name);
|
|
18616
|
+
try {
|
|
18617
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18618
|
+
rmSync2(partial2, { force: true });
|
|
18619
|
+
}
|
|
18620
|
+
} catch {
|
|
18621
|
+
}
|
|
18622
|
+
}
|
|
18623
|
+
}
|
|
18624
|
+
function snapshotStore(db, backup) {
|
|
18625
|
+
const partial2 = `${backup}.partial`;
|
|
18626
|
+
try {
|
|
18627
|
+
rmSync2(partial2, { force: true });
|
|
18628
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18629
|
+
tightenFile(partial2);
|
|
18630
|
+
renameSync2(partial2, backup);
|
|
18631
|
+
} catch (error51) {
|
|
18632
|
+
try {
|
|
18633
|
+
rmSync2(partial2, { force: true });
|
|
18634
|
+
} catch {
|
|
18635
|
+
}
|
|
18636
|
+
throw error51;
|
|
18637
|
+
}
|
|
18638
|
+
}
|
|
18639
|
+
function moveStoreAside(file2, backup) {
|
|
18640
|
+
const undo = [];
|
|
18641
|
+
renameSync2(file2, backup);
|
|
18642
|
+
undo.push([backup, file2]);
|
|
18643
|
+
try {
|
|
18644
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18645
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18646
|
+
try {
|
|
18647
|
+
renameSync2(sidecar, moved);
|
|
18648
|
+
undo.push([moved, sidecar]);
|
|
18649
|
+
} catch {
|
|
18650
|
+
rmSync2(sidecar, { force: true });
|
|
18651
|
+
}
|
|
18652
|
+
}
|
|
18653
|
+
} catch (error51) {
|
|
18654
|
+
for (const [from, to] of undo.reverse()) {
|
|
18655
|
+
try {
|
|
18656
|
+
renameSync2(from, to);
|
|
18657
|
+
} catch {
|
|
18658
|
+
}
|
|
18659
|
+
}
|
|
18660
|
+
throw error51;
|
|
18661
|
+
}
|
|
18662
|
+
tightenPerms(backup);
|
|
18663
|
+
}
|
|
18664
|
+
function discardStore(file2, backup) {
|
|
18665
|
+
try {
|
|
18666
|
+
rmSync2(file2, { force: true });
|
|
18667
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18668
|
+
rmSync2(sidecar, { force: true });
|
|
18669
|
+
}
|
|
18670
|
+
} catch (error51) {
|
|
18671
|
+
if (existsSync(file2)) {
|
|
18672
|
+
try {
|
|
18673
|
+
rmSync2(backup, { force: true });
|
|
18674
|
+
} catch {
|
|
18675
|
+
}
|
|
18676
|
+
}
|
|
18677
|
+
throw error51;
|
|
18678
|
+
}
|
|
18679
|
+
}
|
|
18680
|
+
|
|
18124
18681
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18125
18682
|
function escapeLikePattern(s) {
|
|
18126
18683
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18262,55 +18819,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18262
18819
|
return out;
|
|
18263
18820
|
}
|
|
18264
18821
|
|
|
18265
|
-
// ../../packages/persistence/src/paths.ts
|
|
18266
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18267
|
-
var DATA_DIR_MODE = 448;
|
|
18268
|
-
var DATA_FILE_MODE = 384;
|
|
18269
|
-
var DB_FILENAME = "aka.db";
|
|
18270
|
-
function chmodBestEffort(path, mode) {
|
|
18271
|
-
try {
|
|
18272
|
-
chmodSync(path, mode);
|
|
18273
|
-
} catch {
|
|
18274
|
-
}
|
|
18275
|
-
}
|
|
18276
|
-
function tightenDir(dir) {
|
|
18277
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18278
|
-
}
|
|
18279
|
-
function ensureDataDirSync(dir) {
|
|
18280
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18281
|
-
tightenDir(dir);
|
|
18282
|
-
}
|
|
18283
|
-
function dbSidecars(file2) {
|
|
18284
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18285
|
-
}
|
|
18286
|
-
function tightenFile(file2) {
|
|
18287
|
-
try {
|
|
18288
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18289
|
-
} catch {
|
|
18290
|
-
}
|
|
18291
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18292
|
-
}
|
|
18293
|
-
function tightenPerms(file2) {
|
|
18294
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18295
|
-
}
|
|
18296
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18297
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18298
|
-
try {
|
|
18299
|
-
rmSync(tmp, { force: true });
|
|
18300
|
-
} catch {
|
|
18301
|
-
}
|
|
18302
|
-
try {
|
|
18303
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18304
|
-
renameSync(tmp, file2);
|
|
18305
|
-
} finally {
|
|
18306
|
-
try {
|
|
18307
|
-
rmSync(tmp, { force: true });
|
|
18308
|
-
} catch {
|
|
18309
|
-
}
|
|
18310
|
-
}
|
|
18311
|
-
tightenFile(file2);
|
|
18312
|
-
}
|
|
18313
|
-
|
|
18314
18822
|
// ../../packages/persistence/src/migrations.ts
|
|
18315
18823
|
function describeObject(object2) {
|
|
18316
18824
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18426,9 +18934,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18426
18934
|
}
|
|
18427
18935
|
}
|
|
18428
18936
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18937
|
+
reapStalePartials(file2);
|
|
18938
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18939
|
+
snapshotStore(db, backup);
|
|
18432
18940
|
return backup;
|
|
18433
18941
|
}
|
|
18434
18942
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18772,6 +19280,25 @@ function parseJsonObject(s) {
|
|
|
18772
19280
|
return void 0;
|
|
18773
19281
|
}
|
|
18774
19282
|
|
|
19283
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19284
|
+
function encodeKeysetCursor(payload) {
|
|
19285
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19286
|
+
}
|
|
19287
|
+
function decodeKeysetCursor(cursor) {
|
|
19288
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19289
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19290
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19291
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19292
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19293
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19294
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19295
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19296
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19297
|
+
return parsed;
|
|
19298
|
+
}
|
|
19299
|
+
return null;
|
|
19300
|
+
}
|
|
19301
|
+
|
|
18775
19302
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18776
19303
|
var DAY_MS = 864e5;
|
|
18777
19304
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18817,16 +19344,6 @@ function utcWindow(nowMs) {
|
|
|
18817
19344
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18818
19345
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18819
19346
|
}
|
|
18820
|
-
function encodeCursor(payload) {
|
|
18821
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18822
|
-
}
|
|
18823
|
-
function decodeCursor(cursor) {
|
|
18824
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18825
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18826
|
-
return parsed;
|
|
18827
|
-
}
|
|
18828
|
-
return null;
|
|
18829
|
-
}
|
|
18830
19347
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18831
19348
|
session: "session",
|
|
18832
19349
|
prompt: "prompt",
|
|
@@ -18971,7 +19488,7 @@ var SqliteActivityRepository = class {
|
|
|
18971
19488
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18972
19489
|
}
|
|
18973
19490
|
listSessions(query) {
|
|
18974
|
-
const cursor = query.cursor ?
|
|
19491
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18975
19492
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18976
19493
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18977
19494
|
const conditions = [SESSION_ROOT];
|
|
@@ -19045,7 +19562,7 @@ var SqliteActivityRepository = class {
|
|
|
19045
19562
|
)
|
|
19046
19563
|
);
|
|
19047
19564
|
const last = page[page.length - 1];
|
|
19048
|
-
const nextCursor = hasMore && last ?
|
|
19565
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19049
19566
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19050
19567
|
}
|
|
19051
19568
|
getSession(sessionId) {
|
|
@@ -19918,7 +20435,7 @@ var SqliteEventsRepository = class {
|
|
|
19918
20435
|
};
|
|
19919
20436
|
|
|
19920
20437
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19921
|
-
import { randomUUID } from "crypto";
|
|
20438
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19922
20439
|
|
|
19923
20440
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19924
20441
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19954,8 +20471,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19954
20471
|
AND conditions IS NULL
|
|
19955
20472
|
AND ${ACTIVE_PREDICATE}`;
|
|
19956
20473
|
var SqliteExceptionsRepository = class {
|
|
19957
|
-
constructor(db) {
|
|
20474
|
+
constructor(db, now = () => Date.now()) {
|
|
19958
20475
|
this.db = db;
|
|
20476
|
+
this.now = now;
|
|
19959
20477
|
this.consumeStmt = db.prepare(
|
|
19960
20478
|
`UPDATE exceptions
|
|
19961
20479
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19973,6 +20491,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19973
20491
|
);
|
|
19974
20492
|
}
|
|
19975
20493
|
db;
|
|
20494
|
+
now;
|
|
19976
20495
|
consumeStmt;
|
|
19977
20496
|
insertBlockedStmt;
|
|
19978
20497
|
sweepBlockedStmt;
|
|
@@ -19999,8 +20518,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19999
20518
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
20000
20519
|
);
|
|
20001
20520
|
}
|
|
20002
|
-
const id =
|
|
20003
|
-
const now =
|
|
20521
|
+
const id = randomUUID2();
|
|
20522
|
+
const now = this.now();
|
|
20004
20523
|
try {
|
|
20005
20524
|
this.insertExceptionRow(id, input, now);
|
|
20006
20525
|
} catch (err) {
|
|
@@ -20078,7 +20597,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20078
20597
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20079
20598
|
const rows = allRows(
|
|
20080
20599
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20081
|
-
opts?.includeTerminal ? {} : { now:
|
|
20600
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20082
20601
|
);
|
|
20083
20602
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20084
20603
|
return Promise.resolve(exceptions);
|
|
@@ -20113,7 +20632,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20113
20632
|
* already revoked.
|
|
20114
20633
|
*/
|
|
20115
20634
|
revoke(id, revokedBy, reason) {
|
|
20116
|
-
const now =
|
|
20635
|
+
const now = this.now();
|
|
20117
20636
|
const result = this.db.prepare(
|
|
20118
20637
|
`UPDATE exceptions
|
|
20119
20638
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20127,7 +20646,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20127
20646
|
* callers must treat identically — means it does not and the detection is
|
|
20128
20647
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20129
20648
|
*/
|
|
20130
|
-
consume(id, now =
|
|
20649
|
+
consume(id, now = this.now()) {
|
|
20131
20650
|
const result = this.consumeStmt.run({ id, now });
|
|
20132
20651
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20133
20652
|
}
|
|
@@ -20136,7 +20655,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20136
20655
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20137
20656
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20138
20657
|
*/
|
|
20139
|
-
activeBundleEntries(keyVersion, now =
|
|
20658
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20140
20659
|
const rows = allRows(
|
|
20141
20660
|
this.db.prepare(
|
|
20142
20661
|
`SELECT * FROM exceptions
|
|
@@ -20168,7 +20687,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20168
20687
|
* than the retention window on every write, so the ledger self-limits.
|
|
20169
20688
|
*/
|
|
20170
20689
|
recordBlocked(entry) {
|
|
20171
|
-
const now =
|
|
20690
|
+
const now = this.now();
|
|
20172
20691
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20173
20692
|
this.insertBlockedStmt.run({
|
|
20174
20693
|
reference: entry.reference,
|
|
@@ -20191,7 +20710,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20191
20710
|
WHERE blocked_at > :cutoff
|
|
20192
20711
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20193
20712
|
),
|
|
20194
|
-
{ cutoff:
|
|
20713
|
+
{ cutoff: this.now() - windowMs }
|
|
20195
20714
|
);
|
|
20196
20715
|
return Promise.resolve(
|
|
20197
20716
|
rows.map((row) => ({
|
|
@@ -20219,8 +20738,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20219
20738
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20220
20739
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20221
20740
|
*/
|
|
20222
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20741
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20223
20742
|
try {
|
|
20743
|
+
const at = now ?? this.now();
|
|
20224
20744
|
const row = getRow(
|
|
20225
20745
|
this.db.prepare(
|
|
20226
20746
|
`SELECT id FROM exceptions
|
|
@@ -20229,7 +20749,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20229
20749
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20230
20750
|
LIMIT 1`
|
|
20231
20751
|
),
|
|
20232
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20752
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20233
20753
|
);
|
|
20234
20754
|
return Promise.resolve(row ?? null);
|
|
20235
20755
|
} catch (err) {
|
|
@@ -20243,7 +20763,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20243
20763
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20244
20764
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20245
20765
|
*/
|
|
20246
|
-
sweepTerminal(retentionMs, now =
|
|
20766
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20247
20767
|
const result = this.db.prepare(
|
|
20248
20768
|
`DELETE FROM exceptions
|
|
20249
20769
|
WHERE updated_at < :cutoff
|
|
@@ -20306,6 +20826,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20306
20826
|
|
|
20307
20827
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20308
20828
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20829
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20830
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20831
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20832
|
+
function compareLocationOrder(a, b) {
|
|
20833
|
+
return compareFindingGroupOrder(
|
|
20834
|
+
{
|
|
20835
|
+
severity: a.maxSeverity,
|
|
20836
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20837
|
+
id: ""
|
|
20838
|
+
},
|
|
20839
|
+
{
|
|
20840
|
+
severity: b.maxSeverity,
|
|
20841
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20842
|
+
id: ""
|
|
20843
|
+
}
|
|
20844
|
+
);
|
|
20845
|
+
}
|
|
20309
20846
|
var CONCAT_SEP = ",";
|
|
20310
20847
|
var TUPLE_SEP = "|";
|
|
20311
20848
|
function splitConcat(value) {
|
|
@@ -20318,6 +20855,33 @@ function deriveInstanceStatus(row) {
|
|
|
20318
20855
|
latestResolutionStatus: row.latest_status
|
|
20319
20856
|
});
|
|
20320
20857
|
}
|
|
20858
|
+
function encodeGroupCursor(group) {
|
|
20859
|
+
const payload = {
|
|
20860
|
+
sev: group.severity,
|
|
20861
|
+
t: group.latestDetectedAt,
|
|
20862
|
+
id: group.id
|
|
20863
|
+
};
|
|
20864
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20865
|
+
}
|
|
20866
|
+
function decodeGroupCursor(cursor) {
|
|
20867
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20868
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20869
|
+
return {
|
|
20870
|
+
severity: parsed.sev,
|
|
20871
|
+
latestDetectedAt: parsed.t,
|
|
20872
|
+
id: parsed.id
|
|
20873
|
+
};
|
|
20874
|
+
}
|
|
20875
|
+
return null;
|
|
20876
|
+
}
|
|
20877
|
+
function firstAfter(sorted, cursor) {
|
|
20878
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20879
|
+
return index === -1 ? sorted.length : index;
|
|
20880
|
+
}
|
|
20881
|
+
function findDeepLinked(sorted, page, id) {
|
|
20882
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20883
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20884
|
+
}
|
|
20321
20885
|
var DAY_MS3 = 864e5;
|
|
20322
20886
|
var SqliteFindingsRepository = class {
|
|
20323
20887
|
constructor(db) {
|
|
@@ -20427,8 +20991,13 @@ var SqliteFindingsRepository = class {
|
|
|
20427
20991
|
*/
|
|
20428
20992
|
listGroupedFindings(query) {
|
|
20429
20993
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20430
|
-
const
|
|
20431
|
-
const
|
|
20994
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20995
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20996
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20997
|
+
const sessionParams = {
|
|
20998
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20999
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
21000
|
+
};
|
|
20432
21001
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20433
21002
|
predicate,
|
|
20434
21003
|
params: sessionParams
|
|
@@ -20436,7 +21005,8 @@ var SqliteFindingsRepository = class {
|
|
|
20436
21005
|
const rows = allRows(
|
|
20437
21006
|
this.db.prepare(
|
|
20438
21007
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20439
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
21008
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
21009
|
+
kind, finding_key, latest_status
|
|
20440
21010
|
FROM (
|
|
20441
21011
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20442
21012
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20446,6 +21016,7 @@ var SqliteFindingsRepository = class {
|
|
|
20446
21016
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20447
21017
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20448
21018
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21019
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20449
21020
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20450
21021
|
latest.status AS latest_status,
|
|
20451
21022
|
ROW_NUMBER() OVER (
|
|
@@ -20477,6 +21048,8 @@ var SqliteFindingsRepository = class {
|
|
|
20477
21048
|
repo: r.repo ?? "",
|
|
20478
21049
|
file: r.file ?? "",
|
|
20479
21050
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21051
|
+
eventId: r.event_id,
|
|
21052
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20480
21053
|
status: deriveInstanceStatus(r)
|
|
20481
21054
|
}));
|
|
20482
21055
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20500,18 +21073,23 @@ var SqliteFindingsRepository = class {
|
|
|
20500
21073
|
groups: sorted.length
|
|
20501
21074
|
};
|
|
20502
21075
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21076
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21077
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21078
|
+
const page = sorted.slice(start, start + limit);
|
|
21079
|
+
const lastOnPage = page.at(-1);
|
|
21080
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21081
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20503
21082
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20504
|
-
const
|
|
20505
|
-
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
|
|
20509
|
-
);
|
|
21083
|
+
const narrow = (g) => statusSet ? {
|
|
21084
|
+
...g,
|
|
21085
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21086
|
+
} : g;
|
|
21087
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20510
21088
|
return Promise.resolve({
|
|
20511
21089
|
totals,
|
|
20512
21090
|
facets,
|
|
20513
21091
|
items,
|
|
20514
|
-
nextCursor
|
|
21092
|
+
nextCursor,
|
|
20515
21093
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20516
21094
|
});
|
|
20517
21095
|
}
|
|
@@ -20543,6 +21121,266 @@ var SqliteFindingsRepository = class {
|
|
|
20543
21121
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20544
21122
|
* path repeating across tuples.)
|
|
20545
21123
|
*/
|
|
21124
|
+
/**
|
|
21125
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21126
|
+
* paged by keyset.
|
|
21127
|
+
*
|
|
21128
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21129
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21130
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21131
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21132
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21133
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21134
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21135
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21136
|
+
* none of the mappers names", which no IN-list can say.
|
|
21137
|
+
*
|
|
21138
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21139
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21140
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21141
|
+
* while the counting runs, and only the page itself is retained.
|
|
21142
|
+
*/
|
|
21143
|
+
listFindingInstances(query) {
|
|
21144
|
+
const opts = {
|
|
21145
|
+
severity: query.severity,
|
|
21146
|
+
subtype: query.subtype,
|
|
21147
|
+
providers: query.provider,
|
|
21148
|
+
actions: query.action,
|
|
21149
|
+
statuses: query.status,
|
|
21150
|
+
tools: query.tool,
|
|
21151
|
+
repo: query.repo,
|
|
21152
|
+
file: query.file,
|
|
21153
|
+
q: query.q
|
|
21154
|
+
};
|
|
21155
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21156
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21157
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21158
|
+
const items = [];
|
|
21159
|
+
let total = 0;
|
|
21160
|
+
let last;
|
|
21161
|
+
let hasMore = false;
|
|
21162
|
+
for (const row of this.scanFindingRows({
|
|
21163
|
+
sessionId: query.sessionId,
|
|
21164
|
+
from: query.from
|
|
21165
|
+
})) {
|
|
21166
|
+
accumulator.add(row);
|
|
21167
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21168
|
+
total += 1;
|
|
21169
|
+
if (items.length < limit) {
|
|
21170
|
+
items.push(toInstanceDetail(row));
|
|
21171
|
+
last = row;
|
|
21172
|
+
} else {
|
|
21173
|
+
hasMore = true;
|
|
21174
|
+
}
|
|
21175
|
+
}
|
|
21176
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21177
|
+
if (cursor !== null) {
|
|
21178
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21179
|
+
return Promise.resolve({
|
|
21180
|
+
totals: { findings: total },
|
|
21181
|
+
facets: accumulator.facets(),
|
|
21182
|
+
items: resumed.items,
|
|
21183
|
+
nextCursor: resumed.nextCursor
|
|
21184
|
+
});
|
|
21185
|
+
}
|
|
21186
|
+
return Promise.resolve({
|
|
21187
|
+
totals: { findings: total },
|
|
21188
|
+
facets: accumulator.facets(),
|
|
21189
|
+
items,
|
|
21190
|
+
nextCursor
|
|
21191
|
+
});
|
|
21192
|
+
}
|
|
21193
|
+
/**
|
|
21194
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21195
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21196
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21197
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21198
|
+
*/
|
|
21199
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21200
|
+
const items = [];
|
|
21201
|
+
let last;
|
|
21202
|
+
let hasMore = false;
|
|
21203
|
+
for (const row of this.scanFindingRows({
|
|
21204
|
+
sessionId: query.sessionId,
|
|
21205
|
+
from: query.from,
|
|
21206
|
+
after: cursor
|
|
21207
|
+
})) {
|
|
21208
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21209
|
+
if (items.length < limit) {
|
|
21210
|
+
items.push(toInstanceDetail(row));
|
|
21211
|
+
last = row;
|
|
21212
|
+
} else {
|
|
21213
|
+
hasMore = true;
|
|
21214
|
+
break;
|
|
21215
|
+
}
|
|
21216
|
+
}
|
|
21217
|
+
return {
|
|
21218
|
+
items,
|
|
21219
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21220
|
+
};
|
|
21221
|
+
}
|
|
21222
|
+
/**
|
|
21223
|
+
* The same findings folded by location: repository, then file within it.
|
|
21224
|
+
*
|
|
21225
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21226
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21227
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21228
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21229
|
+
* filter can name it.
|
|
21230
|
+
*/
|
|
21231
|
+
listFindingLocations(query) {
|
|
21232
|
+
const opts = {
|
|
21233
|
+
severity: query.severity,
|
|
21234
|
+
subtype: query.subtype,
|
|
21235
|
+
providers: query.provider,
|
|
21236
|
+
actions: query.action,
|
|
21237
|
+
statuses: query.status,
|
|
21238
|
+
tools: query.tool,
|
|
21239
|
+
q: query.q
|
|
21240
|
+
};
|
|
21241
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21242
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21243
|
+
let total = 0;
|
|
21244
|
+
for (const row of this.scanFindingRows({
|
|
21245
|
+
sessionId: query.sessionId,
|
|
21246
|
+
from: query.from
|
|
21247
|
+
})) {
|
|
21248
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21249
|
+
total += 1;
|
|
21250
|
+
let files = byRepo.get(row.repo);
|
|
21251
|
+
if (files === void 0) {
|
|
21252
|
+
files = /* @__PURE__ */ new Map();
|
|
21253
|
+
byRepo.set(row.repo, files);
|
|
21254
|
+
}
|
|
21255
|
+
let acc = files.get(row.file);
|
|
21256
|
+
if (acc === void 0) {
|
|
21257
|
+
acc = newLocationAccumulator();
|
|
21258
|
+
files.set(row.file, acc);
|
|
21259
|
+
}
|
|
21260
|
+
addToLocation(acc, row);
|
|
21261
|
+
}
|
|
21262
|
+
let fileCount = 0;
|
|
21263
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21264
|
+
fileCount += files.size;
|
|
21265
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21266
|
+
file: file2,
|
|
21267
|
+
instanceCount: acc.instanceCount,
|
|
21268
|
+
maxSeverity: acc.maxSeverity,
|
|
21269
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21270
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21271
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21272
|
+
})).sort(compareLocationOrder);
|
|
21273
|
+
const rollup = fileRows.reduce(
|
|
21274
|
+
(a, f) => ({
|
|
21275
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21276
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21277
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21278
|
+
}),
|
|
21279
|
+
{
|
|
21280
|
+
instanceCount: 0,
|
|
21281
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21282
|
+
latestDetectedAt: ""
|
|
21283
|
+
}
|
|
21284
|
+
);
|
|
21285
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21286
|
+
const folded = foldGroupStatus(statuses);
|
|
21287
|
+
return {
|
|
21288
|
+
repo,
|
|
21289
|
+
instanceCount: rollup.instanceCount,
|
|
21290
|
+
maxSeverity: rollup.maxSeverity,
|
|
21291
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21292
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21293
|
+
files: fileRows
|
|
21294
|
+
};
|
|
21295
|
+
});
|
|
21296
|
+
repos.sort(compareLocationOrder);
|
|
21297
|
+
return Promise.resolve({
|
|
21298
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21299
|
+
items: repos.slice(0, limit),
|
|
21300
|
+
hasMore: repos.length > limit
|
|
21301
|
+
});
|
|
21302
|
+
}
|
|
21303
|
+
/**
|
|
21304
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21305
|
+
*
|
|
21306
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21307
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21308
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21309
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21310
|
+
* rather than one unbounded result set.
|
|
21311
|
+
*
|
|
21312
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21313
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21314
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21315
|
+
* a window over the whole resolution table once per batch.
|
|
21316
|
+
*
|
|
21317
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21318
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21319
|
+
* dimension — see listFindingInstances.
|
|
21320
|
+
*/
|
|
21321
|
+
*scanFindingRows(scope) {
|
|
21322
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21323
|
+
const params = [];
|
|
21324
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21325
|
+
conditions.push("e.root_session_id = ?");
|
|
21326
|
+
params.push(scope.sessionId);
|
|
21327
|
+
}
|
|
21328
|
+
if (scope.from !== void 0) {
|
|
21329
|
+
conditions.push("e.started_at >= ?");
|
|
21330
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21331
|
+
}
|
|
21332
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21333
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21334
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21335
|
+
e.started_at AS occurred_at,
|
|
21336
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21337
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21338
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21339
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21340
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21341
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21342
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21343
|
+
FROM inspection_findings f
|
|
21344
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21345
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21346
|
+
WHERE ${conditions.join(" AND ")}
|
|
21347
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21348
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21349
|
+
LIMIT ?`;
|
|
21350
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21351
|
+
for (; ; ) {
|
|
21352
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21353
|
+
...params,
|
|
21354
|
+
after.startedAtMs,
|
|
21355
|
+
after.startedAtMs,
|
|
21356
|
+
after.id,
|
|
21357
|
+
SCAN_BATCH_ROWS
|
|
21358
|
+
]);
|
|
21359
|
+
for (const r of rows) {
|
|
21360
|
+
yield {
|
|
21361
|
+
id: r.id,
|
|
21362
|
+
ruleId: r.rule_id,
|
|
21363
|
+
category: r.category,
|
|
21364
|
+
severity: r.severity,
|
|
21365
|
+
maskedMatch: r.masked_match,
|
|
21366
|
+
actionTaken: r.action_taken,
|
|
21367
|
+
confidence: r.confidence,
|
|
21368
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21369
|
+
sourceTool: r.source_tool,
|
|
21370
|
+
repo: r.repo ?? "",
|
|
21371
|
+
file: r.file ?? "",
|
|
21372
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21373
|
+
eventId: r.event_id,
|
|
21374
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21375
|
+
status: deriveInstanceStatus(r)
|
|
21376
|
+
};
|
|
21377
|
+
}
|
|
21378
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21379
|
+
const lastRow = rows[rows.length - 1];
|
|
21380
|
+
if (lastRow === void 0) return;
|
|
21381
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21382
|
+
}
|
|
21383
|
+
}
|
|
20546
21384
|
groupAggregates(withSearchText, scope) {
|
|
20547
21385
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20548
21386
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20803,7 +21641,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20803
21641
|
};
|
|
20804
21642
|
|
|
20805
21643
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20806
|
-
import { createHash as createHash2, randomUUID as
|
|
21644
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20807
21645
|
|
|
20808
21646
|
// ../../packages/persistence/src/semver.ts
|
|
20809
21647
|
function parse3(version2) {
|
|
@@ -20954,7 +21792,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20954
21792
|
let behind = false;
|
|
20955
21793
|
for (const row of rows) {
|
|
20956
21794
|
const params = {
|
|
20957
|
-
id:
|
|
21795
|
+
id: randomUUID3(),
|
|
20958
21796
|
namespace: row.namespace,
|
|
20959
21797
|
packId: row.packId,
|
|
20960
21798
|
version: row.version,
|
|
@@ -20966,7 +21804,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20966
21804
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20967
21805
|
this.upsertAvailableStmt.run({
|
|
20968
21806
|
...params,
|
|
20969
|
-
id:
|
|
21807
|
+
id: randomUUID3(),
|
|
20970
21808
|
recordedBy: meta3?.recordedBy ?? null
|
|
20971
21809
|
});
|
|
20972
21810
|
} else {
|
|
@@ -21289,14 +22127,15 @@ var SqliteInventoryRepository = class {
|
|
|
21289
22127
|
};
|
|
21290
22128
|
|
|
21291
22129
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21292
|
-
import { randomUUID as
|
|
22130
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21293
22131
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21294
22132
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21295
22133
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21296
22134
|
var HARNESS_LABELS = {
|
|
21297
22135
|
claudecode: "Claude Code",
|
|
21298
22136
|
cursor: "Cursor",
|
|
21299
|
-
codex: "Codex"
|
|
22137
|
+
codex: "Codex",
|
|
22138
|
+
antigravity: "Antigravity"
|
|
21300
22139
|
};
|
|
21301
22140
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21302
22141
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21311,6 +22150,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21311
22150
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21312
22151
|
if (t.includes("cursor")) return "cursor";
|
|
21313
22152
|
if (t.includes("codex")) return "codex";
|
|
22153
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21314
22154
|
return null;
|
|
21315
22155
|
}
|
|
21316
22156
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21769,7 +22609,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21769
22609
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21770
22610
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21771
22611
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21772
|
-
).run({ id:
|
|
22612
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21773
22613
|
}
|
|
21774
22614
|
return true;
|
|
21775
22615
|
}
|
|
@@ -21790,7 +22630,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21790
22630
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21791
22631
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21792
22632
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21793
|
-
).run({ id:
|
|
22633
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21794
22634
|
}
|
|
21795
22635
|
this.configRowsCache = void 0;
|
|
21796
22636
|
return "ok";
|
|
@@ -22087,7 +22927,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22087
22927
|
};
|
|
22088
22928
|
|
|
22089
22929
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22090
|
-
import { randomUUID as
|
|
22930
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22091
22931
|
var SqlitePoliciesRepository = class {
|
|
22092
22932
|
constructor(db) {
|
|
22093
22933
|
this.db = db;
|
|
@@ -22122,7 +22962,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22122
22962
|
failOpenTransaction(this.db, () => {
|
|
22123
22963
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22124
22964
|
stmt.run({
|
|
22125
|
-
id:
|
|
22965
|
+
id: randomUUID5(),
|
|
22126
22966
|
target: JSON.stringify({ category }),
|
|
22127
22967
|
action,
|
|
22128
22968
|
now: Date.now()
|
|
@@ -22142,7 +22982,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22142
22982
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22143
22983
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22144
22984
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22145
|
-
).run({ id:
|
|
22985
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22146
22986
|
}
|
|
22147
22987
|
// Caps every global per-category policy currently set to block/redact down
|
|
22148
22988
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22210,7 +23050,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22210
23050
|
};
|
|
22211
23051
|
|
|
22212
23052
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22213
|
-
import { randomUUID as
|
|
23053
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22214
23054
|
var SqliteProjectFilesRepository = class {
|
|
22215
23055
|
constructor(db) {
|
|
22216
23056
|
this.db = db;
|
|
@@ -22242,7 +23082,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22242
23082
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22243
23083
|
for (const file2 of scan2.files) {
|
|
22244
23084
|
this.upsertStmt.run({
|
|
22245
|
-
id:
|
|
23085
|
+
id: randomUUID6(),
|
|
22246
23086
|
projectId,
|
|
22247
23087
|
path: file2.path,
|
|
22248
23088
|
name: file2.name,
|
|
@@ -22256,7 +23096,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22256
23096
|
};
|
|
22257
23097
|
|
|
22258
23098
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22259
|
-
import { randomUUID as
|
|
23099
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22260
23100
|
var SqliteResolutionsRepository = class {
|
|
22261
23101
|
constructor(db, now = () => Date.now()) {
|
|
22262
23102
|
this.db = db;
|
|
@@ -22310,7 +23150,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22310
23150
|
*/
|
|
22311
23151
|
insertResolution(r) {
|
|
22312
23152
|
this.insertStmt.run({
|
|
22313
|
-
id:
|
|
23153
|
+
id: randomUUID7(),
|
|
22314
23154
|
findingKey: r.findingKey,
|
|
22315
23155
|
status: FindingStatus.parse(r.status),
|
|
22316
23156
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22369,13 +23209,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22369
23209
|
this.readStmt = db.prepare(
|
|
22370
23210
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22371
23211
|
);
|
|
23212
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23213
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23214
|
+
);
|
|
23215
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23216
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23217
|
+
);
|
|
22372
23218
|
}
|
|
22373
23219
|
db;
|
|
22374
23220
|
upsertStmt;
|
|
22375
23221
|
readStmt;
|
|
23222
|
+
countQuarantinedStmt;
|
|
23223
|
+
clearQuarantinedStmt;
|
|
22376
23224
|
getVerdict(ruleKey) {
|
|
22377
23225
|
return getRow(this.readStmt, { ruleKey });
|
|
22378
23226
|
}
|
|
23227
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23228
|
+
countQuarantined() {
|
|
23229
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23230
|
+
}
|
|
23231
|
+
/**
|
|
23232
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23233
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23234
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23235
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23236
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23237
|
+
*
|
|
23238
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23239
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23240
|
+
*
|
|
23241
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23242
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23243
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23244
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23245
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23246
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23247
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23248
|
+
* to restore are silently still disabled.
|
|
23249
|
+
*/
|
|
23250
|
+
clearQuarantined() {
|
|
23251
|
+
const before = this.countQuarantined();
|
|
23252
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23253
|
+
this.clearQuarantinedStmt.run();
|
|
23254
|
+
});
|
|
23255
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23256
|
+
}
|
|
22379
23257
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22380
23258
|
failOpenTransaction(this.db, () => {
|
|
22381
23259
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22430,7 +23308,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22430
23308
|
};
|
|
22431
23309
|
|
|
22432
23310
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22433
|
-
import { randomUUID as
|
|
23311
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23312
|
+
function pageLimit(requested, fallback) {
|
|
23313
|
+
if (requested === void 0) return fallback;
|
|
23314
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23315
|
+
}
|
|
23316
|
+
function encodeReuseCursor(payload) {
|
|
23317
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23318
|
+
}
|
|
23319
|
+
function decodeReuseCursor(cursor) {
|
|
23320
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23321
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23322
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23323
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23324
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23325
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23326
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23327
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23328
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23329
|
+
}
|
|
23330
|
+
return null;
|
|
23331
|
+
}
|
|
23332
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23333
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23334
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23335
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23336
|
+
function toSighting(row) {
|
|
23337
|
+
return {
|
|
23338
|
+
location: row.location,
|
|
23339
|
+
kind: row.kind,
|
|
23340
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23341
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23342
|
+
};
|
|
23343
|
+
}
|
|
22434
23344
|
var SELECT_COLUMNS = `
|
|
22435
23345
|
pointer_id AS pointerId,
|
|
22436
23346
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22614,39 +23524,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22614
23524
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22615
23525
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22616
23526
|
).run({
|
|
22617
|
-
id:
|
|
23527
|
+
id: randomUUID8(),
|
|
22618
23528
|
pointerId: entry.pointerId,
|
|
22619
23529
|
location: entry.location,
|
|
22620
23530
|
kind: entry.kind,
|
|
22621
23531
|
now
|
|
22622
23532
|
});
|
|
22623
23533
|
}
|
|
22624
|
-
|
|
23534
|
+
/**
|
|
23535
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23536
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23537
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23538
|
+
*
|
|
23539
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23540
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23541
|
+
*/
|
|
23542
|
+
sightingsFor(pointerIds) {
|
|
23543
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23544
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22625
23545
|
const rows = allRows(
|
|
22626
23546
|
this.db.prepare(
|
|
22627
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22628
|
-
|
|
23547
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23548
|
+
FROM secret_vault_sighting
|
|
23549
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23550
|
+
ORDER BY last_seen DESC`
|
|
22629
23551
|
),
|
|
22630
|
-
|
|
23552
|
+
pointerIds
|
|
22631
23553
|
);
|
|
23554
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23555
|
+
return byPointer;
|
|
23556
|
+
}
|
|
23557
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23558
|
+
toInventoryEntries(rows) {
|
|
23559
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22632
23560
|
return rows.map((r) => ({
|
|
22633
|
-
|
|
22634
|
-
|
|
23561
|
+
pointerId: r.pointer_id,
|
|
23562
|
+
category: r.category,
|
|
23563
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23564
|
+
maskedMatch: r.masked_match,
|
|
23565
|
+
occurrences: r.occurrence_count,
|
|
22635
23566
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22636
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23567
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23568
|
+
revealGrantId: r.grant_id,
|
|
23569
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22637
23570
|
}));
|
|
22638
23571
|
}
|
|
22639
23572
|
/**
|
|
22640
|
-
* The dashboard inventory
|
|
22641
|
-
* its sightings and the active
|
|
22642
|
-
* Raw-free by construction — neither
|
|
22643
|
-
* columns are selected.
|
|
23573
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23574
|
+
* value's descriptor data joined with its sightings and the active
|
|
23575
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23576
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23577
|
+
*
|
|
23578
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23579
|
+
* sees never depends on how far they have paged.
|
|
22644
23580
|
*/
|
|
22645
|
-
listInventory(now = Date.now()) {
|
|
23581
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23582
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23583
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23584
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22646
23585
|
const rows = allRows(
|
|
22647
23586
|
this.db.prepare(
|
|
22648
|
-
`SELECT
|
|
22649
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23587
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22650
23588
|
(SELECT e.id FROM exceptions e
|
|
22651
23589
|
WHERE e.rule_id = v.rule_id
|
|
22652
23590
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22654,45 +23592,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22654
23592
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22655
23593
|
LIMIT 1) AS grant_id
|
|
22656
23594
|
FROM secret_vault v
|
|
22657
|
-
|
|
23595
|
+
${where}
|
|
23596
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23597
|
+
LIMIT :limit`
|
|
22658
23598
|
),
|
|
22659
|
-
{
|
|
23599
|
+
bindParams({
|
|
23600
|
+
now,
|
|
23601
|
+
limit: limit + 1,
|
|
23602
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23603
|
+
})
|
|
22660
23604
|
);
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
22671
|
-
|
|
23605
|
+
const hasMore = rows.length > limit;
|
|
23606
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23607
|
+
const last = page[page.length - 1];
|
|
23608
|
+
return {
|
|
23609
|
+
totals: { values: this.countEntries() },
|
|
23610
|
+
items: this.toInventoryEntries(page),
|
|
23611
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23612
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23613
|
+
};
|
|
23614
|
+
}
|
|
23615
|
+
/**
|
|
23616
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23617
|
+
* than one location — most-reused first, one page at a time.
|
|
23618
|
+
*
|
|
23619
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23620
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23621
|
+
* under-report exactly the values a reader most needs to see.
|
|
23622
|
+
*/
|
|
23623
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23624
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23625
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23626
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23627
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23628
|
+
const rows = allRows(
|
|
23629
|
+
this.db.prepare(
|
|
23630
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23631
|
+
(SELECT e.id FROM exceptions e
|
|
23632
|
+
WHERE e.rule_id = v.rule_id
|
|
23633
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23634
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23635
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23636
|
+
LIMIT 1) AS grant_id
|
|
23637
|
+
FROM secret_vault v
|
|
23638
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23639
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23640
|
+
LIMIT :limit`
|
|
23641
|
+
),
|
|
23642
|
+
bindParams({
|
|
23643
|
+
now,
|
|
23644
|
+
limit: limit + 1,
|
|
23645
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23646
|
+
})
|
|
23647
|
+
);
|
|
23648
|
+
const hasMore = rows.length > limit;
|
|
23649
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23650
|
+
const last = page[page.length - 1];
|
|
23651
|
+
return {
|
|
23652
|
+
totals: { reused: this.countReused() },
|
|
23653
|
+
items: this.toInventoryEntries(page),
|
|
23654
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23655
|
+
};
|
|
22672
23656
|
}
|
|
22673
23657
|
/**
|
|
22674
|
-
* The de-reference trail, newest first. By default the
|
|
22675
|
-
* reasons (display, view-render) are hidden and counted
|
|
22676
|
-
* that matter as a signal are the model crossings, and
|
|
22677
|
-
* render noise would defeat the audit's purpose.
|
|
23658
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23659
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23660
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23661
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23662
|
+
*
|
|
23663
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23664
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23665
|
+
* the reader pages.
|
|
22678
23666
|
*/
|
|
22679
|
-
listDerefs(
|
|
22680
|
-
const limit =
|
|
22681
|
-
const
|
|
23667
|
+
listDerefs(query = {}) {
|
|
23668
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23669
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23670
|
+
const conditions = [];
|
|
23671
|
+
if (query.includeBatched !== true) {
|
|
23672
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23673
|
+
}
|
|
23674
|
+
if (cursor !== null) {
|
|
23675
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23676
|
+
}
|
|
23677
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22682
23678
|
const rows = allRows(
|
|
22683
23679
|
this.db.prepare(
|
|
22684
23680
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22685
23681
|
FROM secret_vault_deref ${where}
|
|
22686
|
-
ORDER BY at DESC,
|
|
23682
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22687
23683
|
),
|
|
22688
|
-
{
|
|
23684
|
+
bindParams({
|
|
23685
|
+
limit: limit + 1,
|
|
23686
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23687
|
+
})
|
|
22689
23688
|
);
|
|
22690
|
-
const
|
|
23689
|
+
const hasMore = rows.length > limit;
|
|
23690
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23691
|
+
const last = page[page.length - 1];
|
|
23692
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22691
23693
|
this.db,
|
|
22692
23694
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22693
23695
|
);
|
|
22694
23696
|
return {
|
|
22695
|
-
|
|
23697
|
+
items: page.map((r) => ({
|
|
22696
23698
|
id: r.id,
|
|
22697
23699
|
pointerId: r.pointer_id,
|
|
22698
23700
|
at: new Date(r.at).toISOString(),
|
|
@@ -22702,12 +23704,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22702
23704
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22703
23705
|
pointerCount: r.pointer_count
|
|
22704
23706
|
})),
|
|
23707
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22705
23708
|
hiddenBatched
|
|
22706
23709
|
};
|
|
22707
23710
|
}
|
|
22708
23711
|
countEntries() {
|
|
22709
23712
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22710
23713
|
}
|
|
23714
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23715
|
+
countReused() {
|
|
23716
|
+
return countScalar(
|
|
23717
|
+
this.db,
|
|
23718
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23719
|
+
);
|
|
23720
|
+
}
|
|
22711
23721
|
};
|
|
22712
23722
|
|
|
22713
23723
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22722,7 +23732,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22722
23732
|
var SCAN_COVERAGE = [
|
|
22723
23733
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22724
23734
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22725
|
-
{ provider: "codex", coverage:
|
|
23735
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23736
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23737
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22726
23738
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22727
23739
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22728
23740
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23055,7 +24067,7 @@ var SqliteSecurityRepository = class {
|
|
|
23055
24067
|
};
|
|
23056
24068
|
|
|
23057
24069
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23058
|
-
import { randomUUID as
|
|
24070
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23059
24071
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23060
24072
|
var IN_CHUNK = 500;
|
|
23061
24073
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23311,7 +24323,7 @@ var SqliteSharesRepository = class {
|
|
|
23311
24323
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23312
24324
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23313
24325
|
).run({
|
|
23314
|
-
id:
|
|
24326
|
+
id: randomUUID9(),
|
|
23315
24327
|
destinationId,
|
|
23316
24328
|
host: dest.host,
|
|
23317
24329
|
decision,
|
|
@@ -23460,7 +24472,7 @@ var SqliteSharesRepository = class {
|
|
|
23460
24472
|
let destinationId = destIds.get(hit.host);
|
|
23461
24473
|
if (destinationId === void 0) {
|
|
23462
24474
|
destStmt.run({
|
|
23463
|
-
id:
|
|
24475
|
+
id: randomUUID9(),
|
|
23464
24476
|
kind: hit.kind,
|
|
23465
24477
|
name: hit.name,
|
|
23466
24478
|
host: hit.host,
|
|
@@ -23476,7 +24488,7 @@ var SqliteSharesRepository = class {
|
|
|
23476
24488
|
let endpointId = endpointIds.get(endpointKey);
|
|
23477
24489
|
if (endpointId === void 0) {
|
|
23478
24490
|
endpointStmt.run({
|
|
23479
|
-
id:
|
|
24491
|
+
id: randomUUID9(),
|
|
23480
24492
|
destinationId,
|
|
23481
24493
|
method: hit.method,
|
|
23482
24494
|
transport: hit.transport,
|
|
@@ -23489,7 +24501,7 @@ var SqliteSharesRepository = class {
|
|
|
23489
24501
|
endpointIds.set(endpointKey, endpointId);
|
|
23490
24502
|
}
|
|
23491
24503
|
siteStmt.run({
|
|
23492
|
-
id:
|
|
24504
|
+
id: randomUUID9(),
|
|
23493
24505
|
endpointId,
|
|
23494
24506
|
project: input.project,
|
|
23495
24507
|
projectKey: input.projectKey,
|
|
@@ -23854,6 +24866,9 @@ function purgeSampleData(db) {
|
|
|
23854
24866
|
}
|
|
23855
24867
|
|
|
23856
24868
|
// ../../packages/persistence/src/database.ts
|
|
24869
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24870
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24871
|
+
);
|
|
23857
24872
|
function linkHost(input, hostId) {
|
|
23858
24873
|
return hostId ? { ...input, hostId } : input;
|
|
23859
24874
|
}
|
|
@@ -23875,21 +24890,34 @@ function openWithPragmas(file2) {
|
|
|
23875
24890
|
}
|
|
23876
24891
|
return db;
|
|
23877
24892
|
}
|
|
23878
|
-
function backupLegacyStore(file2) {
|
|
23879
|
-
|
|
23880
|
-
|
|
23881
|
-
|
|
23882
|
-
|
|
23883
|
-
|
|
24893
|
+
function backupLegacyStore(db, file2) {
|
|
24894
|
+
reapStalePartials(file2);
|
|
24895
|
+
const backup = backupPath(file2, "legacy");
|
|
24896
|
+
let snapshotted = false;
|
|
24897
|
+
let snapshotError;
|
|
24898
|
+
try {
|
|
24899
|
+
snapshotStore(db, backup);
|
|
24900
|
+
snapshotted = true;
|
|
24901
|
+
} catch (error51) {
|
|
24902
|
+
snapshotError = error51;
|
|
24903
|
+
} finally {
|
|
24904
|
+
db.close();
|
|
24905
|
+
}
|
|
24906
|
+
if (!snapshotted) {
|
|
24907
|
+
akaWarn(
|
|
24908
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24909
|
+
);
|
|
24910
|
+
moveStoreAside(file2, backup);
|
|
24911
|
+
return backup;
|
|
23884
24912
|
}
|
|
24913
|
+
discardStore(file2, backup);
|
|
23885
24914
|
return backup;
|
|
23886
24915
|
}
|
|
23887
24916
|
function openAndInitialize(file2) {
|
|
23888
24917
|
let db = openWithPragmas(file2);
|
|
23889
24918
|
try {
|
|
23890
24919
|
if (isForeignSqliteLineage(db)) {
|
|
23891
|
-
db
|
|
23892
|
-
const backup = backupLegacyStore(file2);
|
|
24920
|
+
const backup = backupLegacyStore(db, file2);
|
|
23893
24921
|
db = openWithPragmas(file2);
|
|
23894
24922
|
akaWarn(
|
|
23895
24923
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23933,7 +24961,7 @@ function openAndInitialize(file2) {
|
|
|
23933
24961
|
}
|
|
23934
24962
|
function openLocalDatabase(dir) {
|
|
23935
24963
|
ensureDataDirSync(dir);
|
|
23936
|
-
const file2 =
|
|
24964
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23937
24965
|
const {
|
|
23938
24966
|
db,
|
|
23939
24967
|
events,
|
|
@@ -24050,7 +25078,7 @@ function openLocalDatabase(dir) {
|
|
|
24050
25078
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24051
25079
|
if (!definitionId) continue;
|
|
24052
25080
|
inspectionFindings.insertFinding({
|
|
24053
|
-
id:
|
|
25081
|
+
id: randomUUID10(),
|
|
24054
25082
|
auditEventId: record2.scanEvent.id,
|
|
24055
25083
|
inspectionDefinitionId: definitionId,
|
|
24056
25084
|
span: finding.span,
|
|
@@ -24156,7 +25184,9 @@ function openLocalDatabase(dir) {
|
|
|
24156
25184
|
transaction,
|
|
24157
25185
|
close: () => {
|
|
24158
25186
|
db.close();
|
|
24159
|
-
}
|
|
25187
|
+
},
|
|
25188
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25189
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24160
25190
|
};
|
|
24161
25191
|
}
|
|
24162
25192
|
|
|
@@ -24180,18 +25210,32 @@ var UserGrantPolicyProvider = class {
|
|
|
24180
25210
|
}
|
|
24181
25211
|
};
|
|
24182
25212
|
|
|
25213
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25214
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25215
|
+
import {
|
|
25216
|
+
closeSync,
|
|
25217
|
+
existsSync as existsSync2,
|
|
25218
|
+
openSync,
|
|
25219
|
+
readFileSync,
|
|
25220
|
+
rmSync as rmSync3,
|
|
25221
|
+
statSync as statSync2,
|
|
25222
|
+
writeFileSync as writeFileSync2
|
|
25223
|
+
} from "fs";
|
|
25224
|
+
import { hostname as hostname3 } from "os";
|
|
25225
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25226
|
+
|
|
24183
25227
|
// ../../packages/persistence/src/finding-key.ts
|
|
24184
25228
|
import { createHash as createHash3 } from "crypto";
|
|
24185
25229
|
|
|
24186
25230
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24187
25231
|
import { createHmac, randomBytes } from "crypto";
|
|
24188
|
-
import { existsSync as
|
|
24189
|
-
import { join as
|
|
25232
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25233
|
+
import { join as join3 } from "path";
|
|
24190
25234
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24191
|
-
var
|
|
25235
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24192
25236
|
var KEY_MATERIAL_BYTES = 32;
|
|
24193
25237
|
function keyFilePath(dataDir2) {
|
|
24194
|
-
return
|
|
25238
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24195
25239
|
}
|
|
24196
25240
|
function parseKeyFile(raw) {
|
|
24197
25241
|
const parsed = JSON.parse(raw);
|
|
@@ -24229,8 +25273,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
24229
25273
|
}
|
|
24230
25274
|
};
|
|
24231
25275
|
function storedKeyVersionFloor(dataDir2) {
|
|
24232
|
-
const file2 =
|
|
24233
|
-
if (!
|
|
25276
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25277
|
+
if (!existsSync3(file2)) return 0;
|
|
24234
25278
|
let db;
|
|
24235
25279
|
try {
|
|
24236
25280
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -24255,18 +25299,36 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
24255
25299
|
db?.close();
|
|
24256
25300
|
}
|
|
24257
25301
|
}
|
|
24258
|
-
function
|
|
25302
|
+
function serializeKey(key) {
|
|
25303
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
25304
|
+
}
|
|
25305
|
+
function createKeyFile(dataDir2, key) {
|
|
24259
25306
|
ensureDataDirSync(dataDir2);
|
|
24260
25307
|
const file2 = keyFilePath(dataDir2);
|
|
24261
|
-
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
|
|
25308
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
25309
|
+
`)) return key;
|
|
25310
|
+
const winner = readFingerprintKey(dataDir2);
|
|
25311
|
+
if (winner) {
|
|
25312
|
+
tightenFile(file2);
|
|
25313
|
+
return winner;
|
|
25314
|
+
}
|
|
25315
|
+
const occupant = classifyOccupant(file2);
|
|
25316
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
25317
|
+
}
|
|
25318
|
+
function occupantMessage(file2, kind) {
|
|
25319
|
+
switch (kind) {
|
|
25320
|
+
case "symlink":
|
|
25321
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
25322
|
+
case "gone":
|
|
25323
|
+
return "exception key file was removed while it was being created";
|
|
25324
|
+
case "unknown":
|
|
25325
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
25326
|
+
}
|
|
24265
25327
|
}
|
|
24266
25328
|
function readFingerprintKey(dataDir2) {
|
|
24267
25329
|
let raw;
|
|
24268
25330
|
try {
|
|
24269
|
-
raw =
|
|
25331
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24270
25332
|
} catch (err) {
|
|
24271
25333
|
if (err.code === "ENOENT") return null;
|
|
24272
25334
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24279,7 +25341,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
24279
25341
|
tightenFile(keyFilePath(dataDir2));
|
|
24280
25342
|
return existing;
|
|
24281
25343
|
}
|
|
24282
|
-
return
|
|
25344
|
+
return createKeyFile(dataDir2, {
|
|
24283
25345
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
24284
25346
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
24285
25347
|
});
|
|
@@ -24292,21 +25354,21 @@ function fingerprintValue(key, raw) {
|
|
|
24292
25354
|
import { renameSync as renameSync3 } from "fs";
|
|
24293
25355
|
import { mkdir } from "fs/promises";
|
|
24294
25356
|
import { homedir } from "os";
|
|
24295
|
-
import { join as
|
|
25357
|
+
import { join as join4 } from "path";
|
|
24296
25358
|
function defaultDataDir() {
|
|
24297
|
-
return
|
|
25359
|
+
return join4(homedir(), ".aka");
|
|
24298
25360
|
}
|
|
24299
25361
|
function settingsDir(base = defaultDataDir()) {
|
|
24300
|
-
return
|
|
25362
|
+
return join4(base, "settings");
|
|
24301
25363
|
}
|
|
24302
25364
|
function dataDir(base = defaultDataDir()) {
|
|
24303
|
-
return
|
|
25365
|
+
return join4(base, "data");
|
|
24304
25366
|
}
|
|
24305
25367
|
function dbPath(base = defaultDataDir()) {
|
|
24306
|
-
return
|
|
25368
|
+
return join4(dataDir(base), "aka.db");
|
|
24307
25369
|
}
|
|
24308
25370
|
function keysDir(base = defaultDataDir()) {
|
|
24309
|
-
return
|
|
25371
|
+
return join4(base, "keys");
|
|
24310
25372
|
}
|
|
24311
25373
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24312
25374
|
ensureDataDirSync(dir);
|
|
@@ -24319,8 +25381,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24319
25381
|
for (const { name, dest } of moves) {
|
|
24320
25382
|
try {
|
|
24321
25383
|
ensureDataDirSync(dest);
|
|
24322
|
-
const moved =
|
|
24323
|
-
renameSync3(
|
|
25384
|
+
const moved = join4(dest, name);
|
|
25385
|
+
renameSync3(join4(base, name), moved);
|
|
24324
25386
|
tightenFile(moved);
|
|
24325
25387
|
} catch {
|
|
24326
25388
|
}
|
|
@@ -24328,10 +25390,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24328
25390
|
}
|
|
24329
25391
|
|
|
24330
25392
|
// ../../packages/persistence/src/settings.ts
|
|
24331
|
-
import { readFileSync as
|
|
24332
|
-
import { join as
|
|
25393
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25394
|
+
import { join as join5 } from "path";
|
|
25395
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24333
25396
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24334
|
-
const record2 = readJson(
|
|
25397
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24335
25398
|
if (!record2) return defaultWorkspaceSettings();
|
|
24336
25399
|
try {
|
|
24337
25400
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24342,7 +25405,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24342
25405
|
function readJson(file2) {
|
|
24343
25406
|
let text;
|
|
24344
25407
|
try {
|
|
24345
|
-
text =
|
|
25408
|
+
text = readFileSync3(file2, "utf8");
|
|
24346
25409
|
} catch {
|
|
24347
25410
|
return null;
|
|
24348
25411
|
}
|
|
@@ -24463,13 +25526,18 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24463
25526
|
import {
|
|
24464
25527
|
chmodSync as chmodSync2,
|
|
24465
25528
|
mkdirSync as mkdirSync2,
|
|
24466
|
-
readFileSync as
|
|
25529
|
+
readFileSync as readFileSync4,
|
|
24467
25530
|
renameSync as renameSync4,
|
|
24468
|
-
rmSync as
|
|
24469
|
-
statSync,
|
|
24470
|
-
writeFileSync as
|
|
25531
|
+
rmSync as rmSync4,
|
|
25532
|
+
statSync as statSync3,
|
|
25533
|
+
writeFileSync as writeFileSync3
|
|
24471
25534
|
} from "fs";
|
|
24472
|
-
import { join as
|
|
25535
|
+
import { join as join6 } from "path";
|
|
25536
|
+
var VAULT_OCCUPANT_REASON = {
|
|
25537
|
+
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25538
|
+
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
25539
|
+
unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
|
|
25540
|
+
};
|
|
24473
25541
|
var VaultKeyEpochMissingError = class extends Error {
|
|
24474
25542
|
version;
|
|
24475
25543
|
constructor(version2) {
|
|
@@ -24562,28 +25630,28 @@ function claimRotationLock(lock, owner) {
|
|
|
24562
25630
|
throw asError(err);
|
|
24563
25631
|
}
|
|
24564
25632
|
try {
|
|
24565
|
-
|
|
25633
|
+
writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
|
|
24566
25634
|
`, { mode: DATA_FILE_MODE });
|
|
24567
25635
|
return true;
|
|
24568
25636
|
} catch (err) {
|
|
24569
|
-
|
|
25637
|
+
rmSync4(lock, { recursive: true, force: true });
|
|
24570
25638
|
throw asError(err);
|
|
24571
25639
|
}
|
|
24572
25640
|
}
|
|
24573
25641
|
function acquireRotationLock(keysDir2) {
|
|
24574
|
-
const lock =
|
|
25642
|
+
const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
24575
25643
|
const owner = randomBytes2(16).toString("hex");
|
|
24576
25644
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
24577
25645
|
let held;
|
|
24578
25646
|
try {
|
|
24579
|
-
held =
|
|
25647
|
+
held = statSync3(lock);
|
|
24580
25648
|
} catch {
|
|
24581
25649
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24582
25650
|
}
|
|
24583
25651
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
24584
25652
|
const aside = `${lock}.stale.${owner}`;
|
|
24585
25653
|
try {
|
|
24586
|
-
const now =
|
|
25654
|
+
const now = statSync3(lock);
|
|
24587
25655
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
24588
25656
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24589
25657
|
}
|
|
@@ -24592,17 +25660,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
24592
25660
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
24593
25661
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
24594
25662
|
}
|
|
24595
|
-
|
|
25663
|
+
rmSync4(aside, { recursive: true, force: true });
|
|
24596
25664
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
24597
25665
|
return { lock, owner };
|
|
24598
25666
|
}
|
|
24599
25667
|
function releaseRotationLock(lease) {
|
|
24600
25668
|
try {
|
|
24601
|
-
if (
|
|
25669
|
+
if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
24602
25670
|
} catch {
|
|
24603
25671
|
return;
|
|
24604
25672
|
}
|
|
24605
|
-
|
|
25673
|
+
rmSync4(lease.lock, { recursive: true, force: true });
|
|
24606
25674
|
}
|
|
24607
25675
|
function withRotationLock(keysDir2, work) {
|
|
24608
25676
|
ensureDataDirSync(keysDir2);
|
|
@@ -24619,7 +25687,7 @@ var FileKeyProvider = class {
|
|
|
24619
25687
|
this.#keysDir = keysDir2;
|
|
24620
25688
|
}
|
|
24621
25689
|
get filePath() {
|
|
24622
|
-
return
|
|
25690
|
+
return join6(this.#keysDir, VAULT_KEY_FILENAME);
|
|
24623
25691
|
}
|
|
24624
25692
|
loadOrCreate() {
|
|
24625
25693
|
return asAsync(() => {
|
|
@@ -24649,7 +25717,7 @@ var FileKeyProvider = class {
|
|
|
24649
25717
|
#read() {
|
|
24650
25718
|
let raw;
|
|
24651
25719
|
try {
|
|
24652
|
-
raw =
|
|
25720
|
+
raw = readFileSync4(this.filePath, "utf8");
|
|
24653
25721
|
} catch (err) {
|
|
24654
25722
|
if (err.code === "ENOENT") return null;
|
|
24655
25723
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24657,34 +25725,32 @@ var FileKeyProvider = class {
|
|
|
24657
25725
|
return parseKeyring(raw);
|
|
24658
25726
|
}
|
|
24659
25727
|
/**
|
|
24660
|
-
* First mint: the keyring is
|
|
24661
|
-
*
|
|
24662
|
-
*
|
|
24663
|
-
*
|
|
24664
|
-
*
|
|
24665
|
-
*
|
|
24666
|
-
*
|
|
24667
|
-
*
|
|
25728
|
+
* First mint: the keyring is CREATED, never replaced, so two processes racing
|
|
25729
|
+
* a fresh machine cannot each mint a different epoch 1 — with tmp + rename
|
|
25730
|
+
* the loser's replace would orphan everything the winner had already sealed.
|
|
25731
|
+
* The loser re-reads and adopts the winner's keyring; it minted nothing.
|
|
25732
|
+
*
|
|
25733
|
+
* `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
|
|
25734
|
+
* at the final path, so the keyring never exists at zero length: a reader —
|
|
25735
|
+
* including the loser, re-reading in order to adopt — sees the file absent or
|
|
25736
|
+
* whole, and never mistakes a live keyring for a corrupt one. A corrupt file
|
|
25737
|
+
* still throws from the parse and is never re-minted over.
|
|
24668
25738
|
*/
|
|
24669
25739
|
#createExclusive() {
|
|
24670
25740
|
ensureDataDirSync(this.#keysDir);
|
|
24671
25741
|
const keyring = mintKeyring();
|
|
24672
|
-
|
|
24673
|
-
|
|
24674
|
-
|
|
24675
|
-
|
|
24676
|
-
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
if (!winner) {
|
|
24682
|
-
throw new Error("vault: key file vanished during first mint", { cause: err });
|
|
24683
|
-
}
|
|
24684
|
-
return winner;
|
|
25742
|
+
if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
25743
|
+
`)) return keyring;
|
|
25744
|
+
const winner = this.#read();
|
|
25745
|
+
if (!winner) {
|
|
25746
|
+
const occupant = classifyOccupant(this.filePath);
|
|
25747
|
+
throw new KeyUnclaimableError(
|
|
25748
|
+
`vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
|
|
25749
|
+
occupant.cause
|
|
25750
|
+
);
|
|
24685
25751
|
}
|
|
24686
25752
|
tightenFileMode(this.filePath);
|
|
24687
|
-
return
|
|
25753
|
+
return winner;
|
|
24688
25754
|
}
|
|
24689
25755
|
/**
|
|
24690
25756
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
@@ -24695,7 +25761,7 @@ var FileKeyProvider = class {
|
|
|
24695
25761
|
ensureDataDirSync(this.#keysDir);
|
|
24696
25762
|
const file2 = this.filePath;
|
|
24697
25763
|
const tmp = `${file2}.tmp`;
|
|
24698
|
-
|
|
25764
|
+
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
24699
25765
|
`, { mode: DATA_FILE_MODE });
|
|
24700
25766
|
renameSync4(tmp, file2);
|
|
24701
25767
|
tightenFileMode(file2);
|
|
@@ -24821,7 +25887,7 @@ function createKeyProvider(custody, keysDir2) {
|
|
|
24821
25887
|
}
|
|
24822
25888
|
|
|
24823
25889
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24824
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25890
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24825
25891
|
var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
|
|
24826
25892
|
var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
|
|
24827
25893
|
var VAULT_PURGE_POINTER_ID = "*";
|
|
@@ -24848,14 +25914,12 @@ function parsePointer(token) {
|
|
|
24848
25914
|
var SecretVault = class {
|
|
24849
25915
|
#repo;
|
|
24850
25916
|
#keys;
|
|
24851
|
-
#fingerprintKey;
|
|
24852
25917
|
#isConsented;
|
|
24853
25918
|
#verifyGrant;
|
|
24854
25919
|
#now;
|
|
24855
25920
|
constructor(deps) {
|
|
24856
25921
|
this.#repo = deps.repo;
|
|
24857
25922
|
this.#keys = deps.keys;
|
|
24858
|
-
this.#fingerprintKey = deps.fingerprintKey;
|
|
24859
25923
|
this.#isConsented = deps.isConsented;
|
|
24860
25924
|
this.#verifyGrant = deps.verifyGrant;
|
|
24861
25925
|
this.#now = deps.now ?? (() => Date.now());
|
|
@@ -24864,10 +25928,23 @@ var SecretVault = class {
|
|
|
24864
25928
|
* Store a value and return the pointer that stands for it. The same value
|
|
24865
25929
|
* always yields the same pointer on this machine — one row, one pointer id,
|
|
24866
25930
|
* one category — which is what makes dedup and reuse counting work.
|
|
25931
|
+
*
|
|
25932
|
+
* `fingerprintKey` is the exception-key epoch this value's fingerprint is
|
|
25933
|
+
* derived under — a different key from the vault's, with different rotation
|
|
25934
|
+
* semantics. It is a parameter of the WRITE rather than a constructor dep,
|
|
25935
|
+
* and a thunk rather than a value, so that the only way to reach a key is to
|
|
25936
|
+
* store something: a read-only caller never names it, and a caller whose
|
|
25937
|
+
* source mints on absence mints only once consent has actually opened the
|
|
25938
|
+
* write. `refreshFingerprints` takes its key the same way, for the same
|
|
25939
|
+
* reason.
|
|
25940
|
+
*
|
|
25941
|
+
* Resolved once per call, so the fingerprint and the version it is recorded
|
|
25942
|
+
* under can never come from two different epochs.
|
|
24867
25943
|
*/
|
|
24868
|
-
async tokenize(raw, meta3) {
|
|
25944
|
+
async tokenize(raw, meta3, fingerprintKey) {
|
|
24869
25945
|
if (!this.#isConsented()) return CONSENT_ABSENT;
|
|
24870
|
-
const
|
|
25946
|
+
const fpKey = fingerprintKey();
|
|
25947
|
+
const valueFingerprint = fingerprintValue(fpKey, raw);
|
|
24871
25948
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
24872
25949
|
const now = this.#now();
|
|
24873
25950
|
if (existing) {
|
|
@@ -24883,7 +25960,7 @@ var SecretVault = class {
|
|
|
24883
25960
|
{
|
|
24884
25961
|
pointerId: base32Encode(pointerId),
|
|
24885
25962
|
valueFingerprint,
|
|
24886
|
-
fingerprintKeyVersion:
|
|
25963
|
+
fingerprintKeyVersion: fpKey.version,
|
|
24887
25964
|
keyVersion: version2,
|
|
24888
25965
|
// Recorded so the row stays OPENABLE if the wire-format constant ever
|
|
24889
25966
|
// moves: it is part of this row's AEAD AAD. It is not a tag input —
|
|
@@ -25127,7 +26204,7 @@ var SecretVault = class {
|
|
|
25127
26204
|
purgeVault() {
|
|
25128
26205
|
const destroyed = this.#repo.purgeAll();
|
|
25129
26206
|
this.#repo.recordDeref({
|
|
25130
|
-
id:
|
|
26207
|
+
id: randomUUID12(),
|
|
25131
26208
|
pointerId: VAULT_PURGE_POINTER_ID,
|
|
25132
26209
|
at: this.#now(),
|
|
25133
26210
|
target: "human",
|
|
@@ -25196,7 +26273,7 @@ var SecretVault = class {
|
|
|
25196
26273
|
}
|
|
25197
26274
|
#audit(pointerId, opts, outcome) {
|
|
25198
26275
|
this.#repo.recordDeref({
|
|
25199
|
-
id:
|
|
26276
|
+
id: randomUUID12(),
|
|
25200
26277
|
pointerId,
|
|
25201
26278
|
at: this.#now(),
|
|
25202
26279
|
target: opts.target,
|
|
@@ -25211,15 +26288,15 @@ var SecretVault = class {
|
|
|
25211
26288
|
};
|
|
25212
26289
|
|
|
25213
26290
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
25214
|
-
import { existsSync as
|
|
25215
|
-
import { join as
|
|
26291
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
26292
|
+
import { join as join7 } from "path";
|
|
25216
26293
|
var MARKER = "warn-era-capped";
|
|
25217
26294
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
25218
26295
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
25219
|
-
const marker =
|
|
25220
|
-
if (
|
|
26296
|
+
const marker = join7(dataDir2, MARKER);
|
|
26297
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
25221
26298
|
const capped = db.policies.capCategoryActions();
|
|
25222
|
-
|
|
26299
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
25223
26300
|
`, { mode: DATA_FILE_MODE });
|
|
25224
26301
|
return { capped };
|
|
25225
26302
|
}
|
|
@@ -25283,11 +26360,11 @@ function providerFromModelId(modelId) {
|
|
|
25283
26360
|
}
|
|
25284
26361
|
|
|
25285
26362
|
// ../../packages/plugin-sdk/src/config.ts
|
|
25286
|
-
function loadConfig(base = defaultDataDir()) {
|
|
26363
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
25287
26364
|
try {
|
|
25288
26365
|
ensureLayoutDirSync(base);
|
|
25289
|
-
const settingsFile =
|
|
25290
|
-
if (
|
|
26366
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
26367
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
25291
26368
|
} catch {
|
|
25292
26369
|
}
|
|
25293
26370
|
migrateLegacyLayout(base);
|
|
@@ -25298,21 +26375,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
25298
26375
|
dbPath: dbPath(base),
|
|
25299
26376
|
settingsDir: settingsDir(base),
|
|
25300
26377
|
onboarded: settings.onboardedAt != null,
|
|
25301
|
-
provider: resolveProviderSafe()
|
|
26378
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
25302
26379
|
};
|
|
25303
26380
|
}
|
|
25304
|
-
function resolveProviderSafe() {
|
|
26381
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
25305
26382
|
try {
|
|
25306
|
-
return
|
|
26383
|
+
return resolveProviderFn();
|
|
25307
26384
|
} catch {
|
|
25308
26385
|
return { provider: "anthropic" };
|
|
25309
26386
|
}
|
|
25310
26387
|
}
|
|
25311
26388
|
|
|
25312
26389
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
25313
|
-
import { readdirSync, readFileSync as
|
|
26390
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
25314
26391
|
import { homedir as homedir2 } from "os";
|
|
25315
|
-
import { basename as
|
|
26392
|
+
import { basename as basename3, join as join10 } from "path";
|
|
25316
26393
|
|
|
25317
26394
|
// ../../packages/detections/src/egress/registry.ts
|
|
25318
26395
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -27888,7 +28965,7 @@ var gcp_service_account_default = {
|
|
|
27888
28965
|
severity: "critical",
|
|
27889
28966
|
matcher: {
|
|
27890
28967
|
type: "regex",
|
|
27891
|
-
pattern: "
|
|
28968
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27892
28969
|
flags: "g"
|
|
27893
28970
|
},
|
|
27894
28971
|
examples: [
|
|
@@ -28309,8 +29386,8 @@ function scanText(text, ruleVersions) {
|
|
|
28309
29386
|
}
|
|
28310
29387
|
|
|
28311
29388
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
28312
|
-
import { existsSync as
|
|
28313
|
-
import { basename, dirname, isAbsolute, join as
|
|
29389
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29390
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
28314
29391
|
function resolveRepoIdentity(cwd) {
|
|
28315
29392
|
try {
|
|
28316
29393
|
const root = findGitRoot(cwd);
|
|
@@ -28323,7 +29400,7 @@ function resolveRepoIdentity(cwd) {
|
|
|
28323
29400
|
// win32) so the persistence layer's `/`-separated checkout-path patterns
|
|
28324
29401
|
// (the ghost sweep + the read-side worktree filter) match it as written.
|
|
28325
29402
|
url: url2 ?? headRoot.split(sep2).join("/"),
|
|
28326
|
-
name: (url2 ? slugFromUrl(url2) : void 0) ??
|
|
29403
|
+
name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
|
|
28327
29404
|
};
|
|
28328
29405
|
} catch {
|
|
28329
29406
|
return void 0;
|
|
@@ -28343,36 +29420,36 @@ function resolveRepoNwo(cwd) {
|
|
|
28343
29420
|
function findGitRoot(start) {
|
|
28344
29421
|
let dir = start;
|
|
28345
29422
|
for (; ; ) {
|
|
28346
|
-
if (
|
|
28347
|
-
const parent =
|
|
29423
|
+
if (existsSync6(join9(dir, ".git"))) return dir;
|
|
29424
|
+
const parent = dirname2(dir);
|
|
28348
29425
|
if (parent === dir) return void 0;
|
|
28349
29426
|
dir = parent;
|
|
28350
29427
|
}
|
|
28351
29428
|
}
|
|
28352
29429
|
function resolveGitContext(root) {
|
|
28353
|
-
const dotGit =
|
|
29430
|
+
const dotGit = join9(root, ".git");
|
|
28354
29431
|
try {
|
|
28355
|
-
if (
|
|
28356
|
-
return { configPath:
|
|
29432
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
29433
|
+
return { configPath: join9(dotGit, "config"), headRoot: root };
|
|
28357
29434
|
}
|
|
28358
29435
|
} catch {
|
|
28359
29436
|
return void 0;
|
|
28360
29437
|
}
|
|
28361
29438
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
28362
29439
|
if (!target) return void 0;
|
|
28363
|
-
const gitdir = isAbsolute(target) ? target :
|
|
28364
|
-
if (
|
|
28365
|
-
return { configPath:
|
|
29440
|
+
const gitdir = isAbsolute(target) ? target : join9(root, target);
|
|
29441
|
+
if (existsSync6(join9(gitdir, "config"))) {
|
|
29442
|
+
return { configPath: join9(gitdir, "config"), headRoot: root };
|
|
28366
29443
|
}
|
|
28367
|
-
const commonRaw = safeRead(
|
|
29444
|
+
const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
|
|
28368
29445
|
if (!commonRaw) return void 0;
|
|
28369
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
28370
|
-
const headRoot =
|
|
28371
|
-
return { configPath:
|
|
29446
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
|
|
29447
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
|
|
29448
|
+
return { configPath: join9(commonGitDir, "config"), headRoot };
|
|
28372
29449
|
}
|
|
28373
29450
|
function safeRead(path) {
|
|
28374
29451
|
try {
|
|
28375
|
-
return
|
|
29452
|
+
return readFileSync5(path, "utf8");
|
|
28376
29453
|
} catch {
|
|
28377
29454
|
return void 0;
|
|
28378
29455
|
}
|
|
@@ -28423,18 +29500,23 @@ function nwoFromUrl(url2) {
|
|
|
28423
29500
|
}
|
|
28424
29501
|
|
|
28425
29502
|
// ../../packages/plugin-sdk/src/events.ts
|
|
28426
|
-
import { createHash as createHash4, randomUUID as
|
|
29503
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
29504
|
+
|
|
29505
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29506
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29507
|
+
import { fileURLToPath } from "url";
|
|
29508
|
+
import { Worker } from "worker_threads";
|
|
28427
29509
|
|
|
28428
29510
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
28429
|
-
import { arch, hostname as
|
|
29511
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
28430
29512
|
function resolveInventoryContext(input) {
|
|
28431
29513
|
const host = {
|
|
28432
29514
|
objectType: "host",
|
|
28433
29515
|
// Stable-ish machine id; os/arch live in the descriptive bag (a
|
|
28434
29516
|
// harder machine id can replace this without a schema change).
|
|
28435
|
-
identityKey:
|
|
28436
|
-
title:
|
|
28437
|
-
attributes: { host_name:
|
|
29517
|
+
identityKey: hostname4(),
|
|
29518
|
+
title: hostname4(),
|
|
29519
|
+
attributes: { host_name: hostname4(), os: platform(), os_version: release(), arch: arch() }
|
|
28438
29520
|
};
|
|
28439
29521
|
const harnessAttributes = {};
|
|
28440
29522
|
if (input.harnessVersion != null) harnessAttributes.harness_version = input.harnessVersion;
|
|
@@ -28455,27 +29537,53 @@ function resolveInventoryContext(input) {
|
|
|
28455
29537
|
}
|
|
28456
29538
|
|
|
28457
29539
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
28458
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
28459
|
-
import { join as
|
|
29540
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
29541
|
+
import { join as join11 } from "path";
|
|
28460
29542
|
|
|
28461
29543
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
28462
|
-
import { readdirSync as
|
|
28463
|
-
import { basename as
|
|
29544
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
29545
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
28464
29546
|
|
|
28465
29547
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
28466
29548
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
28467
|
-
import { existsSync as
|
|
28468
|
-
import { basename as
|
|
29549
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
29550
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
29551
|
+
|
|
29552
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
29553
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
29554
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
29555
|
+
return v;
|
|
29556
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
29557
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
29558
|
+
if (typeof v !== "string") return false;
|
|
29559
|
+
const normalized = v.trim().toLowerCase();
|
|
29560
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
29561
|
+
}, external_exports.boolean()).catch(false);
|
|
29562
|
+
var antigravityProviderEnvShape = {
|
|
29563
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
29564
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
29565
|
+
};
|
|
29566
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
29567
|
+
|
|
29568
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
29569
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
29570
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
29571
|
+
return v;
|
|
29572
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
29573
|
+
var codexProviderEnvShape = {
|
|
29574
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
29575
|
+
};
|
|
29576
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
28469
29577
|
|
|
28470
29578
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28471
|
-
import { randomUUID as
|
|
29579
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28472
29580
|
|
|
28473
29581
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28474
29582
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28475
29583
|
|
|
28476
29584
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28477
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
28478
|
-
import { join as
|
|
29585
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
29586
|
+
import { join as join13 } from "path";
|
|
28479
29587
|
|
|
28480
29588
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
28481
29589
|
function redactedPlaceholder(category) {
|
|
@@ -28717,7 +29825,6 @@ function createVaultGlue(options) {
|
|
|
28717
29825
|
const vault = new SecretVault({
|
|
28718
29826
|
repo: db.secretVault,
|
|
28719
29827
|
keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
|
|
28720
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
28721
29828
|
// Read live so a revocation applies to the very next call, not the next
|
|
28722
29829
|
// process.
|
|
28723
29830
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
|
|
@@ -28738,8 +29845,10 @@ function createVaultGlue(options) {
|
|
|
28738
29845
|
return decision.allow;
|
|
28739
29846
|
}
|
|
28740
29847
|
});
|
|
29848
|
+
let fingerprintKey;
|
|
29849
|
+
const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
|
|
28741
29850
|
const vaultWithSightings = {
|
|
28742
|
-
tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
|
|
29851
|
+
tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
|
|
28743
29852
|
detokenize: (token, opts) => vault.detokenize(token, opts),
|
|
28744
29853
|
describePointer: (token) => vault.describePointer(token),
|
|
28745
29854
|
resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
|
|
@@ -28773,7 +29882,7 @@ var UNOPENABLE_VAULT = {
|
|
|
28773
29882
|
};
|
|
28774
29883
|
|
|
28775
29884
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
28776
|
-
import { randomUUID as
|
|
29885
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
28777
29886
|
|
|
28778
29887
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
28779
29888
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -28935,7 +30044,7 @@ var StandaloneDataGateway = class {
|
|
|
28935
30044
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
28936
30045
|
const installed = this.installedScanRules();
|
|
28937
30046
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
28938
|
-
id:
|
|
30047
|
+
id: randomUUID15(),
|
|
28939
30048
|
scope: "global",
|
|
28940
30049
|
target: { ruleId },
|
|
28941
30050
|
action,
|
|
@@ -29088,19 +30197,19 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
29088
30197
|
}
|
|
29089
30198
|
|
|
29090
30199
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
29091
|
-
import { randomUUID as
|
|
30200
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
29092
30201
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
29093
30202
|
|
|
29094
30203
|
// src/remediation/redact.ts
|
|
29095
|
-
import { readFileSync as
|
|
30204
|
+
import { readFileSync as readFileSync10, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
29096
30205
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve } from "path";
|
|
29097
30206
|
|
|
29098
30207
|
// src/history/transcripts.ts
|
|
29099
|
-
import { readdirSync as
|
|
30208
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
|
|
29100
30209
|
import { homedir as homedir3 } from "os";
|
|
29101
|
-
import { join as
|
|
30210
|
+
import { join as join14 } from "path";
|
|
29102
30211
|
function transcriptsDir(home) {
|
|
29103
|
-
return
|
|
30212
|
+
return join14(home ?? homedir3(), ".claude", "projects");
|
|
29104
30213
|
}
|
|
29105
30214
|
function isRecord(value) {
|
|
29106
30215
|
return typeof value === "object" && value !== null;
|
|
@@ -29329,17 +30438,17 @@ function resolveRedactableArtifact(filePath, scope) {
|
|
|
29329
30438
|
// src/history/tail.ts
|
|
29330
30439
|
import { createHash as createHash5 } from "crypto";
|
|
29331
30440
|
import {
|
|
29332
|
-
closeSync,
|
|
30441
|
+
closeSync as closeSync2,
|
|
29333
30442
|
fstatSync,
|
|
29334
30443
|
mkdirSync as mkdirSync5,
|
|
29335
|
-
openSync,
|
|
29336
|
-
readFileSync as
|
|
30444
|
+
openSync as openSync2,
|
|
30445
|
+
readFileSync as readFileSync11,
|
|
29337
30446
|
readSync,
|
|
29338
|
-
writeFileSync as
|
|
30447
|
+
writeFileSync as writeFileSync8
|
|
29339
30448
|
} from "fs";
|
|
29340
|
-
import { join as
|
|
30449
|
+
import { join as join15 } from "path";
|
|
29341
30450
|
function offsetsDir(dataDir2) {
|
|
29342
|
-
return
|
|
30451
|
+
return join15(dataDir2, "usage-offsets");
|
|
29343
30452
|
}
|
|
29344
30453
|
var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
|
|
29345
30454
|
function safeSessionId(sessionId) {
|
|
@@ -29349,11 +30458,11 @@ function safeSessionId(sessionId) {
|
|
|
29349
30458
|
return createHash5("sha256").update(sessionId).digest("hex");
|
|
29350
30459
|
}
|
|
29351
30460
|
function offsetPath(dataDir2, sessionId) {
|
|
29352
|
-
return
|
|
30461
|
+
return join15(offsetsDir(dataDir2), safeSessionId(sessionId));
|
|
29353
30462
|
}
|
|
29354
30463
|
function readOffset(dataDir2, sessionId) {
|
|
29355
30464
|
try {
|
|
29356
|
-
const raw =
|
|
30465
|
+
const raw = readFileSync11(offsetPath(dataDir2, sessionId), "utf8");
|
|
29357
30466
|
const parsed = JSON.parse(raw);
|
|
29358
30467
|
if (typeof parsed === "object" && parsed !== null) {
|
|
29359
30468
|
const rec = parsed;
|
|
@@ -29369,7 +30478,7 @@ function writeOffset(dataDir2, sessionId, value) {
|
|
|
29369
30478
|
try {
|
|
29370
30479
|
mkdirSync5(offsetsDir(dataDir2), { recursive: true, mode: DATA_DIR_MODE });
|
|
29371
30480
|
const payload = value.lastPromptId !== void 0 ? { offset: value.offset, lastPromptId: value.lastPromptId } : { offset: value.offset };
|
|
29372
|
-
|
|
30481
|
+
writeFileSync8(offsetPath(dataDir2, sessionId), JSON.stringify(payload), {
|
|
29373
30482
|
mode: DATA_FILE_MODE
|
|
29374
30483
|
});
|
|
29375
30484
|
} catch {
|
|
@@ -29378,7 +30487,7 @@ function writeOffset(dataDir2, sessionId, value) {
|
|
|
29378
30487
|
function readTail(transcriptPath, startOffset) {
|
|
29379
30488
|
let fd;
|
|
29380
30489
|
try {
|
|
29381
|
-
fd =
|
|
30490
|
+
fd = openSync2(transcriptPath, "r");
|
|
29382
30491
|
} catch {
|
|
29383
30492
|
return { chunk: "", nextOffset: startOffset };
|
|
29384
30493
|
}
|
|
@@ -29404,22 +30513,22 @@ function readTail(transcriptPath, startOffset) {
|
|
|
29404
30513
|
return { chunk: "", nextOffset: startOffset };
|
|
29405
30514
|
} finally {
|
|
29406
30515
|
try {
|
|
29407
|
-
|
|
30516
|
+
closeSync2(fd);
|
|
29408
30517
|
} catch {
|
|
29409
30518
|
}
|
|
29410
30519
|
}
|
|
29411
30520
|
}
|
|
29412
30521
|
|
|
29413
30522
|
// src/history/tail-scrub.ts
|
|
29414
|
-
import { readFileSync as
|
|
30523
|
+
import { readFileSync as readFileSync12, renameSync as renameSync6, rmSync as rmSync6, statSync as statSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
29415
30524
|
var DEFAULT_MAX_SCRUB_BYTES = 32 * 1024 * 1024;
|
|
29416
30525
|
async function scrubTranscriptTail(filePath, deps) {
|
|
29417
30526
|
try {
|
|
29418
30527
|
const realPath = resolveRedactableArtifact(filePath, deps.scope);
|
|
29419
30528
|
if (realPath === null) return null;
|
|
29420
|
-
const statBefore =
|
|
30529
|
+
const statBefore = statSync7(realPath);
|
|
29421
30530
|
if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
|
|
29422
|
-
const content =
|
|
30531
|
+
const content = readFileSync12(realPath, "utf8");
|
|
29423
30532
|
const lines = content.split("\n");
|
|
29424
30533
|
let rewritten = 0;
|
|
29425
30534
|
for (const [i, line] of lines.entries()) {
|
|
@@ -29433,16 +30542,16 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
29433
30542
|
if (rewritten === 0) return { rewritten: 0 };
|
|
29434
30543
|
const tmpPath = `${realPath}.aka-scrub.tmp`;
|
|
29435
30544
|
try {
|
|
29436
|
-
|
|
29437
|
-
const statNow =
|
|
30545
|
+
writeFileSync9(tmpPath, lines.join("\n"), { mode: statBefore.mode & 511 });
|
|
30546
|
+
const statNow = statSync7(realPath);
|
|
29438
30547
|
if (statNow.size !== statBefore.size || statNow.mtimeMs !== statBefore.mtimeMs) {
|
|
29439
|
-
|
|
30548
|
+
rmSync6(tmpPath, { force: true, recursive: true });
|
|
29440
30549
|
return null;
|
|
29441
30550
|
}
|
|
29442
30551
|
renameSync6(tmpPath, realPath);
|
|
29443
30552
|
} catch {
|
|
29444
30553
|
try {
|
|
29445
|
-
|
|
30554
|
+
rmSync6(tmpPath, { force: true, recursive: true });
|
|
29446
30555
|
} catch {
|
|
29447
30556
|
}
|
|
29448
30557
|
return null;
|