@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/backfill.js
CHANGED
|
@@ -492,16 +492,15 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/backfill.ts
|
|
495
|
-
import { fileURLToPath } from "url";
|
|
495
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
496
496
|
|
|
497
497
|
// ../../packages/plugin-sdk/src/config.ts
|
|
498
|
-
import { existsSync as
|
|
499
|
-
import { join as
|
|
498
|
+
import { existsSync as existsSync5 } from "fs";
|
|
499
|
+
import { join as join8 } from "path";
|
|
500
500
|
|
|
501
501
|
// ../../packages/persistence/src/database.ts
|
|
502
|
-
import { randomUUID as
|
|
503
|
-
import {
|
|
504
|
-
import { join, sep } from "path";
|
|
502
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
503
|
+
import { join as join2, sep } from "path";
|
|
505
504
|
import { DatabaseSync } from "node:sqlite";
|
|
506
505
|
|
|
507
506
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -581,6 +580,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
581
580
|
{
|
|
582
581
|
tag: "0018_serious_tana_nile",
|
|
583
582
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
tag: "0019_audit_started_at_index",
|
|
586
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
590
|
+
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');"
|
|
584
591
|
}
|
|
585
592
|
];
|
|
586
593
|
|
|
@@ -15318,7 +15325,17 @@ var Finding = external_exports.object({
|
|
|
15318
15325
|
}).meta({ id: "Finding" });
|
|
15319
15326
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15320
15327
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15321
|
-
var FindingProvider = external_exports.enum([
|
|
15328
|
+
var FindingProvider = external_exports.enum([
|
|
15329
|
+
"claudecode",
|
|
15330
|
+
"claudedesktop",
|
|
15331
|
+
"cursor",
|
|
15332
|
+
"copilot",
|
|
15333
|
+
"chatgpt",
|
|
15334
|
+
"claudeai",
|
|
15335
|
+
"codex",
|
|
15336
|
+
"antigravity",
|
|
15337
|
+
"api"
|
|
15338
|
+
]).meta({ id: "FindingProvider" });
|
|
15322
15339
|
var FindingCategory = external_exports.enum([
|
|
15323
15340
|
"secret",
|
|
15324
15341
|
"pii",
|
|
@@ -15372,7 +15389,16 @@ var FindingInstance = external_exports.object({
|
|
|
15372
15389
|
confidence: external_exports.number().min(0).max(1),
|
|
15373
15390
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15374
15391
|
// that predate the resolution feature stay valid.
|
|
15375
|
-
status: FindingStatus.optional()
|
|
15392
|
+
status: FindingStatus.optional(),
|
|
15393
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15394
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15395
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15396
|
+
// MOST RECENT detection event, not the first.
|
|
15397
|
+
eventId: external_exports.string().optional(),
|
|
15398
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15399
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15400
|
+
// outside a session.
|
|
15401
|
+
sessionId: external_exports.string().optional()
|
|
15376
15402
|
}).meta({ id: "FindingInstance" });
|
|
15377
15403
|
var FindingGroup = external_exports.object({
|
|
15378
15404
|
id: external_exports.string(),
|
|
@@ -15416,7 +15442,11 @@ var FindingFacets = external_exports.object({
|
|
|
15416
15442
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15417
15443
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15418
15444
|
// counted under no value.
|
|
15419
|
-
status: external_exports.array(FindingFacetItem)
|
|
15445
|
+
status: external_exports.array(FindingFacetItem),
|
|
15446
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15447
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15448
|
+
// because a group spans tools.
|
|
15449
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15420
15450
|
}).meta({ id: "FindingFacets" });
|
|
15421
15451
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15422
15452
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15434,6 +15464,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15434
15464
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15435
15465
|
// session → findings drilldown). Findings without a session never match.
|
|
15436
15466
|
sessionId: external_exports.string().optional(),
|
|
15467
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15468
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15469
|
+
// means all time — this list has no default window.
|
|
15470
|
+
from: external_exports.iso.datetime().optional(),
|
|
15471
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15472
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15473
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15474
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15475
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15476
|
+
includeId: external_exports.string().optional(),
|
|
15437
15477
|
groupBy: external_exports.literal("type").optional(),
|
|
15438
15478
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15439
15479
|
cursor: external_exports.string().optional()
|
|
@@ -15478,15 +15518,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15478
15518
|
detection: FindingDetectionRef,
|
|
15479
15519
|
policy: FindingPolicyRef
|
|
15480
15520
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15521
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15522
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15523
|
+
severity: external_exports.array(Severity).optional(),
|
|
15524
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15525
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15526
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15527
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15528
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15529
|
+
// the grouped query's group-level fold.
|
|
15530
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15531
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15532
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15533
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15534
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15535
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15536
|
+
repo: external_exports.string().optional(),
|
|
15537
|
+
file: external_exports.string().optional(),
|
|
15538
|
+
q: external_exports.string().optional(),
|
|
15539
|
+
sessionId: external_exports.string().optional(),
|
|
15540
|
+
from: external_exports.iso.datetime().optional(),
|
|
15541
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15542
|
+
cursor: external_exports.string().optional()
|
|
15543
|
+
});
|
|
15544
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15545
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15546
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15547
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15548
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15549
|
+
// dimension still excludes its own filter.
|
|
15550
|
+
facets: FindingFacets,
|
|
15551
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15552
|
+
nextCursor: external_exports.string().nullable()
|
|
15553
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15554
|
+
var FindingLocationFile = external_exports.object({
|
|
15555
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15556
|
+
// with no file attribution).
|
|
15557
|
+
file: external_exports.string(),
|
|
15558
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15559
|
+
maxSeverity: Severity,
|
|
15560
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15561
|
+
// Folded from the instances' derived statuses with the same
|
|
15562
|
+
// open-dominates precedence a group uses.
|
|
15563
|
+
status: FindingStatus.optional(),
|
|
15564
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15565
|
+
// chips, and the count is what conveys scale.
|
|
15566
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15567
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15568
|
+
var FindingLocationRepo = external_exports.object({
|
|
15569
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15570
|
+
repo: external_exports.string(),
|
|
15571
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15572
|
+
maxSeverity: Severity,
|
|
15573
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15574
|
+
status: FindingStatus.optional(),
|
|
15575
|
+
files: external_exports.array(FindingLocationFile)
|
|
15576
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15577
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15578
|
+
severity: external_exports.array(Severity).optional(),
|
|
15579
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15580
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15581
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15582
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15583
|
+
// instances that match, and folds its status from those.
|
|
15584
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15585
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15586
|
+
q: external_exports.string().optional(),
|
|
15587
|
+
sessionId: external_exports.string().optional(),
|
|
15588
|
+
from: external_exports.iso.datetime().optional(),
|
|
15589
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15590
|
+
});
|
|
15591
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15592
|
+
totals: external_exports.object({
|
|
15593
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15594
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15595
|
+
files: external_exports.number().int().nonnegative()
|
|
15596
|
+
}),
|
|
15597
|
+
/** Sorted by max severity, then most recent. */
|
|
15598
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15599
|
+
/** Whether `limit` truncated the repo list. */
|
|
15600
|
+
hasMore: external_exports.boolean()
|
|
15601
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15481
15602
|
|
|
15482
15603
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15483
|
-
var Harness = external_exports.enum([
|
|
15604
|
+
var Harness = external_exports.enum([
|
|
15605
|
+
"claudecode",
|
|
15606
|
+
"cursor",
|
|
15607
|
+
"copilot",
|
|
15608
|
+
"codex",
|
|
15609
|
+
"antigravity",
|
|
15610
|
+
"windsurf",
|
|
15611
|
+
"claudedesktop",
|
|
15612
|
+
"chatgpt",
|
|
15613
|
+
"claudeai",
|
|
15614
|
+
"api"
|
|
15615
|
+
]).meta({ id: "Harness" });
|
|
15484
15616
|
var TOOL_TO_HARNESS = {
|
|
15485
15617
|
"claude-code": "claudecode",
|
|
15486
15618
|
"claude-desktop": "claudedesktop",
|
|
15487
15619
|
"github-copilot": "copilot",
|
|
15488
15620
|
cursor: "cursor",
|
|
15489
|
-
chatgpt: "chatgpt"
|
|
15621
|
+
chatgpt: "chatgpt",
|
|
15622
|
+
codex: "codex",
|
|
15623
|
+
antigravity: "antigravity",
|
|
15624
|
+
"claude-ai": "claudeai"
|
|
15490
15625
|
};
|
|
15491
15626
|
function harnessFromTool(tool) {
|
|
15492
15627
|
return TOOL_TO_HARNESS[tool] ?? tool;
|
|
@@ -15947,7 +16082,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15947
16082
|
// ../../packages/schema/src/zod/event.ts
|
|
15948
16083
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15949
16084
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15950
|
-
var SourceTool = external_exports.enum([
|
|
16085
|
+
var SourceTool = external_exports.enum([
|
|
16086
|
+
"claude-code",
|
|
16087
|
+
"claude-desktop",
|
|
16088
|
+
"cursor",
|
|
16089
|
+
"chatgpt",
|
|
16090
|
+
"claude-ai",
|
|
16091
|
+
"github-copilot",
|
|
16092
|
+
"codex",
|
|
16093
|
+
"antigravity",
|
|
16094
|
+
"cli",
|
|
16095
|
+
"unknown"
|
|
16096
|
+
]).meta({ id: "SourceTool" });
|
|
15951
16097
|
var EventMetadata = external_exports.object({
|
|
15952
16098
|
sessionId: external_exports.string().optional(),
|
|
15953
16099
|
repo: external_exports.string().optional(),
|
|
@@ -16018,7 +16164,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16018
16164
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16019
16165
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16020
16166
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16021
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16167
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16022
16168
|
var AccessCounts = external_exports.object({
|
|
16023
16169
|
open: external_exports.number().int().nonnegative(),
|
|
16024
16170
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16287,6 +16433,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16287
16433
|
useCount: true,
|
|
16288
16434
|
conditions: true
|
|
16289
16435
|
});
|
|
16436
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16290
16437
|
|
|
16291
16438
|
// ../../packages/schema/src/zod/rule.ts
|
|
16292
16439
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17097,6 +17244,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17097
17244
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17098
17245
|
}).meta({ id: "EgressWriteSummary" });
|
|
17099
17246
|
|
|
17247
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17248
|
+
var confirmation = external_exports.string().optional();
|
|
17249
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17250
|
+
reference: external_exports.string(),
|
|
17251
|
+
scope: external_exports.string(),
|
|
17252
|
+
reason: external_exports.string(),
|
|
17253
|
+
confirmation
|
|
17254
|
+
});
|
|
17255
|
+
var AddExceptionInput = external_exports.object({
|
|
17256
|
+
ruleId: external_exports.string(),
|
|
17257
|
+
value: external_exports.string(),
|
|
17258
|
+
scope: external_exports.string(),
|
|
17259
|
+
reason: external_exports.string(),
|
|
17260
|
+
confirmation
|
|
17261
|
+
});
|
|
17262
|
+
var GrantRevealInput = external_exports.object({
|
|
17263
|
+
pointer: external_exports.string(),
|
|
17264
|
+
scope: external_exports.string(),
|
|
17265
|
+
justification: external_exports.string(),
|
|
17266
|
+
confirmation
|
|
17267
|
+
});
|
|
17268
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17269
|
+
id: external_exports.string(),
|
|
17270
|
+
reason: external_exports.string()
|
|
17271
|
+
});
|
|
17272
|
+
var RotateKeyInput = external_exports.object({
|
|
17273
|
+
confirmation: external_exports.string()
|
|
17274
|
+
});
|
|
17275
|
+
|
|
17100
17276
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17101
17277
|
function toApiAction(dbVal) {
|
|
17102
17278
|
const map2 = {
|
|
@@ -17152,6 +17328,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17152
17328
|
repo: r.repo,
|
|
17153
17329
|
file: r.file,
|
|
17154
17330
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17331
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17332
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17155
17333
|
action: toApiAction(effectiveDbAction),
|
|
17156
17334
|
detectedAt: r.occurredAt,
|
|
17157
17335
|
confidence: r.confidence,
|
|
@@ -17283,14 +17461,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17283
17461
|
}
|
|
17284
17462
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17285
17463
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17464
|
+
function compareFindingGroupOrder(a, b) {
|
|
17465
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17466
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17467
|
+
const severityDiff = rankA - rankB;
|
|
17468
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17469
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17470
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17471
|
+
return a.id.localeCompare(b.id);
|
|
17472
|
+
}
|
|
17286
17473
|
function sortFindingGroups(groups) {
|
|
17287
|
-
return [...groups].sort(
|
|
17288
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17289
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17290
|
-
const severityDiff = rankA - rankB;
|
|
17291
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17292
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17293
|
-
});
|
|
17474
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17294
17475
|
}
|
|
17295
17476
|
function computeFindingFacets(allGroups, opts) {
|
|
17296
17477
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17346,15 +17527,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17346
17527
|
for (const g of forStatus) {
|
|
17347
17528
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17348
17529
|
}
|
|
17349
|
-
const
|
|
17530
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17531
|
+
return {
|
|
17532
|
+
severity: toItems2(severityMap),
|
|
17533
|
+
provider: toItems2(providerMap),
|
|
17534
|
+
action: toItems2(actionMap),
|
|
17535
|
+
subtype: toItems2(subtypeMap),
|
|
17536
|
+
status: toItems2(statusMap)
|
|
17537
|
+
};
|
|
17538
|
+
}
|
|
17539
|
+
|
|
17540
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17541
|
+
function rowHaystack(row) {
|
|
17542
|
+
return [
|
|
17543
|
+
row.ruleId,
|
|
17544
|
+
row.category,
|
|
17545
|
+
row.maskedMatch,
|
|
17546
|
+
row.repo,
|
|
17547
|
+
row.file,
|
|
17548
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17549
|
+
row.id
|
|
17550
|
+
].join(" ").toLowerCase();
|
|
17551
|
+
}
|
|
17552
|
+
function matchesDimension(row, opts, dimension) {
|
|
17553
|
+
switch (dimension) {
|
|
17554
|
+
case "severity":
|
|
17555
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17556
|
+
case "subtype":
|
|
17557
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17558
|
+
case "providers":
|
|
17559
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17560
|
+
case "actions":
|
|
17561
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17562
|
+
case "statuses":
|
|
17563
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17564
|
+
case "tools":
|
|
17565
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17566
|
+
case "repo":
|
|
17567
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17568
|
+
case "file":
|
|
17569
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17570
|
+
case "q":
|
|
17571
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17572
|
+
}
|
|
17573
|
+
}
|
|
17574
|
+
var DIMENSIONS = [
|
|
17575
|
+
"severity",
|
|
17576
|
+
"subtype",
|
|
17577
|
+
"providers",
|
|
17578
|
+
"actions",
|
|
17579
|
+
"statuses",
|
|
17580
|
+
"tools",
|
|
17581
|
+
"repo",
|
|
17582
|
+
"file",
|
|
17583
|
+
"q"
|
|
17584
|
+
];
|
|
17585
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17586
|
+
for (const dimension of DIMENSIONS) {
|
|
17587
|
+
if (dimension === except) continue;
|
|
17588
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17589
|
+
}
|
|
17590
|
+
return true;
|
|
17591
|
+
}
|
|
17592
|
+
function toItems(counts) {
|
|
17593
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17594
|
+
}
|
|
17595
|
+
function bump(counts, value) {
|
|
17596
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17597
|
+
}
|
|
17598
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17599
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17600
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17601
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17602
|
+
const action = /* @__PURE__ */ new Map();
|
|
17603
|
+
const status = /* @__PURE__ */ new Map();
|
|
17604
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17605
|
+
return {
|
|
17606
|
+
add(row) {
|
|
17607
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17608
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17609
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17610
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17611
|
+
}
|
|
17612
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17613
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17614
|
+
bump(status, row.status);
|
|
17615
|
+
}
|
|
17616
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17617
|
+
bump(tool, row.toolName);
|
|
17618
|
+
}
|
|
17619
|
+
},
|
|
17620
|
+
facets: () => ({
|
|
17621
|
+
severity: toItems(severity),
|
|
17622
|
+
subtype: toItems(subtype),
|
|
17623
|
+
provider: toItems(provider),
|
|
17624
|
+
action: toItems(action),
|
|
17625
|
+
status: toItems(status),
|
|
17626
|
+
tool: toItems(tool)
|
|
17627
|
+
})
|
|
17628
|
+
};
|
|
17629
|
+
}
|
|
17630
|
+
function toInstanceDetail(row) {
|
|
17631
|
+
const category = toApiCategory(row.category);
|
|
17632
|
+
return {
|
|
17633
|
+
id: row.id,
|
|
17634
|
+
provider: toApiProvider(row.sourceTool),
|
|
17635
|
+
repo: row.repo,
|
|
17636
|
+
file: row.file,
|
|
17637
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17638
|
+
eventId: row.eventId,
|
|
17639
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17640
|
+
action: toApiAction(row.actionTaken),
|
|
17641
|
+
detectedAt: row.occurredAt,
|
|
17642
|
+
confidence: row.confidence,
|
|
17643
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17644
|
+
groupId: row.ruleId,
|
|
17645
|
+
category,
|
|
17646
|
+
subtype: row.ruleId,
|
|
17647
|
+
severity: row.severity,
|
|
17648
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17649
|
+
detection: { id: row.ruleId, name: null },
|
|
17650
|
+
policy: { id: `category:${category}`, name: category }
|
|
17651
|
+
};
|
|
17652
|
+
}
|
|
17653
|
+
var SEVERITY_ORDER2 = {
|
|
17654
|
+
critical: 0,
|
|
17655
|
+
high: 1,
|
|
17656
|
+
medium: 2,
|
|
17657
|
+
low: 3
|
|
17658
|
+
};
|
|
17659
|
+
function newLocationAccumulator() {
|
|
17350
17660
|
return {
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17661
|
+
instanceCount: 0,
|
|
17662
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17663
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17664
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17665
|
+
maxSeverity: "low",
|
|
17666
|
+
latestDetectedAt: "",
|
|
17667
|
+
statuses: [],
|
|
17668
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17356
17669
|
};
|
|
17357
17670
|
}
|
|
17671
|
+
function addToLocation(acc, row) {
|
|
17672
|
+
acc.instanceCount += 1;
|
|
17673
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17674
|
+
if (rank < acc.maxSeverityRank) {
|
|
17675
|
+
acc.maxSeverityRank = rank;
|
|
17676
|
+
acc.maxSeverity = row.severity;
|
|
17677
|
+
}
|
|
17678
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17679
|
+
acc.statuses.push(row.status);
|
|
17680
|
+
acc.ruleIds.add(row.ruleId);
|
|
17681
|
+
}
|
|
17358
17682
|
|
|
17359
17683
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17360
17684
|
var InstalledPack = external_exports.object({
|
|
@@ -17495,6 +17819,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17495
17819
|
revealGrantId: external_exports.string().nullable(),
|
|
17496
17820
|
sightings: external_exports.array(VaultSighting)
|
|
17497
17821
|
});
|
|
17822
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17823
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17824
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17825
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17826
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17827
|
+
// Opaque; names the last row of the page just served.
|
|
17828
|
+
cursor: external_exports.string().optional()
|
|
17829
|
+
});
|
|
17830
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17831
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17832
|
+
// independent, so paging never changes what the count claims.
|
|
17833
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17834
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17835
|
+
// `null` once the last page is reached.
|
|
17836
|
+
nextCursor: external_exports.string().nullable()
|
|
17837
|
+
});
|
|
17838
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17839
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17840
|
+
cursor: external_exports.string().optional()
|
|
17841
|
+
});
|
|
17842
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17843
|
+
// Reused values across the whole store — the number the section's claim
|
|
17844
|
+
// ("values detected in more than one place") is about.
|
|
17845
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17846
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17847
|
+
nextCursor: external_exports.string().nullable()
|
|
17848
|
+
});
|
|
17849
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17850
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17851
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17852
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17853
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17854
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17855
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17856
|
+
cursor: external_exports.string().optional()
|
|
17857
|
+
});
|
|
17858
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17859
|
+
items: external_exports.array(VaultDeref),
|
|
17860
|
+
nextCursor: external_exports.string().nullable(),
|
|
17861
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17862
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17863
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17864
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17865
|
+
});
|
|
17498
17866
|
var VaultKeyCustody = external_exports.string();
|
|
17499
17867
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17500
17868
|
var VAULT_CONSENT_VERSION = 1;
|
|
@@ -17871,7 +18239,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17871
18239
|
// Omit for both kinds.
|
|
17872
18240
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17873
18241
|
});
|
|
17874
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18242
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17875
18243
|
var ScanCoverageProvider = external_exports.object({
|
|
17876
18244
|
provider: Provider,
|
|
17877
18245
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18124,6 +18492,195 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18124
18492
|
);
|
|
18125
18493
|
}
|
|
18126
18494
|
|
|
18495
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18496
|
+
import { randomUUID } from "crypto";
|
|
18497
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18498
|
+
import { basename, dirname, join } from "path";
|
|
18499
|
+
|
|
18500
|
+
// ../../packages/persistence/src/paths.ts
|
|
18501
|
+
import {
|
|
18502
|
+
chmodSync,
|
|
18503
|
+
linkSync,
|
|
18504
|
+
lstatSync,
|
|
18505
|
+
mkdirSync,
|
|
18506
|
+
renameSync,
|
|
18507
|
+
rmSync,
|
|
18508
|
+
writeFileSync
|
|
18509
|
+
} from "fs";
|
|
18510
|
+
import { threadId } from "worker_threads";
|
|
18511
|
+
var DATA_DIR_MODE = 448;
|
|
18512
|
+
var DATA_FILE_MODE = 384;
|
|
18513
|
+
var DB_FILENAME = "aka.db";
|
|
18514
|
+
function isSymlink(path) {
|
|
18515
|
+
try {
|
|
18516
|
+
return lstatSync(path).isSymbolicLink();
|
|
18517
|
+
} catch {
|
|
18518
|
+
return false;
|
|
18519
|
+
}
|
|
18520
|
+
}
|
|
18521
|
+
function chmodBestEffort(path, mode) {
|
|
18522
|
+
if (isSymlink(path)) return;
|
|
18523
|
+
try {
|
|
18524
|
+
chmodSync(path, mode);
|
|
18525
|
+
} catch {
|
|
18526
|
+
}
|
|
18527
|
+
}
|
|
18528
|
+
function tightenDir(dir) {
|
|
18529
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18530
|
+
}
|
|
18531
|
+
function ensureDataDirSync(dir) {
|
|
18532
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18533
|
+
tightenDir(dir);
|
|
18534
|
+
}
|
|
18535
|
+
function dbSidecars(file2) {
|
|
18536
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18537
|
+
}
|
|
18538
|
+
function tightenFile(file2) {
|
|
18539
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18540
|
+
}
|
|
18541
|
+
function tightenPerms(file2) {
|
|
18542
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18543
|
+
}
|
|
18544
|
+
function classifyOccupant(file2) {
|
|
18545
|
+
try {
|
|
18546
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
18547
|
+
return { kind: "gone" };
|
|
18548
|
+
} catch (err) {
|
|
18549
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
18550
|
+
return { kind: "unknown", cause: err };
|
|
18551
|
+
}
|
|
18552
|
+
}
|
|
18553
|
+
var KeyUnclaimableError = class extends Error {
|
|
18554
|
+
code = "key-unclaimable";
|
|
18555
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
18556
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
18557
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
18558
|
+
// captured and then lost, which is worse than its plain absence.
|
|
18559
|
+
constructor(message, cause) {
|
|
18560
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
18561
|
+
this.name = "KeyUnclaimableError";
|
|
18562
|
+
}
|
|
18563
|
+
};
|
|
18564
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
18565
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
18566
|
+
try {
|
|
18567
|
+
rmSync(tmp, { force: true });
|
|
18568
|
+
} catch {
|
|
18569
|
+
}
|
|
18570
|
+
let created;
|
|
18571
|
+
try {
|
|
18572
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18573
|
+
created = publishByLink(tmp, file2, data);
|
|
18574
|
+
} finally {
|
|
18575
|
+
try {
|
|
18576
|
+
rmSync(tmp, { force: true });
|
|
18577
|
+
} catch {
|
|
18578
|
+
}
|
|
18579
|
+
}
|
|
18580
|
+
if (created) tightenFile(file2);
|
|
18581
|
+
return created;
|
|
18582
|
+
}
|
|
18583
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
18584
|
+
function publishByLink(tmp, file2, data) {
|
|
18585
|
+
try {
|
|
18586
|
+
linkSync(tmp, file2);
|
|
18587
|
+
return true;
|
|
18588
|
+
} catch (err) {
|
|
18589
|
+
const code = err.code;
|
|
18590
|
+
if (code === "EEXIST") return false;
|
|
18591
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18592
|
+
}
|
|
18593
|
+
try {
|
|
18594
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18595
|
+
return true;
|
|
18596
|
+
} catch (err) {
|
|
18597
|
+
if (err.code === "EEXIST") return false;
|
|
18598
|
+
throw err;
|
|
18599
|
+
}
|
|
18600
|
+
}
|
|
18601
|
+
|
|
18602
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18603
|
+
function backupPath(file2, tag) {
|
|
18604
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18605
|
+
}
|
|
18606
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18607
|
+
function reapStalePartials(file2) {
|
|
18608
|
+
const dir = dirname(file2);
|
|
18609
|
+
const prefix = `${basename(file2)}.`;
|
|
18610
|
+
let entries;
|
|
18611
|
+
try {
|
|
18612
|
+
entries = readdirSync(dir);
|
|
18613
|
+
} catch {
|
|
18614
|
+
return;
|
|
18615
|
+
}
|
|
18616
|
+
for (const name of entries) {
|
|
18617
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18618
|
+
const partial2 = join(dir, name);
|
|
18619
|
+
try {
|
|
18620
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18621
|
+
rmSync2(partial2, { force: true });
|
|
18622
|
+
}
|
|
18623
|
+
} catch {
|
|
18624
|
+
}
|
|
18625
|
+
}
|
|
18626
|
+
}
|
|
18627
|
+
function snapshotStore(db, backup) {
|
|
18628
|
+
const partial2 = `${backup}.partial`;
|
|
18629
|
+
try {
|
|
18630
|
+
rmSync2(partial2, { force: true });
|
|
18631
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18632
|
+
tightenFile(partial2);
|
|
18633
|
+
renameSync2(partial2, backup);
|
|
18634
|
+
} catch (error51) {
|
|
18635
|
+
try {
|
|
18636
|
+
rmSync2(partial2, { force: true });
|
|
18637
|
+
} catch {
|
|
18638
|
+
}
|
|
18639
|
+
throw error51;
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
function moveStoreAside(file2, backup) {
|
|
18643
|
+
const undo = [];
|
|
18644
|
+
renameSync2(file2, backup);
|
|
18645
|
+
undo.push([backup, file2]);
|
|
18646
|
+
try {
|
|
18647
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18648
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18649
|
+
try {
|
|
18650
|
+
renameSync2(sidecar, moved);
|
|
18651
|
+
undo.push([moved, sidecar]);
|
|
18652
|
+
} catch {
|
|
18653
|
+
rmSync2(sidecar, { force: true });
|
|
18654
|
+
}
|
|
18655
|
+
}
|
|
18656
|
+
} catch (error51) {
|
|
18657
|
+
for (const [from, to] of undo.reverse()) {
|
|
18658
|
+
try {
|
|
18659
|
+
renameSync2(from, to);
|
|
18660
|
+
} catch {
|
|
18661
|
+
}
|
|
18662
|
+
}
|
|
18663
|
+
throw error51;
|
|
18664
|
+
}
|
|
18665
|
+
tightenPerms(backup);
|
|
18666
|
+
}
|
|
18667
|
+
function discardStore(file2, backup) {
|
|
18668
|
+
try {
|
|
18669
|
+
rmSync2(file2, { force: true });
|
|
18670
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18671
|
+
rmSync2(sidecar, { force: true });
|
|
18672
|
+
}
|
|
18673
|
+
} catch (error51) {
|
|
18674
|
+
if (existsSync(file2)) {
|
|
18675
|
+
try {
|
|
18676
|
+
rmSync2(backup, { force: true });
|
|
18677
|
+
} catch {
|
|
18678
|
+
}
|
|
18679
|
+
}
|
|
18680
|
+
throw error51;
|
|
18681
|
+
}
|
|
18682
|
+
}
|
|
18683
|
+
|
|
18127
18684
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18128
18685
|
function escapeLikePattern(s) {
|
|
18129
18686
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18265,55 +18822,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18265
18822
|
return out;
|
|
18266
18823
|
}
|
|
18267
18824
|
|
|
18268
|
-
// ../../packages/persistence/src/paths.ts
|
|
18269
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18270
|
-
var DATA_DIR_MODE = 448;
|
|
18271
|
-
var DATA_FILE_MODE = 384;
|
|
18272
|
-
var DB_FILENAME = "aka.db";
|
|
18273
|
-
function chmodBestEffort(path, mode) {
|
|
18274
|
-
try {
|
|
18275
|
-
chmodSync(path, mode);
|
|
18276
|
-
} catch {
|
|
18277
|
-
}
|
|
18278
|
-
}
|
|
18279
|
-
function tightenDir(dir) {
|
|
18280
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18281
|
-
}
|
|
18282
|
-
function ensureDataDirSync(dir) {
|
|
18283
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18284
|
-
tightenDir(dir);
|
|
18285
|
-
}
|
|
18286
|
-
function dbSidecars(file2) {
|
|
18287
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18288
|
-
}
|
|
18289
|
-
function tightenFile(file2) {
|
|
18290
|
-
try {
|
|
18291
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18292
|
-
} catch {
|
|
18293
|
-
}
|
|
18294
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18295
|
-
}
|
|
18296
|
-
function tightenPerms(file2) {
|
|
18297
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18298
|
-
}
|
|
18299
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18300
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18301
|
-
try {
|
|
18302
|
-
rmSync(tmp, { force: true });
|
|
18303
|
-
} catch {
|
|
18304
|
-
}
|
|
18305
|
-
try {
|
|
18306
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18307
|
-
renameSync(tmp, file2);
|
|
18308
|
-
} finally {
|
|
18309
|
-
try {
|
|
18310
|
-
rmSync(tmp, { force: true });
|
|
18311
|
-
} catch {
|
|
18312
|
-
}
|
|
18313
|
-
}
|
|
18314
|
-
tightenFile(file2);
|
|
18315
|
-
}
|
|
18316
|
-
|
|
18317
18825
|
// ../../packages/persistence/src/migrations.ts
|
|
18318
18826
|
function describeObject(object2) {
|
|
18319
18827
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18429,9 +18937,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18429
18937
|
}
|
|
18430
18938
|
}
|
|
18431
18939
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
|
|
18940
|
+
reapStalePartials(file2);
|
|
18941
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18942
|
+
snapshotStore(db, backup);
|
|
18435
18943
|
return backup;
|
|
18436
18944
|
}
|
|
18437
18945
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18775,6 +19283,25 @@ function parseJsonObject(s) {
|
|
|
18775
19283
|
return void 0;
|
|
18776
19284
|
}
|
|
18777
19285
|
|
|
19286
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19287
|
+
function encodeKeysetCursor(payload) {
|
|
19288
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19289
|
+
}
|
|
19290
|
+
function decodeKeysetCursor(cursor) {
|
|
19291
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19292
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19293
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19294
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19295
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19296
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19297
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19298
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19299
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19300
|
+
return parsed;
|
|
19301
|
+
}
|
|
19302
|
+
return null;
|
|
19303
|
+
}
|
|
19304
|
+
|
|
18778
19305
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18779
19306
|
var DAY_MS = 864e5;
|
|
18780
19307
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18820,16 +19347,6 @@ function utcWindow(nowMs) {
|
|
|
18820
19347
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18821
19348
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18822
19349
|
}
|
|
18823
|
-
function encodeCursor(payload) {
|
|
18824
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18825
|
-
}
|
|
18826
|
-
function decodeCursor(cursor) {
|
|
18827
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18828
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18829
|
-
return parsed;
|
|
18830
|
-
}
|
|
18831
|
-
return null;
|
|
18832
|
-
}
|
|
18833
19350
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18834
19351
|
session: "session",
|
|
18835
19352
|
prompt: "prompt",
|
|
@@ -18974,7 +19491,7 @@ var SqliteActivityRepository = class {
|
|
|
18974
19491
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18975
19492
|
}
|
|
18976
19493
|
listSessions(query) {
|
|
18977
|
-
const cursor = query.cursor ?
|
|
19494
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18978
19495
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18979
19496
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18980
19497
|
const conditions = [SESSION_ROOT];
|
|
@@ -19048,7 +19565,7 @@ var SqliteActivityRepository = class {
|
|
|
19048
19565
|
)
|
|
19049
19566
|
);
|
|
19050
19567
|
const last = page[page.length - 1];
|
|
19051
|
-
const nextCursor = hasMore && last ?
|
|
19568
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19052
19569
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19053
19570
|
}
|
|
19054
19571
|
getSession(sessionId) {
|
|
@@ -19921,7 +20438,7 @@ var SqliteEventsRepository = class {
|
|
|
19921
20438
|
};
|
|
19922
20439
|
|
|
19923
20440
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19924
|
-
import { randomUUID } from "crypto";
|
|
20441
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19925
20442
|
|
|
19926
20443
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19927
20444
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19957,8 +20474,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19957
20474
|
AND conditions IS NULL
|
|
19958
20475
|
AND ${ACTIVE_PREDICATE}`;
|
|
19959
20476
|
var SqliteExceptionsRepository = class {
|
|
19960
|
-
constructor(db) {
|
|
20477
|
+
constructor(db, now = () => Date.now()) {
|
|
19961
20478
|
this.db = db;
|
|
20479
|
+
this.now = now;
|
|
19962
20480
|
this.consumeStmt = db.prepare(
|
|
19963
20481
|
`UPDATE exceptions
|
|
19964
20482
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19976,6 +20494,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19976
20494
|
);
|
|
19977
20495
|
}
|
|
19978
20496
|
db;
|
|
20497
|
+
now;
|
|
19979
20498
|
consumeStmt;
|
|
19980
20499
|
insertBlockedStmt;
|
|
19981
20500
|
sweepBlockedStmt;
|
|
@@ -20002,8 +20521,8 @@ var SqliteExceptionsRepository = class {
|
|
|
20002
20521
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
20003
20522
|
);
|
|
20004
20523
|
}
|
|
20005
|
-
const id =
|
|
20006
|
-
const now =
|
|
20524
|
+
const id = randomUUID2();
|
|
20525
|
+
const now = this.now();
|
|
20007
20526
|
try {
|
|
20008
20527
|
this.insertExceptionRow(id, input, now);
|
|
20009
20528
|
} catch (err) {
|
|
@@ -20081,7 +20600,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20081
20600
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20082
20601
|
const rows = allRows(
|
|
20083
20602
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20084
|
-
opts?.includeTerminal ? {} : { now:
|
|
20603
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20085
20604
|
);
|
|
20086
20605
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20087
20606
|
return Promise.resolve(exceptions);
|
|
@@ -20116,7 +20635,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20116
20635
|
* already revoked.
|
|
20117
20636
|
*/
|
|
20118
20637
|
revoke(id, revokedBy, reason) {
|
|
20119
|
-
const now =
|
|
20638
|
+
const now = this.now();
|
|
20120
20639
|
const result = this.db.prepare(
|
|
20121
20640
|
`UPDATE exceptions
|
|
20122
20641
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20130,7 +20649,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20130
20649
|
* callers must treat identically — means it does not and the detection is
|
|
20131
20650
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20132
20651
|
*/
|
|
20133
|
-
consume(id, now =
|
|
20652
|
+
consume(id, now = this.now()) {
|
|
20134
20653
|
const result = this.consumeStmt.run({ id, now });
|
|
20135
20654
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20136
20655
|
}
|
|
@@ -20139,7 +20658,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20139
20658
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20140
20659
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20141
20660
|
*/
|
|
20142
|
-
activeBundleEntries(keyVersion, now =
|
|
20661
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20143
20662
|
const rows = allRows(
|
|
20144
20663
|
this.db.prepare(
|
|
20145
20664
|
`SELECT * FROM exceptions
|
|
@@ -20171,7 +20690,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20171
20690
|
* than the retention window on every write, so the ledger self-limits.
|
|
20172
20691
|
*/
|
|
20173
20692
|
recordBlocked(entry) {
|
|
20174
|
-
const now =
|
|
20693
|
+
const now = this.now();
|
|
20175
20694
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20176
20695
|
this.insertBlockedStmt.run({
|
|
20177
20696
|
reference: entry.reference,
|
|
@@ -20194,7 +20713,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20194
20713
|
WHERE blocked_at > :cutoff
|
|
20195
20714
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20196
20715
|
),
|
|
20197
|
-
{ cutoff:
|
|
20716
|
+
{ cutoff: this.now() - windowMs }
|
|
20198
20717
|
);
|
|
20199
20718
|
return Promise.resolve(
|
|
20200
20719
|
rows.map((row) => ({
|
|
@@ -20222,8 +20741,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20222
20741
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20223
20742
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20224
20743
|
*/
|
|
20225
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20744
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20226
20745
|
try {
|
|
20746
|
+
const at = now ?? this.now();
|
|
20227
20747
|
const row = getRow(
|
|
20228
20748
|
this.db.prepare(
|
|
20229
20749
|
`SELECT id FROM exceptions
|
|
@@ -20232,7 +20752,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20232
20752
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20233
20753
|
LIMIT 1`
|
|
20234
20754
|
),
|
|
20235
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20755
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20236
20756
|
);
|
|
20237
20757
|
return Promise.resolve(row ?? null);
|
|
20238
20758
|
} catch (err) {
|
|
@@ -20246,7 +20766,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20246
20766
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20247
20767
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20248
20768
|
*/
|
|
20249
|
-
sweepTerminal(retentionMs, now =
|
|
20769
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20250
20770
|
const result = this.db.prepare(
|
|
20251
20771
|
`DELETE FROM exceptions
|
|
20252
20772
|
WHERE updated_at < :cutoff
|
|
@@ -20309,6 +20829,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20309
20829
|
|
|
20310
20830
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20311
20831
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20832
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20833
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20834
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20835
|
+
function compareLocationOrder(a, b) {
|
|
20836
|
+
return compareFindingGroupOrder(
|
|
20837
|
+
{
|
|
20838
|
+
severity: a.maxSeverity,
|
|
20839
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20840
|
+
id: ""
|
|
20841
|
+
},
|
|
20842
|
+
{
|
|
20843
|
+
severity: b.maxSeverity,
|
|
20844
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20845
|
+
id: ""
|
|
20846
|
+
}
|
|
20847
|
+
);
|
|
20848
|
+
}
|
|
20312
20849
|
var CONCAT_SEP = ",";
|
|
20313
20850
|
var TUPLE_SEP = "|";
|
|
20314
20851
|
function splitConcat(value) {
|
|
@@ -20321,6 +20858,33 @@ function deriveInstanceStatus(row) {
|
|
|
20321
20858
|
latestResolutionStatus: row.latest_status
|
|
20322
20859
|
});
|
|
20323
20860
|
}
|
|
20861
|
+
function encodeGroupCursor(group) {
|
|
20862
|
+
const payload = {
|
|
20863
|
+
sev: group.severity,
|
|
20864
|
+
t: group.latestDetectedAt,
|
|
20865
|
+
id: group.id
|
|
20866
|
+
};
|
|
20867
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20868
|
+
}
|
|
20869
|
+
function decodeGroupCursor(cursor) {
|
|
20870
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20871
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20872
|
+
return {
|
|
20873
|
+
severity: parsed.sev,
|
|
20874
|
+
latestDetectedAt: parsed.t,
|
|
20875
|
+
id: parsed.id
|
|
20876
|
+
};
|
|
20877
|
+
}
|
|
20878
|
+
return null;
|
|
20879
|
+
}
|
|
20880
|
+
function firstAfter(sorted, cursor) {
|
|
20881
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20882
|
+
return index === -1 ? sorted.length : index;
|
|
20883
|
+
}
|
|
20884
|
+
function findDeepLinked(sorted, page, id) {
|
|
20885
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20886
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20887
|
+
}
|
|
20324
20888
|
var DAY_MS3 = 864e5;
|
|
20325
20889
|
var SqliteFindingsRepository = class {
|
|
20326
20890
|
constructor(db) {
|
|
@@ -20430,8 +20994,13 @@ var SqliteFindingsRepository = class {
|
|
|
20430
20994
|
*/
|
|
20431
20995
|
listGroupedFindings(query) {
|
|
20432
20996
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20433
|
-
const
|
|
20434
|
-
const
|
|
20997
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20998
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20999
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
21000
|
+
const sessionParams = {
|
|
21001
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
21002
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
21003
|
+
};
|
|
20435
21004
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20436
21005
|
predicate,
|
|
20437
21006
|
params: sessionParams
|
|
@@ -20439,7 +21008,8 @@ var SqliteFindingsRepository = class {
|
|
|
20439
21008
|
const rows = allRows(
|
|
20440
21009
|
this.db.prepare(
|
|
20441
21010
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20442
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
21011
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
21012
|
+
kind, finding_key, latest_status
|
|
20443
21013
|
FROM (
|
|
20444
21014
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20445
21015
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20449,6 +21019,7 @@ var SqliteFindingsRepository = class {
|
|
|
20449
21019
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20450
21020
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20451
21021
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21022
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20452
21023
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20453
21024
|
latest.status AS latest_status,
|
|
20454
21025
|
ROW_NUMBER() OVER (
|
|
@@ -20480,6 +21051,8 @@ var SqliteFindingsRepository = class {
|
|
|
20480
21051
|
repo: r.repo ?? "",
|
|
20481
21052
|
file: r.file ?? "",
|
|
20482
21053
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21054
|
+
eventId: r.event_id,
|
|
21055
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20483
21056
|
status: deriveInstanceStatus(r)
|
|
20484
21057
|
}));
|
|
20485
21058
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20503,18 +21076,23 @@ var SqliteFindingsRepository = class {
|
|
|
20503
21076
|
groups: sorted.length
|
|
20504
21077
|
};
|
|
20505
21078
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21079
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21080
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21081
|
+
const page = sorted.slice(start, start + limit);
|
|
21082
|
+
const lastOnPage = page.at(-1);
|
|
21083
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21084
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20506
21085
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20507
|
-
const
|
|
20508
|
-
|
|
20509
|
-
|
|
20510
|
-
|
|
20511
|
-
|
|
20512
|
-
);
|
|
21086
|
+
const narrow = (g) => statusSet ? {
|
|
21087
|
+
...g,
|
|
21088
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21089
|
+
} : g;
|
|
21090
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20513
21091
|
return Promise.resolve({
|
|
20514
21092
|
totals,
|
|
20515
21093
|
facets,
|
|
20516
21094
|
items,
|
|
20517
|
-
nextCursor
|
|
21095
|
+
nextCursor,
|
|
20518
21096
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20519
21097
|
});
|
|
20520
21098
|
}
|
|
@@ -20546,6 +21124,266 @@ var SqliteFindingsRepository = class {
|
|
|
20546
21124
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20547
21125
|
* path repeating across tuples.)
|
|
20548
21126
|
*/
|
|
21127
|
+
/**
|
|
21128
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21129
|
+
* paged by keyset.
|
|
21130
|
+
*
|
|
21131
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21132
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21133
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21134
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21135
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21136
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21137
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21138
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21139
|
+
* none of the mappers names", which no IN-list can say.
|
|
21140
|
+
*
|
|
21141
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21142
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21143
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21144
|
+
* while the counting runs, and only the page itself is retained.
|
|
21145
|
+
*/
|
|
21146
|
+
listFindingInstances(query) {
|
|
21147
|
+
const opts = {
|
|
21148
|
+
severity: query.severity,
|
|
21149
|
+
subtype: query.subtype,
|
|
21150
|
+
providers: query.provider,
|
|
21151
|
+
actions: query.action,
|
|
21152
|
+
statuses: query.status,
|
|
21153
|
+
tools: query.tool,
|
|
21154
|
+
repo: query.repo,
|
|
21155
|
+
file: query.file,
|
|
21156
|
+
q: query.q
|
|
21157
|
+
};
|
|
21158
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21159
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21160
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21161
|
+
const items = [];
|
|
21162
|
+
let total = 0;
|
|
21163
|
+
let last;
|
|
21164
|
+
let hasMore = false;
|
|
21165
|
+
for (const row of this.scanFindingRows({
|
|
21166
|
+
sessionId: query.sessionId,
|
|
21167
|
+
from: query.from
|
|
21168
|
+
})) {
|
|
21169
|
+
accumulator.add(row);
|
|
21170
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21171
|
+
total += 1;
|
|
21172
|
+
if (items.length < limit) {
|
|
21173
|
+
items.push(toInstanceDetail(row));
|
|
21174
|
+
last = row;
|
|
21175
|
+
} else {
|
|
21176
|
+
hasMore = true;
|
|
21177
|
+
}
|
|
21178
|
+
}
|
|
21179
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21180
|
+
if (cursor !== null) {
|
|
21181
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21182
|
+
return Promise.resolve({
|
|
21183
|
+
totals: { findings: total },
|
|
21184
|
+
facets: accumulator.facets(),
|
|
21185
|
+
items: resumed.items,
|
|
21186
|
+
nextCursor: resumed.nextCursor
|
|
21187
|
+
});
|
|
21188
|
+
}
|
|
21189
|
+
return Promise.resolve({
|
|
21190
|
+
totals: { findings: total },
|
|
21191
|
+
facets: accumulator.facets(),
|
|
21192
|
+
items,
|
|
21193
|
+
nextCursor
|
|
21194
|
+
});
|
|
21195
|
+
}
|
|
21196
|
+
/**
|
|
21197
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21198
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21199
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21200
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21201
|
+
*/
|
|
21202
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21203
|
+
const items = [];
|
|
21204
|
+
let last;
|
|
21205
|
+
let hasMore = false;
|
|
21206
|
+
for (const row of this.scanFindingRows({
|
|
21207
|
+
sessionId: query.sessionId,
|
|
21208
|
+
from: query.from,
|
|
21209
|
+
after: cursor
|
|
21210
|
+
})) {
|
|
21211
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21212
|
+
if (items.length < limit) {
|
|
21213
|
+
items.push(toInstanceDetail(row));
|
|
21214
|
+
last = row;
|
|
21215
|
+
} else {
|
|
21216
|
+
hasMore = true;
|
|
21217
|
+
break;
|
|
21218
|
+
}
|
|
21219
|
+
}
|
|
21220
|
+
return {
|
|
21221
|
+
items,
|
|
21222
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21223
|
+
};
|
|
21224
|
+
}
|
|
21225
|
+
/**
|
|
21226
|
+
* The same findings folded by location: repository, then file within it.
|
|
21227
|
+
*
|
|
21228
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21229
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21230
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21231
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21232
|
+
* filter can name it.
|
|
21233
|
+
*/
|
|
21234
|
+
listFindingLocations(query) {
|
|
21235
|
+
const opts = {
|
|
21236
|
+
severity: query.severity,
|
|
21237
|
+
subtype: query.subtype,
|
|
21238
|
+
providers: query.provider,
|
|
21239
|
+
actions: query.action,
|
|
21240
|
+
statuses: query.status,
|
|
21241
|
+
tools: query.tool,
|
|
21242
|
+
q: query.q
|
|
21243
|
+
};
|
|
21244
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21245
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21246
|
+
let total = 0;
|
|
21247
|
+
for (const row of this.scanFindingRows({
|
|
21248
|
+
sessionId: query.sessionId,
|
|
21249
|
+
from: query.from
|
|
21250
|
+
})) {
|
|
21251
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21252
|
+
total += 1;
|
|
21253
|
+
let files = byRepo.get(row.repo);
|
|
21254
|
+
if (files === void 0) {
|
|
21255
|
+
files = /* @__PURE__ */ new Map();
|
|
21256
|
+
byRepo.set(row.repo, files);
|
|
21257
|
+
}
|
|
21258
|
+
let acc = files.get(row.file);
|
|
21259
|
+
if (acc === void 0) {
|
|
21260
|
+
acc = newLocationAccumulator();
|
|
21261
|
+
files.set(row.file, acc);
|
|
21262
|
+
}
|
|
21263
|
+
addToLocation(acc, row);
|
|
21264
|
+
}
|
|
21265
|
+
let fileCount = 0;
|
|
21266
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21267
|
+
fileCount += files.size;
|
|
21268
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21269
|
+
file: file2,
|
|
21270
|
+
instanceCount: acc.instanceCount,
|
|
21271
|
+
maxSeverity: acc.maxSeverity,
|
|
21272
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21273
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21274
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21275
|
+
})).sort(compareLocationOrder);
|
|
21276
|
+
const rollup = fileRows.reduce(
|
|
21277
|
+
(a, f) => ({
|
|
21278
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21279
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21280
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21281
|
+
}),
|
|
21282
|
+
{
|
|
21283
|
+
instanceCount: 0,
|
|
21284
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21285
|
+
latestDetectedAt: ""
|
|
21286
|
+
}
|
|
21287
|
+
);
|
|
21288
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21289
|
+
const folded = foldGroupStatus(statuses);
|
|
21290
|
+
return {
|
|
21291
|
+
repo,
|
|
21292
|
+
instanceCount: rollup.instanceCount,
|
|
21293
|
+
maxSeverity: rollup.maxSeverity,
|
|
21294
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21295
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21296
|
+
files: fileRows
|
|
21297
|
+
};
|
|
21298
|
+
});
|
|
21299
|
+
repos.sort(compareLocationOrder);
|
|
21300
|
+
return Promise.resolve({
|
|
21301
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21302
|
+
items: repos.slice(0, limit),
|
|
21303
|
+
hasMore: repos.length > limit
|
|
21304
|
+
});
|
|
21305
|
+
}
|
|
21306
|
+
/**
|
|
21307
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21308
|
+
*
|
|
21309
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21310
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21311
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21312
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21313
|
+
* rather than one unbounded result set.
|
|
21314
|
+
*
|
|
21315
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21316
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21317
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21318
|
+
* a window over the whole resolution table once per batch.
|
|
21319
|
+
*
|
|
21320
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21321
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21322
|
+
* dimension — see listFindingInstances.
|
|
21323
|
+
*/
|
|
21324
|
+
*scanFindingRows(scope) {
|
|
21325
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21326
|
+
const params = [];
|
|
21327
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21328
|
+
conditions.push("e.root_session_id = ?");
|
|
21329
|
+
params.push(scope.sessionId);
|
|
21330
|
+
}
|
|
21331
|
+
if (scope.from !== void 0) {
|
|
21332
|
+
conditions.push("e.started_at >= ?");
|
|
21333
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21334
|
+
}
|
|
21335
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21336
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21337
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21338
|
+
e.started_at AS occurred_at,
|
|
21339
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21340
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21341
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21342
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21343
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21344
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21345
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21346
|
+
FROM inspection_findings f
|
|
21347
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21348
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21349
|
+
WHERE ${conditions.join(" AND ")}
|
|
21350
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21351
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21352
|
+
LIMIT ?`;
|
|
21353
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21354
|
+
for (; ; ) {
|
|
21355
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21356
|
+
...params,
|
|
21357
|
+
after.startedAtMs,
|
|
21358
|
+
after.startedAtMs,
|
|
21359
|
+
after.id,
|
|
21360
|
+
SCAN_BATCH_ROWS
|
|
21361
|
+
]);
|
|
21362
|
+
for (const r of rows) {
|
|
21363
|
+
yield {
|
|
21364
|
+
id: r.id,
|
|
21365
|
+
ruleId: r.rule_id,
|
|
21366
|
+
category: r.category,
|
|
21367
|
+
severity: r.severity,
|
|
21368
|
+
maskedMatch: r.masked_match,
|
|
21369
|
+
actionTaken: r.action_taken,
|
|
21370
|
+
confidence: r.confidence,
|
|
21371
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21372
|
+
sourceTool: r.source_tool,
|
|
21373
|
+
repo: r.repo ?? "",
|
|
21374
|
+
file: r.file ?? "",
|
|
21375
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21376
|
+
eventId: r.event_id,
|
|
21377
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21378
|
+
status: deriveInstanceStatus(r)
|
|
21379
|
+
};
|
|
21380
|
+
}
|
|
21381
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21382
|
+
const lastRow = rows[rows.length - 1];
|
|
21383
|
+
if (lastRow === void 0) return;
|
|
21384
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21385
|
+
}
|
|
21386
|
+
}
|
|
20549
21387
|
groupAggregates(withSearchText, scope) {
|
|
20550
21388
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20551
21389
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20806,7 +21644,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20806
21644
|
};
|
|
20807
21645
|
|
|
20808
21646
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20809
|
-
import { createHash as createHash2, randomUUID as
|
|
21647
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20810
21648
|
|
|
20811
21649
|
// ../../packages/persistence/src/semver.ts
|
|
20812
21650
|
function parse3(version2) {
|
|
@@ -20957,7 +21795,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20957
21795
|
let behind = false;
|
|
20958
21796
|
for (const row of rows) {
|
|
20959
21797
|
const params = {
|
|
20960
|
-
id:
|
|
21798
|
+
id: randomUUID3(),
|
|
20961
21799
|
namespace: row.namespace,
|
|
20962
21800
|
packId: row.packId,
|
|
20963
21801
|
version: row.version,
|
|
@@ -20969,7 +21807,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20969
21807
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20970
21808
|
this.upsertAvailableStmt.run({
|
|
20971
21809
|
...params,
|
|
20972
|
-
id:
|
|
21810
|
+
id: randomUUID3(),
|
|
20973
21811
|
recordedBy: meta3?.recordedBy ?? null
|
|
20974
21812
|
});
|
|
20975
21813
|
} else {
|
|
@@ -21292,14 +22130,15 @@ var SqliteInventoryRepository = class {
|
|
|
21292
22130
|
};
|
|
21293
22131
|
|
|
21294
22132
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21295
|
-
import { randomUUID as
|
|
22133
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21296
22134
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21297
22135
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21298
22136
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21299
22137
|
var HARNESS_LABELS = {
|
|
21300
22138
|
claudecode: "Claude Code",
|
|
21301
22139
|
cursor: "Cursor",
|
|
21302
|
-
codex: "Codex"
|
|
22140
|
+
codex: "Codex",
|
|
22141
|
+
antigravity: "Antigravity"
|
|
21303
22142
|
};
|
|
21304
22143
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21305
22144
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21314,6 +22153,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21314
22153
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21315
22154
|
if (t.includes("cursor")) return "cursor";
|
|
21316
22155
|
if (t.includes("codex")) return "codex";
|
|
22156
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21317
22157
|
return null;
|
|
21318
22158
|
}
|
|
21319
22159
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21772,7 +22612,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21772
22612
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21773
22613
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21774
22614
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21775
|
-
).run({ id:
|
|
22615
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21776
22616
|
}
|
|
21777
22617
|
return true;
|
|
21778
22618
|
}
|
|
@@ -21793,7 +22633,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21793
22633
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21794
22634
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21795
22635
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21796
|
-
).run({ id:
|
|
22636
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21797
22637
|
}
|
|
21798
22638
|
this.configRowsCache = void 0;
|
|
21799
22639
|
return "ok";
|
|
@@ -22090,7 +22930,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22090
22930
|
};
|
|
22091
22931
|
|
|
22092
22932
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22093
|
-
import { randomUUID as
|
|
22933
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22094
22934
|
var SqlitePoliciesRepository = class {
|
|
22095
22935
|
constructor(db) {
|
|
22096
22936
|
this.db = db;
|
|
@@ -22125,7 +22965,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22125
22965
|
failOpenTransaction(this.db, () => {
|
|
22126
22966
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22127
22967
|
stmt.run({
|
|
22128
|
-
id:
|
|
22968
|
+
id: randomUUID5(),
|
|
22129
22969
|
target: JSON.stringify({ category }),
|
|
22130
22970
|
action,
|
|
22131
22971
|
now: Date.now()
|
|
@@ -22145,7 +22985,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22145
22985
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22146
22986
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22147
22987
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22148
|
-
).run({ id:
|
|
22988
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22149
22989
|
}
|
|
22150
22990
|
// Caps every global per-category policy currently set to block/redact down
|
|
22151
22991
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22213,7 +23053,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22213
23053
|
};
|
|
22214
23054
|
|
|
22215
23055
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22216
|
-
import { randomUUID as
|
|
23056
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22217
23057
|
var SqliteProjectFilesRepository = class {
|
|
22218
23058
|
constructor(db) {
|
|
22219
23059
|
this.db = db;
|
|
@@ -22245,7 +23085,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22245
23085
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22246
23086
|
for (const file2 of scan2.files) {
|
|
22247
23087
|
this.upsertStmt.run({
|
|
22248
|
-
id:
|
|
23088
|
+
id: randomUUID6(),
|
|
22249
23089
|
projectId,
|
|
22250
23090
|
path: file2.path,
|
|
22251
23091
|
name: file2.name,
|
|
@@ -22259,7 +23099,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22259
23099
|
};
|
|
22260
23100
|
|
|
22261
23101
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22262
|
-
import { randomUUID as
|
|
23102
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22263
23103
|
var SqliteResolutionsRepository = class {
|
|
22264
23104
|
constructor(db, now = () => Date.now()) {
|
|
22265
23105
|
this.db = db;
|
|
@@ -22313,7 +23153,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22313
23153
|
*/
|
|
22314
23154
|
insertResolution(r) {
|
|
22315
23155
|
this.insertStmt.run({
|
|
22316
|
-
id:
|
|
23156
|
+
id: randomUUID7(),
|
|
22317
23157
|
findingKey: r.findingKey,
|
|
22318
23158
|
status: FindingStatus.parse(r.status),
|
|
22319
23159
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22372,13 +23212,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22372
23212
|
this.readStmt = db.prepare(
|
|
22373
23213
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22374
23214
|
);
|
|
23215
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23216
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23217
|
+
);
|
|
23218
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23219
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23220
|
+
);
|
|
22375
23221
|
}
|
|
22376
23222
|
db;
|
|
22377
23223
|
upsertStmt;
|
|
22378
23224
|
readStmt;
|
|
23225
|
+
countQuarantinedStmt;
|
|
23226
|
+
clearQuarantinedStmt;
|
|
22379
23227
|
getVerdict(ruleKey) {
|
|
22380
23228
|
return getRow(this.readStmt, { ruleKey });
|
|
22381
23229
|
}
|
|
23230
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23231
|
+
countQuarantined() {
|
|
23232
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23233
|
+
}
|
|
23234
|
+
/**
|
|
23235
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23236
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23237
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23238
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23239
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23240
|
+
*
|
|
23241
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23242
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23243
|
+
*
|
|
23244
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23245
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23246
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23247
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23248
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23249
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23250
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23251
|
+
* to restore are silently still disabled.
|
|
23252
|
+
*/
|
|
23253
|
+
clearQuarantined() {
|
|
23254
|
+
const before = this.countQuarantined();
|
|
23255
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23256
|
+
this.clearQuarantinedStmt.run();
|
|
23257
|
+
});
|
|
23258
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23259
|
+
}
|
|
22382
23260
|
setVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
22383
23261
|
failOpenTransaction(this.db, () => {
|
|
22384
23262
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs: worstProbeMs2, checkedAt: Date.now() });
|
|
@@ -22433,7 +23311,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22433
23311
|
};
|
|
22434
23312
|
|
|
22435
23313
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22436
|
-
import { randomUUID as
|
|
23314
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23315
|
+
function pageLimit(requested, fallback) {
|
|
23316
|
+
if (requested === void 0) return fallback;
|
|
23317
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23318
|
+
}
|
|
23319
|
+
function encodeReuseCursor(payload) {
|
|
23320
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23321
|
+
}
|
|
23322
|
+
function decodeReuseCursor(cursor) {
|
|
23323
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23324
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23325
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23326
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23327
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23328
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23329
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23330
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23331
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23332
|
+
}
|
|
23333
|
+
return null;
|
|
23334
|
+
}
|
|
23335
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23336
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23337
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23338
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23339
|
+
function toSighting(row) {
|
|
23340
|
+
return {
|
|
23341
|
+
location: row.location,
|
|
23342
|
+
kind: row.kind,
|
|
23343
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23344
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23345
|
+
};
|
|
23346
|
+
}
|
|
22437
23347
|
var SELECT_COLUMNS = `
|
|
22438
23348
|
pointer_id AS pointerId,
|
|
22439
23349
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22617,39 +23527,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22617
23527
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22618
23528
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22619
23529
|
).run({
|
|
22620
|
-
id:
|
|
23530
|
+
id: randomUUID8(),
|
|
22621
23531
|
pointerId: entry.pointerId,
|
|
22622
23532
|
location: entry.location,
|
|
22623
23533
|
kind: entry.kind,
|
|
22624
23534
|
now
|
|
22625
23535
|
});
|
|
22626
23536
|
}
|
|
22627
|
-
|
|
23537
|
+
/**
|
|
23538
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23539
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23540
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23541
|
+
*
|
|
23542
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23543
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23544
|
+
*/
|
|
23545
|
+
sightingsFor(pointerIds) {
|
|
23546
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23547
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22628
23548
|
const rows = allRows(
|
|
22629
23549
|
this.db.prepare(
|
|
22630
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22631
|
-
|
|
23550
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23551
|
+
FROM secret_vault_sighting
|
|
23552
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23553
|
+
ORDER BY last_seen DESC`
|
|
22632
23554
|
),
|
|
22633
|
-
|
|
23555
|
+
pointerIds
|
|
22634
23556
|
);
|
|
23557
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23558
|
+
return byPointer;
|
|
23559
|
+
}
|
|
23560
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23561
|
+
toInventoryEntries(rows) {
|
|
23562
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22635
23563
|
return rows.map((r) => ({
|
|
22636
|
-
|
|
22637
|
-
|
|
23564
|
+
pointerId: r.pointer_id,
|
|
23565
|
+
category: r.category,
|
|
23566
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23567
|
+
maskedMatch: r.masked_match,
|
|
23568
|
+
occurrences: r.occurrence_count,
|
|
22638
23569
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22639
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23570
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23571
|
+
revealGrantId: r.grant_id,
|
|
23572
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22640
23573
|
}));
|
|
22641
23574
|
}
|
|
22642
23575
|
/**
|
|
22643
|
-
* The dashboard inventory
|
|
22644
|
-
* its sightings and the active
|
|
22645
|
-
* Raw-free by construction — neither
|
|
22646
|
-
* columns are selected.
|
|
23576
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23577
|
+
* value's descriptor data joined with its sightings and the active
|
|
23578
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23579
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23580
|
+
*
|
|
23581
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23582
|
+
* sees never depends on how far they have paged.
|
|
22647
23583
|
*/
|
|
22648
|
-
listInventory(now = Date.now()) {
|
|
23584
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23585
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23586
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23587
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22649
23588
|
const rows = allRows(
|
|
22650
23589
|
this.db.prepare(
|
|
22651
|
-
`SELECT
|
|
22652
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23590
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22653
23591
|
(SELECT e.id FROM exceptions e
|
|
22654
23592
|
WHERE e.rule_id = v.rule_id
|
|
22655
23593
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22657,45 +23595,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22657
23595
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22658
23596
|
LIMIT 1) AS grant_id
|
|
22659
23597
|
FROM secret_vault v
|
|
22660
|
-
|
|
23598
|
+
${where}
|
|
23599
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23600
|
+
LIMIT :limit`
|
|
22661
23601
|
),
|
|
22662
|
-
{
|
|
23602
|
+
bindParams({
|
|
23603
|
+
now,
|
|
23604
|
+
limit: limit + 1,
|
|
23605
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23606
|
+
})
|
|
22663
23607
|
);
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
22671
|
-
|
|
22672
|
-
|
|
22673
|
-
sightings: this.listSightings(r.pointer_id)
|
|
22674
|
-
}));
|
|
23608
|
+
const hasMore = rows.length > limit;
|
|
23609
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23610
|
+
const last = page[page.length - 1];
|
|
23611
|
+
return {
|
|
23612
|
+
totals: { values: this.countEntries() },
|
|
23613
|
+
items: this.toInventoryEntries(page),
|
|
23614
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23615
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23616
|
+
};
|
|
22675
23617
|
}
|
|
22676
23618
|
/**
|
|
22677
|
-
*
|
|
22678
|
-
*
|
|
22679
|
-
*
|
|
22680
|
-
*
|
|
23619
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23620
|
+
* than one location — most-reused first, one page at a time.
|
|
23621
|
+
*
|
|
23622
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23623
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23624
|
+
* under-report exactly the values a reader most needs to see.
|
|
22681
23625
|
*/
|
|
22682
|
-
|
|
22683
|
-
const limit =
|
|
22684
|
-
const
|
|
23626
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23627
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23628
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23629
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23630
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23631
|
+
const rows = allRows(
|
|
23632
|
+
this.db.prepare(
|
|
23633
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23634
|
+
(SELECT e.id FROM exceptions e
|
|
23635
|
+
WHERE e.rule_id = v.rule_id
|
|
23636
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23637
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23638
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23639
|
+
LIMIT 1) AS grant_id
|
|
23640
|
+
FROM secret_vault v
|
|
23641
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23642
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23643
|
+
LIMIT :limit`
|
|
23644
|
+
),
|
|
23645
|
+
bindParams({
|
|
23646
|
+
now,
|
|
23647
|
+
limit: limit + 1,
|
|
23648
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23649
|
+
})
|
|
23650
|
+
);
|
|
23651
|
+
const hasMore = rows.length > limit;
|
|
23652
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23653
|
+
const last = page[page.length - 1];
|
|
23654
|
+
return {
|
|
23655
|
+
totals: { reused: this.countReused() },
|
|
23656
|
+
items: this.toInventoryEntries(page),
|
|
23657
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23658
|
+
};
|
|
23659
|
+
}
|
|
23660
|
+
/**
|
|
23661
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23662
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23663
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23664
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23665
|
+
*
|
|
23666
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23667
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23668
|
+
* the reader pages.
|
|
23669
|
+
*/
|
|
23670
|
+
listDerefs(query = {}) {
|
|
23671
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23672
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23673
|
+
const conditions = [];
|
|
23674
|
+
if (query.includeBatched !== true) {
|
|
23675
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23676
|
+
}
|
|
23677
|
+
if (cursor !== null) {
|
|
23678
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23679
|
+
}
|
|
23680
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22685
23681
|
const rows = allRows(
|
|
22686
23682
|
this.db.prepare(
|
|
22687
23683
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22688
23684
|
FROM secret_vault_deref ${where}
|
|
22689
|
-
ORDER BY at DESC,
|
|
23685
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22690
23686
|
),
|
|
22691
|
-
{
|
|
23687
|
+
bindParams({
|
|
23688
|
+
limit: limit + 1,
|
|
23689
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23690
|
+
})
|
|
22692
23691
|
);
|
|
22693
|
-
const
|
|
23692
|
+
const hasMore = rows.length > limit;
|
|
23693
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23694
|
+
const last = page[page.length - 1];
|
|
23695
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22694
23696
|
this.db,
|
|
22695
23697
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22696
23698
|
);
|
|
22697
23699
|
return {
|
|
22698
|
-
|
|
23700
|
+
items: page.map((r) => ({
|
|
22699
23701
|
id: r.id,
|
|
22700
23702
|
pointerId: r.pointer_id,
|
|
22701
23703
|
at: new Date(r.at).toISOString(),
|
|
@@ -22705,12 +23707,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22705
23707
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22706
23708
|
pointerCount: r.pointer_count
|
|
22707
23709
|
})),
|
|
23710
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22708
23711
|
hiddenBatched
|
|
22709
23712
|
};
|
|
22710
23713
|
}
|
|
22711
23714
|
countEntries() {
|
|
22712
23715
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22713
23716
|
}
|
|
23717
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23718
|
+
countReused() {
|
|
23719
|
+
return countScalar(
|
|
23720
|
+
this.db,
|
|
23721
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23722
|
+
);
|
|
23723
|
+
}
|
|
22714
23724
|
};
|
|
22715
23725
|
|
|
22716
23726
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22725,7 +23735,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22725
23735
|
var SCAN_COVERAGE = [
|
|
22726
23736
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22727
23737
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22728
|
-
{ provider: "codex", coverage:
|
|
23738
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23739
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23740
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22729
23741
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22730
23742
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22731
23743
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23058,7 +24070,7 @@ var SqliteSecurityRepository = class {
|
|
|
23058
24070
|
};
|
|
23059
24071
|
|
|
23060
24072
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23061
|
-
import { randomUUID as
|
|
24073
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23062
24074
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23063
24075
|
var IN_CHUNK = 500;
|
|
23064
24076
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23314,7 +24326,7 @@ var SqliteSharesRepository = class {
|
|
|
23314
24326
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23315
24327
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23316
24328
|
).run({
|
|
23317
|
-
id:
|
|
24329
|
+
id: randomUUID9(),
|
|
23318
24330
|
destinationId,
|
|
23319
24331
|
host: dest.host,
|
|
23320
24332
|
decision,
|
|
@@ -23463,7 +24475,7 @@ var SqliteSharesRepository = class {
|
|
|
23463
24475
|
let destinationId = destIds.get(hit.host);
|
|
23464
24476
|
if (destinationId === void 0) {
|
|
23465
24477
|
destStmt.run({
|
|
23466
|
-
id:
|
|
24478
|
+
id: randomUUID9(),
|
|
23467
24479
|
kind: hit.kind,
|
|
23468
24480
|
name: hit.name,
|
|
23469
24481
|
host: hit.host,
|
|
@@ -23479,7 +24491,7 @@ var SqliteSharesRepository = class {
|
|
|
23479
24491
|
let endpointId = endpointIds.get(endpointKey);
|
|
23480
24492
|
if (endpointId === void 0) {
|
|
23481
24493
|
endpointStmt.run({
|
|
23482
|
-
id:
|
|
24494
|
+
id: randomUUID9(),
|
|
23483
24495
|
destinationId,
|
|
23484
24496
|
method: hit.method,
|
|
23485
24497
|
transport: hit.transport,
|
|
@@ -23492,7 +24504,7 @@ var SqliteSharesRepository = class {
|
|
|
23492
24504
|
endpointIds.set(endpointKey, endpointId);
|
|
23493
24505
|
}
|
|
23494
24506
|
siteStmt.run({
|
|
23495
|
-
id:
|
|
24507
|
+
id: randomUUID9(),
|
|
23496
24508
|
endpointId,
|
|
23497
24509
|
project: input.project,
|
|
23498
24510
|
projectKey: input.projectKey,
|
|
@@ -23857,6 +24869,9 @@ function purgeSampleData(db) {
|
|
|
23857
24869
|
}
|
|
23858
24870
|
|
|
23859
24871
|
// ../../packages/persistence/src/database.ts
|
|
24872
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24873
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24874
|
+
);
|
|
23860
24875
|
function linkHost(input, hostId) {
|
|
23861
24876
|
return hostId ? { ...input, hostId } : input;
|
|
23862
24877
|
}
|
|
@@ -23878,21 +24893,34 @@ function openWithPragmas(file2) {
|
|
|
23878
24893
|
}
|
|
23879
24894
|
return db;
|
|
23880
24895
|
}
|
|
23881
|
-
function backupLegacyStore(file2) {
|
|
23882
|
-
|
|
23883
|
-
|
|
23884
|
-
|
|
23885
|
-
|
|
23886
|
-
|
|
24896
|
+
function backupLegacyStore(db, file2) {
|
|
24897
|
+
reapStalePartials(file2);
|
|
24898
|
+
const backup = backupPath(file2, "legacy");
|
|
24899
|
+
let snapshotted = false;
|
|
24900
|
+
let snapshotError;
|
|
24901
|
+
try {
|
|
24902
|
+
snapshotStore(db, backup);
|
|
24903
|
+
snapshotted = true;
|
|
24904
|
+
} catch (error51) {
|
|
24905
|
+
snapshotError = error51;
|
|
24906
|
+
} finally {
|
|
24907
|
+
db.close();
|
|
23887
24908
|
}
|
|
24909
|
+
if (!snapshotted) {
|
|
24910
|
+
akaWarn(
|
|
24911
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24912
|
+
);
|
|
24913
|
+
moveStoreAside(file2, backup);
|
|
24914
|
+
return backup;
|
|
24915
|
+
}
|
|
24916
|
+
discardStore(file2, backup);
|
|
23888
24917
|
return backup;
|
|
23889
24918
|
}
|
|
23890
24919
|
function openAndInitialize(file2) {
|
|
23891
24920
|
let db = openWithPragmas(file2);
|
|
23892
24921
|
try {
|
|
23893
24922
|
if (isForeignSqliteLineage(db)) {
|
|
23894
|
-
db
|
|
23895
|
-
const backup = backupLegacyStore(file2);
|
|
24923
|
+
const backup = backupLegacyStore(db, file2);
|
|
23896
24924
|
db = openWithPragmas(file2);
|
|
23897
24925
|
akaWarn(
|
|
23898
24926
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23936,7 +24964,7 @@ function openAndInitialize(file2) {
|
|
|
23936
24964
|
}
|
|
23937
24965
|
function openLocalDatabase(dir) {
|
|
23938
24966
|
ensureDataDirSync(dir);
|
|
23939
|
-
const file2 =
|
|
24967
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23940
24968
|
const {
|
|
23941
24969
|
db,
|
|
23942
24970
|
events,
|
|
@@ -24053,7 +25081,7 @@ function openLocalDatabase(dir) {
|
|
|
24053
25081
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24054
25082
|
if (!definitionId) continue;
|
|
24055
25083
|
inspectionFindings.insertFinding({
|
|
24056
|
-
id:
|
|
25084
|
+
id: randomUUID10(),
|
|
24057
25085
|
auditEventId: record2.scanEvent.id,
|
|
24058
25086
|
inspectionDefinitionId: definitionId,
|
|
24059
25087
|
span: finding.span,
|
|
@@ -24159,7 +25187,9 @@ function openLocalDatabase(dir) {
|
|
|
24159
25187
|
transaction,
|
|
24160
25188
|
close: () => {
|
|
24161
25189
|
db.close();
|
|
24162
|
-
}
|
|
25190
|
+
},
|
|
25191
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25192
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24163
25193
|
};
|
|
24164
25194
|
}
|
|
24165
25195
|
|
|
@@ -24183,6 +25213,20 @@ var UserGrantPolicyProvider = class {
|
|
|
24183
25213
|
}
|
|
24184
25214
|
};
|
|
24185
25215
|
|
|
25216
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25217
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25218
|
+
import {
|
|
25219
|
+
closeSync,
|
|
25220
|
+
existsSync as existsSync2,
|
|
25221
|
+
openSync,
|
|
25222
|
+
readFileSync,
|
|
25223
|
+
rmSync as rmSync3,
|
|
25224
|
+
statSync as statSync2,
|
|
25225
|
+
writeFileSync as writeFileSync2
|
|
25226
|
+
} from "fs";
|
|
25227
|
+
import { hostname as hostname3 } from "os";
|
|
25228
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25229
|
+
|
|
24186
25230
|
// ../../packages/persistence/src/finding-key.ts
|
|
24187
25231
|
import { createHash as createHash3 } from "crypto";
|
|
24188
25232
|
function normalizeFilePath(filePath) {
|
|
@@ -24195,13 +25239,13 @@ function computeFindingKey(input) {
|
|
|
24195
25239
|
|
|
24196
25240
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24197
25241
|
import { createHmac, randomBytes } from "crypto";
|
|
24198
|
-
import { existsSync as
|
|
24199
|
-
import { join as
|
|
25242
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25243
|
+
import { join as join3 } from "path";
|
|
24200
25244
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24201
|
-
var
|
|
25245
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24202
25246
|
var KEY_MATERIAL_BYTES = 32;
|
|
24203
25247
|
function keyFilePath(dataDir2) {
|
|
24204
|
-
return
|
|
25248
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24205
25249
|
}
|
|
24206
25250
|
function parseKeyFile(raw) {
|
|
24207
25251
|
const parsed = JSON.parse(raw);
|
|
@@ -24239,8 +25283,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
24239
25283
|
}
|
|
24240
25284
|
};
|
|
24241
25285
|
function storedKeyVersionFloor(dataDir2) {
|
|
24242
|
-
const file2 =
|
|
24243
|
-
if (!
|
|
25286
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25287
|
+
if (!existsSync3(file2)) return 0;
|
|
24244
25288
|
let db;
|
|
24245
25289
|
try {
|
|
24246
25290
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -24265,18 +25309,36 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
24265
25309
|
db?.close();
|
|
24266
25310
|
}
|
|
24267
25311
|
}
|
|
24268
|
-
function
|
|
25312
|
+
function serializeKey(key) {
|
|
25313
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
25314
|
+
}
|
|
25315
|
+
function createKeyFile(dataDir2, key) {
|
|
24269
25316
|
ensureDataDirSync(dataDir2);
|
|
24270
25317
|
const file2 = keyFilePath(dataDir2);
|
|
24271
|
-
|
|
24272
|
-
|
|
24273
|
-
|
|
24274
|
-
|
|
25318
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
25319
|
+
`)) return key;
|
|
25320
|
+
const winner = readFingerprintKey(dataDir2);
|
|
25321
|
+
if (winner) {
|
|
25322
|
+
tightenFile(file2);
|
|
25323
|
+
return winner;
|
|
25324
|
+
}
|
|
25325
|
+
const occupant = classifyOccupant(file2);
|
|
25326
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
25327
|
+
}
|
|
25328
|
+
function occupantMessage(file2, kind) {
|
|
25329
|
+
switch (kind) {
|
|
25330
|
+
case "symlink":
|
|
25331
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
25332
|
+
case "gone":
|
|
25333
|
+
return "exception key file was removed while it was being created";
|
|
25334
|
+
case "unknown":
|
|
25335
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
25336
|
+
}
|
|
24275
25337
|
}
|
|
24276
25338
|
function readFingerprintKey(dataDir2) {
|
|
24277
25339
|
let raw;
|
|
24278
25340
|
try {
|
|
24279
|
-
raw =
|
|
25341
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24280
25342
|
} catch (err) {
|
|
24281
25343
|
if (err.code === "ENOENT") return null;
|
|
24282
25344
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24289,7 +25351,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
24289
25351
|
tightenFile(keyFilePath(dataDir2));
|
|
24290
25352
|
return existing;
|
|
24291
25353
|
}
|
|
24292
|
-
return
|
|
25354
|
+
return createKeyFile(dataDir2, {
|
|
24293
25355
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
24294
25356
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
24295
25357
|
});
|
|
@@ -24302,21 +25364,21 @@ function fingerprintValue(key, raw) {
|
|
|
24302
25364
|
import { renameSync as renameSync3 } from "fs";
|
|
24303
25365
|
import { mkdir } from "fs/promises";
|
|
24304
25366
|
import { homedir } from "os";
|
|
24305
|
-
import { join as
|
|
25367
|
+
import { join as join4 } from "path";
|
|
24306
25368
|
function defaultDataDir() {
|
|
24307
|
-
return
|
|
25369
|
+
return join4(homedir(), ".aka");
|
|
24308
25370
|
}
|
|
24309
25371
|
function settingsDir(base = defaultDataDir()) {
|
|
24310
|
-
return
|
|
25372
|
+
return join4(base, "settings");
|
|
24311
25373
|
}
|
|
24312
25374
|
function dataDir(base = defaultDataDir()) {
|
|
24313
|
-
return
|
|
25375
|
+
return join4(base, "data");
|
|
24314
25376
|
}
|
|
24315
25377
|
function dbPath(base = defaultDataDir()) {
|
|
24316
|
-
return
|
|
25378
|
+
return join4(dataDir(base), "aka.db");
|
|
24317
25379
|
}
|
|
24318
25380
|
function keysDir(base = defaultDataDir()) {
|
|
24319
|
-
return
|
|
25381
|
+
return join4(base, "keys");
|
|
24320
25382
|
}
|
|
24321
25383
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24322
25384
|
ensureDataDirSync(dir);
|
|
@@ -24329,8 +25391,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24329
25391
|
for (const { name, dest } of moves) {
|
|
24330
25392
|
try {
|
|
24331
25393
|
ensureDataDirSync(dest);
|
|
24332
|
-
const moved =
|
|
24333
|
-
renameSync3(
|
|
25394
|
+
const moved = join4(dest, name);
|
|
25395
|
+
renameSync3(join4(base, name), moved);
|
|
24334
25396
|
tightenFile(moved);
|
|
24335
25397
|
} catch {
|
|
24336
25398
|
}
|
|
@@ -24338,10 +25400,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24338
25400
|
}
|
|
24339
25401
|
|
|
24340
25402
|
// ../../packages/persistence/src/settings.ts
|
|
24341
|
-
import { readFileSync as
|
|
24342
|
-
import { join as
|
|
25403
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25404
|
+
import { join as join5 } from "path";
|
|
25405
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24343
25406
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24344
|
-
const record2 = readJson(
|
|
25407
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24345
25408
|
if (!record2) return defaultWorkspaceSettings();
|
|
24346
25409
|
try {
|
|
24347
25410
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24352,7 +25415,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24352
25415
|
function readJson(file2) {
|
|
24353
25416
|
let text;
|
|
24354
25417
|
try {
|
|
24355
|
-
text =
|
|
25418
|
+
text = readFileSync3(file2, "utf8");
|
|
24356
25419
|
} catch {
|
|
24357
25420
|
return null;
|
|
24358
25421
|
}
|
|
@@ -24473,13 +25536,18 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24473
25536
|
import {
|
|
24474
25537
|
chmodSync as chmodSync2,
|
|
24475
25538
|
mkdirSync as mkdirSync2,
|
|
24476
|
-
readFileSync as
|
|
25539
|
+
readFileSync as readFileSync4,
|
|
24477
25540
|
renameSync as renameSync4,
|
|
24478
|
-
rmSync as
|
|
24479
|
-
statSync,
|
|
24480
|
-
writeFileSync as
|
|
25541
|
+
rmSync as rmSync4,
|
|
25542
|
+
statSync as statSync3,
|
|
25543
|
+
writeFileSync as writeFileSync3
|
|
24481
25544
|
} from "fs";
|
|
24482
|
-
import { join as
|
|
25545
|
+
import { join as join6 } from "path";
|
|
25546
|
+
var VAULT_OCCUPANT_REASON = {
|
|
25547
|
+
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25548
|
+
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
25549
|
+
unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
|
|
25550
|
+
};
|
|
24483
25551
|
var VaultKeyEpochMissingError = class extends Error {
|
|
24484
25552
|
version;
|
|
24485
25553
|
constructor(version2) {
|
|
@@ -24572,28 +25640,28 @@ function claimRotationLock(lock, owner) {
|
|
|
24572
25640
|
throw asError(err);
|
|
24573
25641
|
}
|
|
24574
25642
|
try {
|
|
24575
|
-
|
|
25643
|
+
writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
|
|
24576
25644
|
`, { mode: DATA_FILE_MODE });
|
|
24577
25645
|
return true;
|
|
24578
25646
|
} catch (err) {
|
|
24579
|
-
|
|
25647
|
+
rmSync4(lock, { recursive: true, force: true });
|
|
24580
25648
|
throw asError(err);
|
|
24581
25649
|
}
|
|
24582
25650
|
}
|
|
24583
25651
|
function acquireRotationLock(keysDir2) {
|
|
24584
|
-
const lock =
|
|
25652
|
+
const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
24585
25653
|
const owner = randomBytes2(16).toString("hex");
|
|
24586
25654
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
24587
25655
|
let held;
|
|
24588
25656
|
try {
|
|
24589
|
-
held =
|
|
25657
|
+
held = statSync3(lock);
|
|
24590
25658
|
} catch {
|
|
24591
25659
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24592
25660
|
}
|
|
24593
25661
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
24594
25662
|
const aside = `${lock}.stale.${owner}`;
|
|
24595
25663
|
try {
|
|
24596
|
-
const now =
|
|
25664
|
+
const now = statSync3(lock);
|
|
24597
25665
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
24598
25666
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24599
25667
|
}
|
|
@@ -24602,17 +25670,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
24602
25670
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
24603
25671
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
24604
25672
|
}
|
|
24605
|
-
|
|
25673
|
+
rmSync4(aside, { recursive: true, force: true });
|
|
24606
25674
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
24607
25675
|
return { lock, owner };
|
|
24608
25676
|
}
|
|
24609
25677
|
function releaseRotationLock(lease) {
|
|
24610
25678
|
try {
|
|
24611
|
-
if (
|
|
25679
|
+
if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
24612
25680
|
} catch {
|
|
24613
25681
|
return;
|
|
24614
25682
|
}
|
|
24615
|
-
|
|
25683
|
+
rmSync4(lease.lock, { recursive: true, force: true });
|
|
24616
25684
|
}
|
|
24617
25685
|
function withRotationLock(keysDir2, work) {
|
|
24618
25686
|
ensureDataDirSync(keysDir2);
|
|
@@ -24629,7 +25697,7 @@ var FileKeyProvider = class {
|
|
|
24629
25697
|
this.#keysDir = keysDir2;
|
|
24630
25698
|
}
|
|
24631
25699
|
get filePath() {
|
|
24632
|
-
return
|
|
25700
|
+
return join6(this.#keysDir, VAULT_KEY_FILENAME);
|
|
24633
25701
|
}
|
|
24634
25702
|
loadOrCreate() {
|
|
24635
25703
|
return asAsync(() => {
|
|
@@ -24659,7 +25727,7 @@ var FileKeyProvider = class {
|
|
|
24659
25727
|
#read() {
|
|
24660
25728
|
let raw;
|
|
24661
25729
|
try {
|
|
24662
|
-
raw =
|
|
25730
|
+
raw = readFileSync4(this.filePath, "utf8");
|
|
24663
25731
|
} catch (err) {
|
|
24664
25732
|
if (err.code === "ENOENT") return null;
|
|
24665
25733
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24667,34 +25735,32 @@ var FileKeyProvider = class {
|
|
|
24667
25735
|
return parseKeyring(raw);
|
|
24668
25736
|
}
|
|
24669
25737
|
/**
|
|
24670
|
-
* First mint: the keyring is
|
|
24671
|
-
*
|
|
24672
|
-
*
|
|
24673
|
-
*
|
|
24674
|
-
*
|
|
24675
|
-
*
|
|
24676
|
-
*
|
|
24677
|
-
*
|
|
25738
|
+
* First mint: the keyring is CREATED, never replaced, so two processes racing
|
|
25739
|
+
* a fresh machine cannot each mint a different epoch 1 — with tmp + rename
|
|
25740
|
+
* the loser's replace would orphan everything the winner had already sealed.
|
|
25741
|
+
* The loser re-reads and adopts the winner's keyring; it minted nothing.
|
|
25742
|
+
*
|
|
25743
|
+
* `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
|
|
25744
|
+
* at the final path, so the keyring never exists at zero length: a reader —
|
|
25745
|
+
* including the loser, re-reading in order to adopt — sees the file absent or
|
|
25746
|
+
* whole, and never mistakes a live keyring for a corrupt one. A corrupt file
|
|
25747
|
+
* still throws from the parse and is never re-minted over.
|
|
24678
25748
|
*/
|
|
24679
25749
|
#createExclusive() {
|
|
24680
25750
|
ensureDataDirSync(this.#keysDir);
|
|
24681
25751
|
const keyring = mintKeyring();
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
|
|
24686
|
-
|
|
24687
|
-
|
|
24688
|
-
|
|
24689
|
-
|
|
24690
|
-
|
|
24691
|
-
if (!winner) {
|
|
24692
|
-
throw new Error("vault: key file vanished during first mint", { cause: err });
|
|
24693
|
-
}
|
|
24694
|
-
return winner;
|
|
25752
|
+
if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
25753
|
+
`)) return keyring;
|
|
25754
|
+
const winner = this.#read();
|
|
25755
|
+
if (!winner) {
|
|
25756
|
+
const occupant = classifyOccupant(this.filePath);
|
|
25757
|
+
throw new KeyUnclaimableError(
|
|
25758
|
+
`vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
|
|
25759
|
+
occupant.cause
|
|
25760
|
+
);
|
|
24695
25761
|
}
|
|
24696
25762
|
tightenFileMode(this.filePath);
|
|
24697
|
-
return
|
|
25763
|
+
return winner;
|
|
24698
25764
|
}
|
|
24699
25765
|
/**
|
|
24700
25766
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
@@ -24705,7 +25771,7 @@ var FileKeyProvider = class {
|
|
|
24705
25771
|
ensureDataDirSync(this.#keysDir);
|
|
24706
25772
|
const file2 = this.filePath;
|
|
24707
25773
|
const tmp = `${file2}.tmp`;
|
|
24708
|
-
|
|
25774
|
+
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
24709
25775
|
`, { mode: DATA_FILE_MODE });
|
|
24710
25776
|
renameSync4(tmp, file2);
|
|
24711
25777
|
tightenFileMode(file2);
|
|
@@ -24831,7 +25897,7 @@ function createKeyProvider(custody, keysDir2) {
|
|
|
24831
25897
|
}
|
|
24832
25898
|
|
|
24833
25899
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24834
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25900
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24835
25901
|
var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
|
|
24836
25902
|
var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
|
|
24837
25903
|
var VAULT_PURGE_POINTER_ID = "*";
|
|
@@ -24858,14 +25924,12 @@ function parsePointer(token) {
|
|
|
24858
25924
|
var SecretVault = class {
|
|
24859
25925
|
#repo;
|
|
24860
25926
|
#keys;
|
|
24861
|
-
#fingerprintKey;
|
|
24862
25927
|
#isConsented;
|
|
24863
25928
|
#verifyGrant;
|
|
24864
25929
|
#now;
|
|
24865
25930
|
constructor(deps) {
|
|
24866
25931
|
this.#repo = deps.repo;
|
|
24867
25932
|
this.#keys = deps.keys;
|
|
24868
|
-
this.#fingerprintKey = deps.fingerprintKey;
|
|
24869
25933
|
this.#isConsented = deps.isConsented;
|
|
24870
25934
|
this.#verifyGrant = deps.verifyGrant;
|
|
24871
25935
|
this.#now = deps.now ?? (() => Date.now());
|
|
@@ -24874,10 +25938,23 @@ var SecretVault = class {
|
|
|
24874
25938
|
* Store a value and return the pointer that stands for it. The same value
|
|
24875
25939
|
* always yields the same pointer on this machine — one row, one pointer id,
|
|
24876
25940
|
* one category — which is what makes dedup and reuse counting work.
|
|
25941
|
+
*
|
|
25942
|
+
* `fingerprintKey` is the exception-key epoch this value's fingerprint is
|
|
25943
|
+
* derived under — a different key from the vault's, with different rotation
|
|
25944
|
+
* semantics. It is a parameter of the WRITE rather than a constructor dep,
|
|
25945
|
+
* and a thunk rather than a value, so that the only way to reach a key is to
|
|
25946
|
+
* store something: a read-only caller never names it, and a caller whose
|
|
25947
|
+
* source mints on absence mints only once consent has actually opened the
|
|
25948
|
+
* write. `refreshFingerprints` takes its key the same way, for the same
|
|
25949
|
+
* reason.
|
|
25950
|
+
*
|
|
25951
|
+
* Resolved once per call, so the fingerprint and the version it is recorded
|
|
25952
|
+
* under can never come from two different epochs.
|
|
24877
25953
|
*/
|
|
24878
|
-
async tokenize(raw, meta3) {
|
|
25954
|
+
async tokenize(raw, meta3, fingerprintKey) {
|
|
24879
25955
|
if (!this.#isConsented()) return CONSENT_ABSENT;
|
|
24880
|
-
const
|
|
25956
|
+
const fpKey = fingerprintKey();
|
|
25957
|
+
const valueFingerprint = fingerprintValue(fpKey, raw);
|
|
24881
25958
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
24882
25959
|
const now = this.#now();
|
|
24883
25960
|
if (existing) {
|
|
@@ -24893,7 +25970,7 @@ var SecretVault = class {
|
|
|
24893
25970
|
{
|
|
24894
25971
|
pointerId: base32Encode(pointerId),
|
|
24895
25972
|
valueFingerprint,
|
|
24896
|
-
fingerprintKeyVersion:
|
|
25973
|
+
fingerprintKeyVersion: fpKey.version,
|
|
24897
25974
|
keyVersion: version2,
|
|
24898
25975
|
// Recorded so the row stays OPENABLE if the wire-format constant ever
|
|
24899
25976
|
// moves: it is part of this row's AEAD AAD. It is not a tag input —
|
|
@@ -25137,7 +26214,7 @@ var SecretVault = class {
|
|
|
25137
26214
|
purgeVault() {
|
|
25138
26215
|
const destroyed = this.#repo.purgeAll();
|
|
25139
26216
|
this.#repo.recordDeref({
|
|
25140
|
-
id:
|
|
26217
|
+
id: randomUUID12(),
|
|
25141
26218
|
pointerId: VAULT_PURGE_POINTER_ID,
|
|
25142
26219
|
at: this.#now(),
|
|
25143
26220
|
target: "human",
|
|
@@ -25206,7 +26283,7 @@ var SecretVault = class {
|
|
|
25206
26283
|
}
|
|
25207
26284
|
#audit(pointerId, opts, outcome) {
|
|
25208
26285
|
this.#repo.recordDeref({
|
|
25209
|
-
id:
|
|
26286
|
+
id: randomUUID12(),
|
|
25210
26287
|
pointerId,
|
|
25211
26288
|
at: this.#now(),
|
|
25212
26289
|
target: opts.target,
|
|
@@ -25221,15 +26298,15 @@ var SecretVault = class {
|
|
|
25221
26298
|
};
|
|
25222
26299
|
|
|
25223
26300
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
25224
|
-
import { existsSync as
|
|
25225
|
-
import { join as
|
|
26301
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
26302
|
+
import { join as join7 } from "path";
|
|
25226
26303
|
var MARKER = "warn-era-capped";
|
|
25227
26304
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
25228
26305
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
25229
|
-
const marker =
|
|
25230
|
-
if (
|
|
26306
|
+
const marker = join7(dataDir2, MARKER);
|
|
26307
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
25231
26308
|
const capped = db.policies.capCategoryActions();
|
|
25232
|
-
|
|
26309
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
25233
26310
|
`, { mode: DATA_FILE_MODE });
|
|
25234
26311
|
return { capped };
|
|
25235
26312
|
}
|
|
@@ -25293,11 +26370,11 @@ function providerFromModelId(modelId) {
|
|
|
25293
26370
|
}
|
|
25294
26371
|
|
|
25295
26372
|
// ../../packages/plugin-sdk/src/config.ts
|
|
25296
|
-
function loadConfig(base = defaultDataDir()) {
|
|
26373
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
25297
26374
|
try {
|
|
25298
26375
|
ensureLayoutDirSync(base);
|
|
25299
|
-
const settingsFile =
|
|
25300
|
-
if (
|
|
26376
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
26377
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
25301
26378
|
} catch {
|
|
25302
26379
|
}
|
|
25303
26380
|
migrateLegacyLayout(base);
|
|
@@ -25308,21 +26385,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
25308
26385
|
dbPath: dbPath(base),
|
|
25309
26386
|
settingsDir: settingsDir(base),
|
|
25310
26387
|
onboarded: settings.onboardedAt != null,
|
|
25311
|
-
provider: resolveProviderSafe()
|
|
26388
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
25312
26389
|
};
|
|
25313
26390
|
}
|
|
25314
|
-
function resolveProviderSafe() {
|
|
26391
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
25315
26392
|
try {
|
|
25316
|
-
return
|
|
26393
|
+
return resolveProviderFn();
|
|
25317
26394
|
} catch {
|
|
25318
26395
|
return { provider: "anthropic" };
|
|
25319
26396
|
}
|
|
25320
26397
|
}
|
|
25321
26398
|
|
|
25322
26399
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
25323
|
-
import { readdirSync, readFileSync as
|
|
26400
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
25324
26401
|
import { homedir as homedir2 } from "os";
|
|
25325
|
-
import { basename as
|
|
26402
|
+
import { basename as basename3, join as join10 } from "path";
|
|
25326
26403
|
|
|
25327
26404
|
// ../../packages/detections/src/egress/registry.ts
|
|
25328
26405
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -27979,7 +29056,7 @@ var gcp_service_account_default = {
|
|
|
27979
29056
|
severity: "critical",
|
|
27980
29057
|
matcher: {
|
|
27981
29058
|
type: "regex",
|
|
27982
|
-
pattern: "
|
|
29059
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27983
29060
|
flags: "g"
|
|
27984
29061
|
},
|
|
27985
29062
|
examples: [
|
|
@@ -28400,8 +29477,8 @@ function scanText(text, ruleVersions) {
|
|
|
28400
29477
|
}
|
|
28401
29478
|
|
|
28402
29479
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
28403
|
-
import { existsSync as
|
|
28404
|
-
import { basename, dirname, isAbsolute, join as
|
|
29480
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29481
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
28405
29482
|
function resolveRepoIdentity(cwd) {
|
|
28406
29483
|
try {
|
|
28407
29484
|
const root = findGitRoot(cwd);
|
|
@@ -28414,7 +29491,7 @@ function resolveRepoIdentity(cwd) {
|
|
|
28414
29491
|
// win32) so the persistence layer's `/`-separated checkout-path patterns
|
|
28415
29492
|
// (the ghost sweep + the read-side worktree filter) match it as written.
|
|
28416
29493
|
url: url2 ?? headRoot.split(sep2).join("/"),
|
|
28417
|
-
name: (url2 ? slugFromUrl(url2) : void 0) ??
|
|
29494
|
+
name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
|
|
28418
29495
|
};
|
|
28419
29496
|
} catch {
|
|
28420
29497
|
return void 0;
|
|
@@ -28434,36 +29511,36 @@ function resolveRepoNwo(cwd) {
|
|
|
28434
29511
|
function findGitRoot(start) {
|
|
28435
29512
|
let dir = start;
|
|
28436
29513
|
for (; ; ) {
|
|
28437
|
-
if (
|
|
28438
|
-
const parent =
|
|
29514
|
+
if (existsSync6(join9(dir, ".git"))) return dir;
|
|
29515
|
+
const parent = dirname2(dir);
|
|
28439
29516
|
if (parent === dir) return void 0;
|
|
28440
29517
|
dir = parent;
|
|
28441
29518
|
}
|
|
28442
29519
|
}
|
|
28443
29520
|
function resolveGitContext(root) {
|
|
28444
|
-
const dotGit =
|
|
29521
|
+
const dotGit = join9(root, ".git");
|
|
28445
29522
|
try {
|
|
28446
|
-
if (
|
|
28447
|
-
return { configPath:
|
|
29523
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
29524
|
+
return { configPath: join9(dotGit, "config"), headRoot: root };
|
|
28448
29525
|
}
|
|
28449
29526
|
} catch {
|
|
28450
29527
|
return void 0;
|
|
28451
29528
|
}
|
|
28452
29529
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
28453
29530
|
if (!target) return void 0;
|
|
28454
|
-
const gitdir = isAbsolute(target) ? target :
|
|
28455
|
-
if (
|
|
28456
|
-
return { configPath:
|
|
29531
|
+
const gitdir = isAbsolute(target) ? target : join9(root, target);
|
|
29532
|
+
if (existsSync6(join9(gitdir, "config"))) {
|
|
29533
|
+
return { configPath: join9(gitdir, "config"), headRoot: root };
|
|
28457
29534
|
}
|
|
28458
|
-
const commonRaw = safeRead(
|
|
29535
|
+
const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
|
|
28459
29536
|
if (!commonRaw) return void 0;
|
|
28460
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
28461
|
-
const headRoot =
|
|
28462
|
-
return { configPath:
|
|
29537
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
|
|
29538
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
|
|
29539
|
+
return { configPath: join9(commonGitDir, "config"), headRoot };
|
|
28463
29540
|
}
|
|
28464
29541
|
function safeRead(path) {
|
|
28465
29542
|
try {
|
|
28466
|
-
return
|
|
29543
|
+
return readFileSync5(path, "utf8");
|
|
28467
29544
|
} catch {
|
|
28468
29545
|
return void 0;
|
|
28469
29546
|
}
|
|
@@ -28514,13 +29591,13 @@ function nwoFromUrl(url2) {
|
|
|
28514
29591
|
}
|
|
28515
29592
|
|
|
28516
29593
|
// ../../packages/plugin-sdk/src/events.ts
|
|
28517
|
-
import { createHash as createHash4, randomUUID as
|
|
29594
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28518
29595
|
function contentHashOf(text) {
|
|
28519
29596
|
return createHash4("sha256").update(text).digest("hex");
|
|
28520
29597
|
}
|
|
28521
29598
|
function buildIngestEvent(input) {
|
|
28522
29599
|
return {
|
|
28523
|
-
id:
|
|
29600
|
+
id: randomUUID13(),
|
|
28524
29601
|
sourceTool: input.sourceTool,
|
|
28525
29602
|
kind: input.kind,
|
|
28526
29603
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -28531,21 +29608,473 @@ function buildIngestEvent(input) {
|
|
|
28531
29608
|
// SDK boot in the fail-open hook path). Preserve any id the caller already set.
|
|
28532
29609
|
metadata: {
|
|
28533
29610
|
...input.metadata,
|
|
28534
|
-
correlationId: input.metadata?.correlationId ??
|
|
29611
|
+
correlationId: input.metadata?.correlationId ?? randomUUID13()
|
|
29612
|
+
}
|
|
29613
|
+
};
|
|
29614
|
+
}
|
|
29615
|
+
|
|
29616
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29617
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29618
|
+
import { fileURLToPath } from "url";
|
|
29619
|
+
import { Worker } from "worker_threads";
|
|
29620
|
+
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
29621
|
+
var ISOLATED_PROBE_BUDGET_MS = 1e3;
|
|
29622
|
+
var ISOLATED_START_BUDGET_MS = 5e3;
|
|
29623
|
+
var ATTRIBUTION_MIN_RULE_MS = 500;
|
|
29624
|
+
var ATTRIBUTION_MIN_SHARE = 0.5;
|
|
29625
|
+
var resolvedWorkerUrl;
|
|
29626
|
+
function resolveWorkerUrl() {
|
|
29627
|
+
if (resolvedWorkerUrl !== void 0) return resolvedWorkerUrl ?? void 0;
|
|
29628
|
+
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
29629
|
+
const candidate = new URL(name, import.meta.url);
|
|
29630
|
+
try {
|
|
29631
|
+
if (existsSync7(fileURLToPath(candidate))) {
|
|
29632
|
+
resolvedWorkerUrl = candidate;
|
|
29633
|
+
return candidate;
|
|
29634
|
+
}
|
|
29635
|
+
} catch {
|
|
29636
|
+
}
|
|
29637
|
+
}
|
|
29638
|
+
resolvedWorkerUrl = null;
|
|
29639
|
+
return void 0;
|
|
29640
|
+
}
|
|
29641
|
+
function messageOf(error51) {
|
|
29642
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
29643
|
+
}
|
|
29644
|
+
function createIsolatedScanner(data, opts = {}) {
|
|
29645
|
+
const budgetMs = opts.budgetMs ?? ISOLATED_SCAN_BUDGET_MS;
|
|
29646
|
+
const probeBudgetMs = opts.probeBudgetMs ?? ISOLATED_PROBE_BUDGET_MS;
|
|
29647
|
+
const startBudgetMs = opts.startBudgetMs ?? ISOLATED_START_BUDGET_MS;
|
|
29648
|
+
const minAttributionMs = opts.minAttributionMs ?? ATTRIBUTION_MIN_RULE_MS;
|
|
29649
|
+
let worker;
|
|
29650
|
+
let readyWorker;
|
|
29651
|
+
let broken;
|
|
29652
|
+
let closed = false;
|
|
29653
|
+
let nextJobId = 1;
|
|
29654
|
+
let pending;
|
|
29655
|
+
const terminating = /* @__PURE__ */ new Set();
|
|
29656
|
+
let chain = Promise.resolve();
|
|
29657
|
+
function clearTimers(job) {
|
|
29658
|
+
if (job.startupTimer !== void 0) clearTimeout(job.startupTimer);
|
|
29659
|
+
if (job.timer !== void 0) clearTimeout(job.timer);
|
|
29660
|
+
}
|
|
29661
|
+
function take() {
|
|
29662
|
+
const job = pending;
|
|
29663
|
+
if (!job) return void 0;
|
|
29664
|
+
pending = void 0;
|
|
29665
|
+
clearTimers(job);
|
|
29666
|
+
worker?.unref();
|
|
29667
|
+
return job;
|
|
29668
|
+
}
|
|
29669
|
+
function failPending(outcome) {
|
|
29670
|
+
take()?.fail(outcome);
|
|
29671
|
+
}
|
|
29672
|
+
function kill(dead) {
|
|
29673
|
+
if (worker === dead) worker = void 0;
|
|
29674
|
+
if (readyWorker === dead) readyWorker = void 0;
|
|
29675
|
+
const done = dead.terminate().catch(() => void 0);
|
|
29676
|
+
terminating.add(done);
|
|
29677
|
+
void done.finally(() => terminating.delete(done));
|
|
29678
|
+
}
|
|
29679
|
+
function onDeadline(job) {
|
|
29680
|
+
if (pending !== job) return;
|
|
29681
|
+
const now = performance.now();
|
|
29682
|
+
const runningMs = now - job.progressAt;
|
|
29683
|
+
const elapsedMs = now - job.startedAt;
|
|
29684
|
+
const blamed = job.progressIndex >= 0 && runningMs >= minAttributionMs && runningMs >= elapsedMs * ATTRIBUTION_MIN_SHARE;
|
|
29685
|
+
const culpritIndex = blamed ? job.progressIndex : void 0;
|
|
29686
|
+
kill(job.worker);
|
|
29687
|
+
failPending({ status: "timeout", culpritIndex, elapsedMs });
|
|
29688
|
+
}
|
|
29689
|
+
function ensureWorker() {
|
|
29690
|
+
if (worker) return worker;
|
|
29691
|
+
const url2 = opts.workerUrl ?? resolveWorkerUrl();
|
|
29692
|
+
if (!url2) {
|
|
29693
|
+
return {
|
|
29694
|
+
error: "the scan worker script was not found next to this bundle"
|
|
29695
|
+
};
|
|
29696
|
+
}
|
|
29697
|
+
let started;
|
|
29698
|
+
try {
|
|
29699
|
+
started = new Worker(url2, { workerData: data });
|
|
29700
|
+
} catch (error51) {
|
|
29701
|
+
return { error: `could not start the scan worker: ${messageOf(error51)}` };
|
|
29702
|
+
}
|
|
29703
|
+
opts.onWorkerStart?.(started.threadId);
|
|
29704
|
+
started.on("message", (message) => {
|
|
29705
|
+
if (worker !== started) return;
|
|
29706
|
+
if (message.kind === "ready") {
|
|
29707
|
+
readyWorker = started;
|
|
29708
|
+
if (pending?.worker === started) beginDeadline(pending);
|
|
29709
|
+
return;
|
|
29710
|
+
}
|
|
29711
|
+
if (message.kind === "progress") {
|
|
29712
|
+
if (pending?.worker === started) {
|
|
29713
|
+
pending.progressIndex = message.index;
|
|
29714
|
+
pending.progressAt = performance.now();
|
|
29715
|
+
}
|
|
29716
|
+
return;
|
|
29717
|
+
}
|
|
29718
|
+
if (pending?.id !== message.id) return;
|
|
29719
|
+
if (message.kind === "failed") {
|
|
29720
|
+
failPending({
|
|
29721
|
+
status: "unavailable",
|
|
29722
|
+
reason: `the scan worker failed: ${message.message}`
|
|
29723
|
+
});
|
|
29724
|
+
return;
|
|
29725
|
+
}
|
|
29726
|
+
const job = take();
|
|
29727
|
+
if (job && !job.reply(message)) {
|
|
29728
|
+
job.fail({ status: "unavailable", reason: "the scan worker answered the wrong job" });
|
|
29729
|
+
}
|
|
29730
|
+
});
|
|
29731
|
+
started.on("error", (error51) => {
|
|
29732
|
+
if (worker !== started) return;
|
|
29733
|
+
broken = messageOf(error51);
|
|
29734
|
+
worker = void 0;
|
|
29735
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29736
|
+
failPending({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29737
|
+
});
|
|
29738
|
+
started.on("exit", () => {
|
|
29739
|
+
if (worker !== started) return;
|
|
29740
|
+
broken ??= "the scan worker exited before answering";
|
|
29741
|
+
worker = void 0;
|
|
29742
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29743
|
+
failPending({ status: "unavailable", reason: "the scan worker exited before answering" });
|
|
29744
|
+
});
|
|
29745
|
+
started.unref();
|
|
29746
|
+
worker = started;
|
|
29747
|
+
return started;
|
|
29748
|
+
}
|
|
29749
|
+
function beginDeadline(job) {
|
|
29750
|
+
if (job.startupTimer !== void 0) {
|
|
29751
|
+
clearTimeout(job.startupTimer);
|
|
29752
|
+
job.startupTimer = void 0;
|
|
29753
|
+
}
|
|
29754
|
+
if (job.timer !== void 0) return;
|
|
29755
|
+
job.startedAt = performance.now();
|
|
29756
|
+
job.progressAt = job.startedAt;
|
|
29757
|
+
job.timer = setTimeout(() => {
|
|
29758
|
+
onDeadline(job);
|
|
29759
|
+
}, job.budgetMs);
|
|
29760
|
+
}
|
|
29761
|
+
function runOne(spec, fail) {
|
|
29762
|
+
if (closed) {
|
|
29763
|
+
fail({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29764
|
+
return;
|
|
29765
|
+
}
|
|
29766
|
+
if (broken !== void 0) {
|
|
29767
|
+
fail({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29768
|
+
return;
|
|
29769
|
+
}
|
|
29770
|
+
const started = ensureWorker();
|
|
29771
|
+
if (!(started instanceof Worker)) {
|
|
29772
|
+
broken = started.error;
|
|
29773
|
+
fail({ status: "unavailable", reason: started.error });
|
|
29774
|
+
return;
|
|
29775
|
+
}
|
|
29776
|
+
const id = nextJobId++;
|
|
29777
|
+
const now = performance.now();
|
|
29778
|
+
const job = {
|
|
29779
|
+
id,
|
|
29780
|
+
worker: started,
|
|
29781
|
+
budgetMs: spec.budgetMs,
|
|
29782
|
+
startedAt: now,
|
|
29783
|
+
progressIndex: -1,
|
|
29784
|
+
progressAt: now,
|
|
29785
|
+
startupTimer: void 0,
|
|
29786
|
+
timer: void 0,
|
|
29787
|
+
reply: spec.reply,
|
|
29788
|
+
fail
|
|
29789
|
+
};
|
|
29790
|
+
pending = job;
|
|
29791
|
+
started.ref();
|
|
29792
|
+
if (readyWorker === started) {
|
|
29793
|
+
beginDeadline(job);
|
|
29794
|
+
} else {
|
|
29795
|
+
job.startupTimer = setTimeout(() => {
|
|
29796
|
+
if (pending !== job) return;
|
|
29797
|
+
kill(job.worker);
|
|
29798
|
+
failPending({
|
|
29799
|
+
status: "unavailable",
|
|
29800
|
+
reason: `the scan worker did not start within ${String(startBudgetMs)}ms`
|
|
29801
|
+
});
|
|
29802
|
+
}, startBudgetMs);
|
|
29803
|
+
}
|
|
29804
|
+
try {
|
|
29805
|
+
started.postMessage(spec.build(id));
|
|
29806
|
+
} catch (error51) {
|
|
29807
|
+
failPending({
|
|
29808
|
+
// The thread went away between the ref and the post.
|
|
29809
|
+
status: "unavailable",
|
|
29810
|
+
reason: `could not reach the scan worker: ${messageOf(error51)}`
|
|
29811
|
+
});
|
|
29812
|
+
}
|
|
29813
|
+
}
|
|
29814
|
+
function enqueue(spec) {
|
|
29815
|
+
const next = chain.then(
|
|
29816
|
+
() => new Promise((resolve2) => {
|
|
29817
|
+
spec(resolve2);
|
|
29818
|
+
})
|
|
29819
|
+
);
|
|
29820
|
+
chain = next.then(
|
|
29821
|
+
() => void 0,
|
|
29822
|
+
() => void 0
|
|
29823
|
+
);
|
|
29824
|
+
return next;
|
|
29825
|
+
}
|
|
29826
|
+
return {
|
|
29827
|
+
scan(text, context, scanOpts) {
|
|
29828
|
+
return enqueue((resolve2) => {
|
|
29829
|
+
runOne(
|
|
29830
|
+
{
|
|
29831
|
+
budgetMs,
|
|
29832
|
+
build: (id) => ({
|
|
29833
|
+
kind: "scan",
|
|
29834
|
+
id,
|
|
29835
|
+
text,
|
|
29836
|
+
filePath: context?.filePath,
|
|
29837
|
+
attribute: scanOpts?.attribute === true
|
|
29838
|
+
}),
|
|
29839
|
+
reply: (message) => {
|
|
29840
|
+
if (message.kind !== "result") return false;
|
|
29841
|
+
resolve2({ status: "ok", findings: message.findings });
|
|
29842
|
+
return true;
|
|
29843
|
+
}
|
|
29844
|
+
},
|
|
29845
|
+
resolve2
|
|
29846
|
+
);
|
|
29847
|
+
});
|
|
29848
|
+
},
|
|
29849
|
+
probe(rule) {
|
|
29850
|
+
return enqueue((resolve2) => {
|
|
29851
|
+
runOne(
|
|
29852
|
+
{
|
|
29853
|
+
budgetMs: probeBudgetMs,
|
|
29854
|
+
build: (id) => ({ kind: "probe", id, rule }),
|
|
29855
|
+
reply: (message) => {
|
|
29856
|
+
if (message.kind !== "probed") return false;
|
|
29857
|
+
resolve2({ status: "ok", safe: message.safe, worstMs: message.worstMs });
|
|
29858
|
+
return true;
|
|
29859
|
+
}
|
|
29860
|
+
},
|
|
29861
|
+
resolve2
|
|
29862
|
+
);
|
|
29863
|
+
});
|
|
29864
|
+
},
|
|
29865
|
+
async close() {
|
|
29866
|
+
closed = true;
|
|
29867
|
+
const live = worker;
|
|
29868
|
+
worker = void 0;
|
|
29869
|
+
readyWorker = void 0;
|
|
29870
|
+
failPending({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29871
|
+
if (live) kill(live);
|
|
29872
|
+
await Promise.all([...terminating]);
|
|
29873
|
+
}
|
|
29874
|
+
};
|
|
29875
|
+
}
|
|
29876
|
+
|
|
29877
|
+
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
29878
|
+
var PASS_BUDGET_MS = 2e3;
|
|
29879
|
+
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
29880
|
+
function ruleProbeKey(rule) {
|
|
29881
|
+
if (rule.matcher.type !== "regex") return void 0;
|
|
29882
|
+
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
29883
|
+
}
|
|
29884
|
+
function warn(rule, verb, detail, recoverable) {
|
|
29885
|
+
const hint = recoverable ? ` (${UNQUARANTINE_HINT})` : "";
|
|
29886
|
+
process.stderr.write(`[aka] ${verb} rule "${rule.id}": ${detail}${hint}
|
|
29887
|
+
`);
|
|
29888
|
+
}
|
|
29889
|
+
function warnQuarantined(rule, worstMs, cached2) {
|
|
29890
|
+
warn(
|
|
29891
|
+
rule,
|
|
29892
|
+
"quarantined",
|
|
29893
|
+
Number.isFinite(worstMs) ? `regex matcher exceeded the ReDoS timing budget (${worstMs.toFixed(1)}ms); excluded from this scan.` : "the timing battery failed while measuring its regex matcher; excluded from this scan.",
|
|
29894
|
+
cached2
|
|
29895
|
+
);
|
|
29896
|
+
}
|
|
29897
|
+
function warnUnmeasured(rule) {
|
|
29898
|
+
warn(
|
|
29899
|
+
rule,
|
|
29900
|
+
"skipped",
|
|
29901
|
+
"the timing pre-flight ran out of time before this rule could be measured; excluded for the rest of this run, and measured again next time.",
|
|
29902
|
+
false
|
|
29903
|
+
);
|
|
29904
|
+
}
|
|
29905
|
+
function warnUnmeasurable(reason, count) {
|
|
29906
|
+
process.stderr.write(
|
|
29907
|
+
`[aka] ${String(count)} pulled/custom-pack rule(s) could not be time-checked: ${reason}. That is a problem with this install, not with the rules \u2014 until it is fixed they are excluded from every scan on this machine. Nothing was quarantined, so reinstalling AKA brings them straight back.
|
|
29908
|
+
`
|
|
29909
|
+
);
|
|
29910
|
+
}
|
|
29911
|
+
async function quarantineRule(gateway, rule, worstMs, detail) {
|
|
29912
|
+
const key = ruleProbeKey(rule);
|
|
29913
|
+
let cached2 = false;
|
|
29914
|
+
if (key !== void 0) {
|
|
29915
|
+
try {
|
|
29916
|
+
await gateway.setRuleProbeVerdict(key, "quarantined", worstMs);
|
|
29917
|
+
cached2 = true;
|
|
29918
|
+
} catch {
|
|
29919
|
+
}
|
|
29920
|
+
}
|
|
29921
|
+
warn(rule, "quarantined", detail, cached2);
|
|
29922
|
+
}
|
|
29923
|
+
async function filterUnsafeRules(rules, gateway, opts) {
|
|
29924
|
+
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
29925
|
+
const prober = opts?.prober;
|
|
29926
|
+
const passStart = performance.now();
|
|
29927
|
+
const safe = [];
|
|
29928
|
+
const unmeasurable = /* @__PURE__ */ new Map();
|
|
29929
|
+
try {
|
|
29930
|
+
for (const rule of rules) {
|
|
29931
|
+
const key = ruleProbeKey(rule);
|
|
29932
|
+
if (key === void 0) {
|
|
29933
|
+
safe.push(rule);
|
|
29934
|
+
continue;
|
|
29935
|
+
}
|
|
29936
|
+
let cached2;
|
|
29937
|
+
try {
|
|
29938
|
+
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
29939
|
+
} catch {
|
|
29940
|
+
cached2 = void 0;
|
|
29941
|
+
}
|
|
29942
|
+
if (cached2) {
|
|
29943
|
+
if (cached2.verdict === "safe") safe.push(rule);
|
|
29944
|
+
else warnQuarantined(rule, cached2.worstProbeMs, true);
|
|
29945
|
+
continue;
|
|
29946
|
+
}
|
|
29947
|
+
if (performance.now() - passStart >= passBudgetMs) {
|
|
29948
|
+
warnUnmeasured(rule);
|
|
29949
|
+
continue;
|
|
29950
|
+
}
|
|
29951
|
+
let isSafe;
|
|
29952
|
+
let worstMs;
|
|
29953
|
+
if (prober) {
|
|
29954
|
+
const outcome = await prober.probe(rule);
|
|
29955
|
+
if (outcome.status === "unavailable") {
|
|
29956
|
+
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29957
|
+
continue;
|
|
29958
|
+
}
|
|
29959
|
+
isSafe = outcome.status === "ok" ? outcome.safe : false;
|
|
29960
|
+
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
29961
|
+
} else {
|
|
29962
|
+
try {
|
|
29963
|
+
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
29964
|
+
} catch {
|
|
29965
|
+
isSafe = false;
|
|
29966
|
+
worstMs = Number.POSITIVE_INFINITY;
|
|
29967
|
+
}
|
|
29968
|
+
}
|
|
29969
|
+
let persisted = false;
|
|
29970
|
+
try {
|
|
29971
|
+
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
29972
|
+
persisted = true;
|
|
29973
|
+
} catch {
|
|
29974
|
+
}
|
|
29975
|
+
if (isSafe) safe.push(rule);
|
|
29976
|
+
else warnQuarantined(rule, worstMs, persisted);
|
|
29977
|
+
}
|
|
29978
|
+
} finally {
|
|
29979
|
+
for (const [reason, count] of unmeasurable) warnUnmeasurable(reason, count);
|
|
29980
|
+
}
|
|
29981
|
+
return safe;
|
|
29982
|
+
}
|
|
29983
|
+
|
|
29984
|
+
// ../../packages/plugin-sdk/src/guarded-scan.ts
|
|
29985
|
+
var DEFAULT_DEGRADE_SCOPE = "the rest of this process";
|
|
29986
|
+
function warnDegraded(scope, dropped, detail) {
|
|
29987
|
+
process.stderr.write(
|
|
29988
|
+
`[aka] isolated scanning is off for ${scope}: ${detail}. ${String(dropped)} pulled/custom-pack rule(s) are excluded; the built-in packs still run.
|
|
29989
|
+
`
|
|
29990
|
+
);
|
|
29991
|
+
}
|
|
29992
|
+
function createGuardedScanner(partition, gateway, opts) {
|
|
29993
|
+
const degradeScope = opts?.degradeScope ?? DEFAULT_DEGRADE_SCOPE;
|
|
29994
|
+
const verified = partition.verified;
|
|
29995
|
+
let unverified = partition.unverified;
|
|
29996
|
+
let isolated = unverified.length > 0 ? createIsolatedScanner({ verified, unverified }, opts) : void 0;
|
|
29997
|
+
let retired = false;
|
|
29998
|
+
function inProcess(text, context) {
|
|
29999
|
+
return scan(text, verified, context);
|
|
30000
|
+
}
|
|
30001
|
+
async function retire() {
|
|
30002
|
+
const live = isolated;
|
|
30003
|
+
isolated = void 0;
|
|
30004
|
+
unverified = [];
|
|
30005
|
+
if (live) await live.close();
|
|
30006
|
+
}
|
|
30007
|
+
async function degrade() {
|
|
30008
|
+
retired = true;
|
|
30009
|
+
await retire();
|
|
30010
|
+
}
|
|
30011
|
+
async function attempt(active, text, context, attribute) {
|
|
30012
|
+
try {
|
|
30013
|
+
return await active.scan(text, context, { attribute });
|
|
30014
|
+
} catch (error51) {
|
|
30015
|
+
return {
|
|
30016
|
+
status: "unavailable",
|
|
30017
|
+
reason: error51 instanceof Error ? error51.message : "the scan worker failed unexpectedly"
|
|
30018
|
+
};
|
|
30019
|
+
}
|
|
30020
|
+
}
|
|
30021
|
+
async function guardedScan(text, context) {
|
|
30022
|
+
const active = isolated;
|
|
30023
|
+
if (!active) return inProcess(text, context);
|
|
30024
|
+
let outcome = await attempt(active, text, context, false);
|
|
30025
|
+
if (outcome.status === "ok") return outcome.findings;
|
|
30026
|
+
if (outcome.status === "timeout") outcome = await attempt(active, text, context, true);
|
|
30027
|
+
const dropped = unverified.length;
|
|
30028
|
+
if (outcome.status === "ok") {
|
|
30029
|
+
warnDegraded(
|
|
30030
|
+
degradeScope,
|
|
30031
|
+
dropped,
|
|
30032
|
+
"a scan overran its bound once and no rule could be held responsible"
|
|
30033
|
+
);
|
|
30034
|
+
const findings = outcome.findings;
|
|
30035
|
+
await degrade();
|
|
30036
|
+
return findings;
|
|
30037
|
+
}
|
|
30038
|
+
if (outcome.status === "timeout") {
|
|
30039
|
+
const culprit = outcome.culpritIndex === void 0 ? void 0 : unverified[outcome.culpritIndex];
|
|
30040
|
+
if (culprit) {
|
|
30041
|
+
await quarantineRule(
|
|
30042
|
+
gateway,
|
|
30043
|
+
culprit,
|
|
30044
|
+
outcome.elapsedMs,
|
|
30045
|
+
`it did not finish within the ${outcome.elapsedMs.toFixed(0)}ms isolated-scan bound and was terminated; excluded from every later scan.`
|
|
30046
|
+
);
|
|
30047
|
+
}
|
|
30048
|
+
warnDegraded(
|
|
30049
|
+
degradeScope,
|
|
30050
|
+
dropped,
|
|
30051
|
+
culprit ? `rule "${culprit.id}" had to be terminated mid-scan` : `a scan was terminated at the ${outcome.elapsedMs.toFixed(0)}ms bound and no single rule could be held responsible, so nothing was quarantined and the next process will try these rules again`
|
|
30052
|
+
);
|
|
30053
|
+
} else {
|
|
30054
|
+
warnDegraded(degradeScope, dropped, outcome.reason);
|
|
30055
|
+
}
|
|
30056
|
+
await degrade();
|
|
30057
|
+
return inProcess(text, context);
|
|
30058
|
+
}
|
|
30059
|
+
return {
|
|
30060
|
+
scan: guardedScan,
|
|
30061
|
+
degraded: () => retired,
|
|
30062
|
+
async close() {
|
|
30063
|
+
await retire();
|
|
28535
30064
|
}
|
|
28536
30065
|
};
|
|
28537
30066
|
}
|
|
28538
30067
|
|
|
28539
30068
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
28540
|
-
import { arch, hostname as
|
|
30069
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
28541
30070
|
function resolveInventoryContext(input) {
|
|
28542
30071
|
const host = {
|
|
28543
30072
|
objectType: "host",
|
|
28544
30073
|
// Stable-ish machine id; os/arch live in the descriptive bag (a
|
|
28545
30074
|
// harder machine id can replace this without a schema change).
|
|
28546
|
-
identityKey:
|
|
28547
|
-
title:
|
|
28548
|
-
attributes: { host_name:
|
|
30075
|
+
identityKey: hostname4(),
|
|
30076
|
+
title: hostname4(),
|
|
30077
|
+
attributes: { host_name: hostname4(), os: platform(), os_version: release(), arch: arch() }
|
|
28549
30078
|
};
|
|
28550
30079
|
const harnessAttributes = {};
|
|
28551
30080
|
if (input.harnessVersion != null) harnessAttributes.harness_version = input.harnessVersion;
|
|
@@ -28566,17 +30095,43 @@ function resolveInventoryContext(input) {
|
|
|
28566
30095
|
}
|
|
28567
30096
|
|
|
28568
30097
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
28569
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
28570
|
-
import { join as
|
|
30098
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
30099
|
+
import { join as join11 } from "path";
|
|
28571
30100
|
|
|
28572
30101
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
28573
|
-
import { readdirSync as
|
|
28574
|
-
import { basename as
|
|
30102
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
30103
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
28575
30104
|
|
|
28576
30105
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
28577
30106
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
28578
|
-
import { existsSync as
|
|
28579
|
-
import { basename as
|
|
30107
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
30108
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
30109
|
+
|
|
30110
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30111
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
30112
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30113
|
+
return v;
|
|
30114
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30115
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
30116
|
+
if (typeof v !== "string") return false;
|
|
30117
|
+
const normalized = v.trim().toLowerCase();
|
|
30118
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
30119
|
+
}, external_exports.boolean()).catch(false);
|
|
30120
|
+
var antigravityProviderEnvShape = {
|
|
30121
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
30122
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
30123
|
+
};
|
|
30124
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
30125
|
+
|
|
30126
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
30127
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
30128
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30129
|
+
return v;
|
|
30130
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30131
|
+
var codexProviderEnvShape = {
|
|
30132
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
30133
|
+
};
|
|
30134
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
28580
30135
|
|
|
28581
30136
|
// ../../packages/plugin-sdk/src/raw-egress.ts
|
|
28582
30137
|
var RawEgressError = class extends Error {
|
|
@@ -28618,61 +30173,8 @@ function safeMaskedMatch(rawMatch) {
|
|
|
28618
30173
|
return masked;
|
|
28619
30174
|
}
|
|
28620
30175
|
|
|
28621
|
-
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
28622
|
-
var PASS_BUDGET_MS = 2e3;
|
|
28623
|
-
function ruleProbeKey(rule) {
|
|
28624
|
-
if (rule.matcher.type !== "regex") return void 0;
|
|
28625
|
-
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
28626
|
-
}
|
|
28627
|
-
function warnQuarantined(rule, worstMs) {
|
|
28628
|
-
const timing = worstMs === void 0 ? "not verified in time" : `${worstMs.toFixed(1)}ms`;
|
|
28629
|
-
process.stderr.write(
|
|
28630
|
-
`[aka] quarantined rule "${rule.id}": regex matcher exceeded the ReDoS timing budget (${timing}); excluded from this scan.
|
|
28631
|
-
`
|
|
28632
|
-
);
|
|
28633
|
-
}
|
|
28634
|
-
async function filterUnsafeRules(rules, gateway, opts) {
|
|
28635
|
-
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
28636
|
-
const passStart = performance.now();
|
|
28637
|
-
const safe = [];
|
|
28638
|
-
for (const rule of rules) {
|
|
28639
|
-
const key = ruleProbeKey(rule);
|
|
28640
|
-
if (key === void 0) {
|
|
28641
|
-
safe.push(rule);
|
|
28642
|
-
continue;
|
|
28643
|
-
}
|
|
28644
|
-
let cached2;
|
|
28645
|
-
try {
|
|
28646
|
-
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
28647
|
-
} catch {
|
|
28648
|
-
cached2 = void 0;
|
|
28649
|
-
}
|
|
28650
|
-
if (cached2) {
|
|
28651
|
-
if (cached2.verdict === "safe") safe.push(rule);
|
|
28652
|
-
else warnQuarantined(rule, cached2.worstProbeMs);
|
|
28653
|
-
continue;
|
|
28654
|
-
}
|
|
28655
|
-
if (performance.now() - passStart >= passBudgetMs) {
|
|
28656
|
-
warnQuarantined(rule, void 0);
|
|
28657
|
-
continue;
|
|
28658
|
-
}
|
|
28659
|
-
let isSafe;
|
|
28660
|
-
let worstMs;
|
|
28661
|
-
try {
|
|
28662
|
-
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
28663
|
-
} catch {
|
|
28664
|
-
isSafe = false;
|
|
28665
|
-
worstMs = Number.POSITIVE_INFINITY;
|
|
28666
|
-
}
|
|
28667
|
-
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
28668
|
-
if (isSafe) safe.push(rule);
|
|
28669
|
-
else warnQuarantined(rule, worstMs);
|
|
28670
|
-
}
|
|
28671
|
-
return safe;
|
|
28672
|
-
}
|
|
28673
|
-
|
|
28674
30176
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28675
|
-
import { randomUUID as
|
|
30177
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28676
30178
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
28677
30179
|
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
28678
30180
|
function entryIsActive(entry, now) {
|
|
@@ -28697,6 +30199,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28697
30199
|
const dataDir2 = opts?.dataDir;
|
|
28698
30200
|
let policies = [];
|
|
28699
30201
|
let rules = [];
|
|
30202
|
+
let scanner;
|
|
28700
30203
|
let bundleExceptions = [];
|
|
28701
30204
|
let initialized = false;
|
|
28702
30205
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
@@ -28722,8 +30225,24 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28722
30225
|
return key !== void 0 && bundledProbeKeys.has(key);
|
|
28723
30226
|
});
|
|
28724
30227
|
const needsGate = incoming.filter((rule) => !ciVerified.includes(rule));
|
|
28725
|
-
|
|
28726
|
-
|
|
30228
|
+
let prober;
|
|
30229
|
+
const gated = await filterUnsafeRules(needsGate, gateway, {
|
|
30230
|
+
prober: {
|
|
30231
|
+
probe: (rule) => {
|
|
30232
|
+
prober ??= createIsolatedScanner({ verified: [], unverified: [] }, opts?.scanIsolation);
|
|
30233
|
+
return prober.probe(rule);
|
|
30234
|
+
}
|
|
30235
|
+
}
|
|
30236
|
+
});
|
|
30237
|
+
await prober?.close();
|
|
30238
|
+
const verified = bundle.rulesComplete ? [...ciVerified] : [...getLoadedRules(), ...ciVerified];
|
|
30239
|
+
const unverified = [];
|
|
30240
|
+
for (const rule of gated) {
|
|
30241
|
+
if (rule.matcher.type === "regex") unverified.push(rule);
|
|
30242
|
+
else verified.push(rule);
|
|
30243
|
+
}
|
|
30244
|
+
rules = [...verified, ...unverified];
|
|
30245
|
+
scanner = createGuardedScanner({ verified, unverified }, gateway, opts?.scanIsolation);
|
|
28727
30246
|
bundleExceptions = bundle.exceptions ?? [];
|
|
28728
30247
|
initialized = true;
|
|
28729
30248
|
}
|
|
@@ -28863,7 +30382,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28863
30382
|
const pair = `${finding.ruleId}:${fp}`;
|
|
28864
30383
|
if (seen.has(pair)) continue;
|
|
28865
30384
|
seen.add(pair);
|
|
28866
|
-
const reference =
|
|
30385
|
+
const reference = randomUUID14().replaceAll("-", "").slice(0, 6);
|
|
28867
30386
|
const maskedValue = maskMatch(finding.rawMatch);
|
|
28868
30387
|
try {
|
|
28869
30388
|
await gateway.recordBlockedDetection({
|
|
@@ -28887,8 +30406,10 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28887
30406
|
async function evaluate(text, context, ctx) {
|
|
28888
30407
|
try {
|
|
28889
30408
|
await ensureInitialized();
|
|
30409
|
+
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
28890
30410
|
const shielded = shieldPointers(text);
|
|
28891
|
-
const
|
|
30411
|
+
const matched = await scanner.scan(shielded.text, context);
|
|
30412
|
+
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
28892
30413
|
const fpCache = /* @__PURE__ */ new Map();
|
|
28893
30414
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
28894
30415
|
const decision = decide(findings, text, excepted);
|
|
@@ -28945,7 +30466,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28945
30466
|
valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
|
|
28946
30467
|
}) : void 0;
|
|
28947
30468
|
return {
|
|
28948
|
-
id:
|
|
30469
|
+
id: randomUUID14(),
|
|
28949
30470
|
eventId: event.id,
|
|
28950
30471
|
ruleId: match.ruleId,
|
|
28951
30472
|
category: match.category,
|
|
@@ -28971,21 +30492,28 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28971
30492
|
const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
28972
30493
|
return contentHashOf(JSON.stringify(sorted));
|
|
28973
30494
|
} catch {
|
|
28974
|
-
return `unresolved-${
|
|
30495
|
+
return `unresolved-${randomUUID14()}`;
|
|
28975
30496
|
}
|
|
28976
30497
|
}
|
|
30498
|
+
function scanIsolationDegraded() {
|
|
30499
|
+
return scanner?.degraded() ?? false;
|
|
30500
|
+
}
|
|
28977
30501
|
async function close() {
|
|
30502
|
+
try {
|
|
30503
|
+
await scanner?.close();
|
|
30504
|
+
} catch {
|
|
30505
|
+
}
|
|
28978
30506
|
await gateway.close();
|
|
28979
30507
|
}
|
|
28980
|
-
return { processText, capture, rulesetFingerprint, close };
|
|
30508
|
+
return { processText, capture, rulesetFingerprint, scanIsolationDegraded, close };
|
|
28981
30509
|
}
|
|
28982
30510
|
|
|
28983
30511
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28984
30512
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28985
30513
|
|
|
28986
30514
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28987
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
28988
|
-
import { join as
|
|
30515
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
30516
|
+
import { join as join13 } from "path";
|
|
28989
30517
|
|
|
28990
30518
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
28991
30519
|
function redactedPlaceholder(category) {
|
|
@@ -29227,7 +30755,6 @@ function createVaultGlue(options) {
|
|
|
29227
30755
|
const vault = new SecretVault({
|
|
29228
30756
|
repo: db.secretVault,
|
|
29229
30757
|
keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
|
|
29230
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
29231
30758
|
// Read live so a revocation applies to the very next call, not the next
|
|
29232
30759
|
// process.
|
|
29233
30760
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
|
|
@@ -29248,8 +30775,10 @@ function createVaultGlue(options) {
|
|
|
29248
30775
|
return decision.allow;
|
|
29249
30776
|
}
|
|
29250
30777
|
});
|
|
30778
|
+
let fingerprintKey;
|
|
30779
|
+
const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
|
|
29251
30780
|
const vaultWithSightings = {
|
|
29252
|
-
tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
|
|
30781
|
+
tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
|
|
29253
30782
|
detokenize: (token, opts) => vault.detokenize(token, opts),
|
|
29254
30783
|
describePointer: (token) => vault.describePointer(token),
|
|
29255
30784
|
resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
|
|
@@ -29282,8 +30811,59 @@ var UNOPENABLE_VAULT = {
|
|
|
29282
30811
|
resolvePointerIdentity: () => Promise.resolve(null)
|
|
29283
30812
|
};
|
|
29284
30813
|
|
|
30814
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
30815
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
30816
|
+
import { join as join14 } from "path";
|
|
30817
|
+
|
|
30818
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
30819
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
30820
|
+
import { tmpdir } from "os";
|
|
30821
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
30822
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
30823
|
+
ruleId: external_exports.string(),
|
|
30824
|
+
category: DetectionCategory,
|
|
30825
|
+
valueFingerprint: external_exports.string(),
|
|
30826
|
+
keyVersion: external_exports.number(),
|
|
30827
|
+
maskedValue: external_exports.string(),
|
|
30828
|
+
justification: external_exports.string()
|
|
30829
|
+
});
|
|
30830
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
30831
|
+
category: DetectionCategory,
|
|
30832
|
+
action: BuiltinPolicyId,
|
|
30833
|
+
genuineCount: external_exports.number(),
|
|
30834
|
+
fpCount: external_exports.number(),
|
|
30835
|
+
reasoning: external_exports.string()
|
|
30836
|
+
});
|
|
30837
|
+
var JoinEntrySchema = external_exports.object({
|
|
30838
|
+
id: external_exports.string(),
|
|
30839
|
+
ruleId: external_exports.string(),
|
|
30840
|
+
category: DetectionCategory,
|
|
30841
|
+
valueFingerprint: external_exports.string().optional(),
|
|
30842
|
+
keyVersion: external_exports.number().optional(),
|
|
30843
|
+
maskedMatch: external_exports.string(),
|
|
30844
|
+
maskedContext: external_exports.string()
|
|
30845
|
+
});
|
|
30846
|
+
var PLAN_FILE_VERSION = 3;
|
|
30847
|
+
var PersistedPlanSchema = external_exports.object({
|
|
30848
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
30849
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
30850
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
30851
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
30852
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
30853
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
30854
|
+
join: external_exports.array(JoinEntrySchema),
|
|
30855
|
+
notes: external_exports.string(),
|
|
30856
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
30857
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
30858
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
30859
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
30860
|
+
});
|
|
30861
|
+
|
|
30862
|
+
// ../../packages/setup-wizard/src/triage/writeback.ts
|
|
30863
|
+
var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
|
|
30864
|
+
|
|
29285
30865
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
29286
|
-
import { randomUUID as
|
|
30866
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
29287
30867
|
|
|
29288
30868
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
29289
30869
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -29445,7 +31025,7 @@ var StandaloneDataGateway = class {
|
|
|
29445
31025
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
29446
31026
|
const installed = this.installedScanRules();
|
|
29447
31027
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
29448
|
-
id:
|
|
31028
|
+
id: randomUUID15(),
|
|
29449
31029
|
scope: "global",
|
|
29450
31030
|
target: { ruleId },
|
|
29451
31031
|
action,
|
|
@@ -29598,15 +31178,15 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
29598
31178
|
}
|
|
29599
31179
|
|
|
29600
31180
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
29601
|
-
import { randomUUID as
|
|
31181
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
29602
31182
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
29603
31183
|
|
|
29604
31184
|
// src/history/transcripts.ts
|
|
29605
|
-
import { readdirSync as
|
|
31185
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
|
|
29606
31186
|
import { homedir as homedir3 } from "os";
|
|
29607
|
-
import { join as
|
|
31187
|
+
import { join as join16 } from "path";
|
|
29608
31188
|
function transcriptsDir(home) {
|
|
29609
|
-
return
|
|
31189
|
+
return join16(home ?? homedir3(), ".claude", "projects");
|
|
29610
31190
|
}
|
|
29611
31191
|
function isRecord(value) {
|
|
29612
31192
|
return typeof value === "object" && value !== null;
|
|
@@ -29849,25 +31429,25 @@ var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
|
29849
31429
|
function* iterateFileContents(dir, excludeSessionId) {
|
|
29850
31430
|
let projects;
|
|
29851
31431
|
try {
|
|
29852
|
-
projects =
|
|
31432
|
+
projects = readdirSync5(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
29853
31433
|
} catch {
|
|
29854
31434
|
return;
|
|
29855
31435
|
}
|
|
29856
31436
|
for (const project of projects) {
|
|
29857
|
-
const projectDir =
|
|
31437
|
+
const projectDir = join16(dir, project);
|
|
29858
31438
|
let files;
|
|
29859
31439
|
try {
|
|
29860
|
-
files =
|
|
31440
|
+
files = readdirSync5(projectDir).filter((name) => name.endsWith(".jsonl"));
|
|
29861
31441
|
} catch {
|
|
29862
31442
|
continue;
|
|
29863
31443
|
}
|
|
29864
31444
|
for (const file2 of files) {
|
|
29865
31445
|
if (excludeSessionId !== void 0 && file2.slice(0, -".jsonl".length) === excludeSessionId)
|
|
29866
31446
|
continue;
|
|
29867
|
-
const filePath =
|
|
31447
|
+
const filePath = join16(projectDir, file2);
|
|
29868
31448
|
let content;
|
|
29869
31449
|
try {
|
|
29870
|
-
content =
|
|
31450
|
+
content = readFileSync10(filePath, "utf8");
|
|
29871
31451
|
} catch {
|
|
29872
31452
|
continue;
|
|
29873
31453
|
}
|
|
@@ -29999,10 +31579,10 @@ async function scanHistory(config2, opts = {}, onHit) {
|
|
|
29999
31579
|
}
|
|
30000
31580
|
|
|
30001
31581
|
// src/history/tail-scrub.ts
|
|
30002
|
-
import { readFileSync as
|
|
31582
|
+
import { readFileSync as readFileSync12, renameSync as renameSync6, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
30003
31583
|
|
|
30004
31584
|
// src/remediation/redact.ts
|
|
30005
|
-
import { readFileSync as
|
|
31585
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
30006
31586
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve } from "path";
|
|
30007
31587
|
function platformRedactionScope(home) {
|
|
30008
31588
|
return { artifactRoots: [transcriptsDir(home)] };
|
|
@@ -30032,9 +31612,9 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
30032
31612
|
try {
|
|
30033
31613
|
const realPath = resolveRedactableArtifact(filePath, deps.scope);
|
|
30034
31614
|
if (realPath === null) return null;
|
|
30035
|
-
const statBefore =
|
|
31615
|
+
const statBefore = statSync7(realPath);
|
|
30036
31616
|
if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
|
|
30037
|
-
const content =
|
|
31617
|
+
const content = readFileSync12(realPath, "utf8");
|
|
30038
31618
|
const lines = content.split("\n");
|
|
30039
31619
|
let rewritten = 0;
|
|
30040
31620
|
for (const [i, line] of lines.entries()) {
|
|
@@ -30048,16 +31628,16 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
30048
31628
|
if (rewritten === 0) return { rewritten: 0 };
|
|
30049
31629
|
const tmpPath = `${realPath}.aka-scrub.tmp`;
|
|
30050
31630
|
try {
|
|
30051
|
-
|
|
30052
|
-
const statNow =
|
|
31631
|
+
writeFileSync10(tmpPath, lines.join("\n"), { mode: statBefore.mode & 511 });
|
|
31632
|
+
const statNow = statSync7(realPath);
|
|
30053
31633
|
if (statNow.size !== statBefore.size || statNow.mtimeMs !== statBefore.mtimeMs) {
|
|
30054
|
-
|
|
31634
|
+
rmSync7(tmpPath, { force: true, recursive: true });
|
|
30055
31635
|
return null;
|
|
30056
31636
|
}
|
|
30057
31637
|
renameSync6(tmpPath, realPath);
|
|
30058
31638
|
} catch {
|
|
30059
31639
|
try {
|
|
30060
|
-
|
|
31640
|
+
rmSync7(tmpPath, { force: true, recursive: true });
|
|
30061
31641
|
} catch {
|
|
30062
31642
|
}
|
|
30063
31643
|
return null;
|
|
@@ -30071,15 +31651,15 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
30071
31651
|
// src/history/tail.ts
|
|
30072
31652
|
import { createHash as createHash5 } from "crypto";
|
|
30073
31653
|
import {
|
|
30074
|
-
closeSync,
|
|
31654
|
+
closeSync as closeSync2,
|
|
30075
31655
|
fstatSync,
|
|
30076
31656
|
mkdirSync as mkdirSync5,
|
|
30077
|
-
openSync,
|
|
30078
|
-
readFileSync as
|
|
31657
|
+
openSync as openSync2,
|
|
31658
|
+
readFileSync as readFileSync13,
|
|
30079
31659
|
readSync,
|
|
30080
|
-
writeFileSync as
|
|
31660
|
+
writeFileSync as writeFileSync11
|
|
30081
31661
|
} from "fs";
|
|
30082
|
-
import { join as
|
|
31662
|
+
import { join as join17 } from "path";
|
|
30083
31663
|
|
|
30084
31664
|
// src/history/usage.ts
|
|
30085
31665
|
var NO_PROJECT_CWD = "/nonexistent/aka-reconciler/no-project";
|
|
@@ -30328,7 +31908,6 @@ function fenced(body) {
|
|
|
30328
31908
|
}
|
|
30329
31909
|
|
|
30330
31910
|
// src/backfill.ts
|
|
30331
|
-
var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
|
|
30332
31911
|
function triageSentinel(count, status) {
|
|
30333
31912
|
return JSON.stringify({ done: true, count, status }) + "\n";
|
|
30334
31913
|
}
|
|
@@ -30437,7 +32016,7 @@ function buildTranscriptScrubber() {
|
|
|
30437
32016
|
scope
|
|
30438
32017
|
});
|
|
30439
32018
|
}
|
|
30440
|
-
if (process.argv[1] &&
|
|
32019
|
+
if (process.argv[1] && fileURLToPath2(import.meta.url) === process.argv[1]) {
|
|
30441
32020
|
const triage = process.argv.includes("--triage");
|
|
30442
32021
|
const startedAt = Date.now();
|
|
30443
32022
|
const sessionId = process.env.CLAUDE_CODE_BRIDGE_SESSION_ID;
|