@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/remediate.js
CHANGED
|
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/remediation/entry.ts
|
|
495
|
-
import { readFileSync as
|
|
496
|
-
import { fileURLToPath as
|
|
495
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
496
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/database.ts
|
|
499
|
-
import { randomUUID as
|
|
500
|
-
import {
|
|
501
|
-
import { join, sep } from "path";
|
|
499
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
500
|
+
import { join as join2, sep } from "path";
|
|
502
501
|
import { DatabaseSync } from "node:sqlite";
|
|
503
502
|
|
|
504
503
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -578,6 +577,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
578
577
|
{
|
|
579
578
|
tag: "0018_serious_tana_nile",
|
|
580
579
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0019_audit_started_at_index",
|
|
583
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
587
|
+
sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
|
|
581
588
|
}
|
|
582
589
|
];
|
|
583
590
|
|
|
@@ -15315,7 +15322,17 @@ var Finding = external_exports.object({
|
|
|
15315
15322
|
}).meta({ id: "Finding" });
|
|
15316
15323
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15317
15324
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15318
|
-
var FindingProvider = external_exports.enum([
|
|
15325
|
+
var FindingProvider = external_exports.enum([
|
|
15326
|
+
"claudecode",
|
|
15327
|
+
"claudedesktop",
|
|
15328
|
+
"cursor",
|
|
15329
|
+
"copilot",
|
|
15330
|
+
"chatgpt",
|
|
15331
|
+
"claudeai",
|
|
15332
|
+
"codex",
|
|
15333
|
+
"antigravity",
|
|
15334
|
+
"api"
|
|
15335
|
+
]).meta({ id: "FindingProvider" });
|
|
15319
15336
|
var FindingCategory = external_exports.enum([
|
|
15320
15337
|
"secret",
|
|
15321
15338
|
"pii",
|
|
@@ -15369,7 +15386,16 @@ var FindingInstance = external_exports.object({
|
|
|
15369
15386
|
confidence: external_exports.number().min(0).max(1),
|
|
15370
15387
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15371
15388
|
// that predate the resolution feature stay valid.
|
|
15372
|
-
status: FindingStatus.optional()
|
|
15389
|
+
status: FindingStatus.optional(),
|
|
15390
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15391
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15392
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15393
|
+
// MOST RECENT detection event, not the first.
|
|
15394
|
+
eventId: external_exports.string().optional(),
|
|
15395
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15396
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15397
|
+
// outside a session.
|
|
15398
|
+
sessionId: external_exports.string().optional()
|
|
15373
15399
|
}).meta({ id: "FindingInstance" });
|
|
15374
15400
|
var FindingGroup = external_exports.object({
|
|
15375
15401
|
id: external_exports.string(),
|
|
@@ -15413,7 +15439,11 @@ var FindingFacets = external_exports.object({
|
|
|
15413
15439
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15414
15440
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15415
15441
|
// counted under no value.
|
|
15416
|
-
status: external_exports.array(FindingFacetItem)
|
|
15442
|
+
status: external_exports.array(FindingFacetItem),
|
|
15443
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15444
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15445
|
+
// because a group spans tools.
|
|
15446
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15417
15447
|
}).meta({ id: "FindingFacets" });
|
|
15418
15448
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15419
15449
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15431,6 +15461,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15431
15461
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15432
15462
|
// session → findings drilldown). Findings without a session never match.
|
|
15433
15463
|
sessionId: external_exports.string().optional(),
|
|
15464
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15465
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15466
|
+
// means all time — this list has no default window.
|
|
15467
|
+
from: external_exports.iso.datetime().optional(),
|
|
15468
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15469
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15470
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15471
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15472
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15473
|
+
includeId: external_exports.string().optional(),
|
|
15434
15474
|
groupBy: external_exports.literal("type").optional(),
|
|
15435
15475
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15436
15476
|
cursor: external_exports.string().optional()
|
|
@@ -15475,15 +15515,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15475
15515
|
detection: FindingDetectionRef,
|
|
15476
15516
|
policy: FindingPolicyRef
|
|
15477
15517
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15518
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15519
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15520
|
+
severity: external_exports.array(Severity).optional(),
|
|
15521
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15522
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15523
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15524
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15525
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15526
|
+
// the grouped query's group-level fold.
|
|
15527
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15528
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15529
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15530
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15531
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15532
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15533
|
+
repo: external_exports.string().optional(),
|
|
15534
|
+
file: external_exports.string().optional(),
|
|
15535
|
+
q: external_exports.string().optional(),
|
|
15536
|
+
sessionId: external_exports.string().optional(),
|
|
15537
|
+
from: external_exports.iso.datetime().optional(),
|
|
15538
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15539
|
+
cursor: external_exports.string().optional()
|
|
15540
|
+
});
|
|
15541
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15542
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15543
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15544
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15545
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15546
|
+
// dimension still excludes its own filter.
|
|
15547
|
+
facets: FindingFacets,
|
|
15548
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15549
|
+
nextCursor: external_exports.string().nullable()
|
|
15550
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15551
|
+
var FindingLocationFile = external_exports.object({
|
|
15552
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15553
|
+
// with no file attribution).
|
|
15554
|
+
file: external_exports.string(),
|
|
15555
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15556
|
+
maxSeverity: Severity,
|
|
15557
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15558
|
+
// Folded from the instances' derived statuses with the same
|
|
15559
|
+
// open-dominates precedence a group uses.
|
|
15560
|
+
status: FindingStatus.optional(),
|
|
15561
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15562
|
+
// chips, and the count is what conveys scale.
|
|
15563
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15564
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15565
|
+
var FindingLocationRepo = external_exports.object({
|
|
15566
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15567
|
+
repo: external_exports.string(),
|
|
15568
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15569
|
+
maxSeverity: Severity,
|
|
15570
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15571
|
+
status: FindingStatus.optional(),
|
|
15572
|
+
files: external_exports.array(FindingLocationFile)
|
|
15573
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15574
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15575
|
+
severity: external_exports.array(Severity).optional(),
|
|
15576
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15577
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15578
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15579
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15580
|
+
// instances that match, and folds its status from those.
|
|
15581
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15582
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15583
|
+
q: external_exports.string().optional(),
|
|
15584
|
+
sessionId: external_exports.string().optional(),
|
|
15585
|
+
from: external_exports.iso.datetime().optional(),
|
|
15586
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15587
|
+
});
|
|
15588
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15589
|
+
totals: external_exports.object({
|
|
15590
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15591
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15592
|
+
files: external_exports.number().int().nonnegative()
|
|
15593
|
+
}),
|
|
15594
|
+
/** Sorted by max severity, then most recent. */
|
|
15595
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15596
|
+
/** Whether `limit` truncated the repo list. */
|
|
15597
|
+
hasMore: external_exports.boolean()
|
|
15598
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15478
15599
|
|
|
15479
15600
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15480
|
-
var Harness = external_exports.enum([
|
|
15601
|
+
var Harness = external_exports.enum([
|
|
15602
|
+
"claudecode",
|
|
15603
|
+
"cursor",
|
|
15604
|
+
"copilot",
|
|
15605
|
+
"codex",
|
|
15606
|
+
"antigravity",
|
|
15607
|
+
"windsurf",
|
|
15608
|
+
"claudedesktop",
|
|
15609
|
+
"chatgpt",
|
|
15610
|
+
"claudeai",
|
|
15611
|
+
"api"
|
|
15612
|
+
]).meta({ id: "Harness" });
|
|
15481
15613
|
var TOOL_TO_HARNESS = {
|
|
15482
15614
|
"claude-code": "claudecode",
|
|
15483
15615
|
"claude-desktop": "claudedesktop",
|
|
15484
15616
|
"github-copilot": "copilot",
|
|
15485
15617
|
cursor: "cursor",
|
|
15486
|
-
chatgpt: "chatgpt"
|
|
15618
|
+
chatgpt: "chatgpt",
|
|
15619
|
+
codex: "codex",
|
|
15620
|
+
antigravity: "antigravity",
|
|
15621
|
+
"claude-ai": "claudeai"
|
|
15487
15622
|
};
|
|
15488
15623
|
|
|
15489
15624
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15941,7 +16076,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15941
16076
|
// ../../packages/schema/src/zod/event.ts
|
|
15942
16077
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15943
16078
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15944
|
-
var SourceTool = external_exports.enum([
|
|
16079
|
+
var SourceTool = external_exports.enum([
|
|
16080
|
+
"claude-code",
|
|
16081
|
+
"claude-desktop",
|
|
16082
|
+
"cursor",
|
|
16083
|
+
"chatgpt",
|
|
16084
|
+
"claude-ai",
|
|
16085
|
+
"github-copilot",
|
|
16086
|
+
"codex",
|
|
16087
|
+
"antigravity",
|
|
16088
|
+
"cli",
|
|
16089
|
+
"unknown"
|
|
16090
|
+
]).meta({ id: "SourceTool" });
|
|
15945
16091
|
var EventMetadata = external_exports.object({
|
|
15946
16092
|
sessionId: external_exports.string().optional(),
|
|
15947
16093
|
repo: external_exports.string().optional(),
|
|
@@ -16012,7 +16158,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16012
16158
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16013
16159
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16014
16160
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16015
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16161
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16016
16162
|
var AccessCounts = external_exports.object({
|
|
16017
16163
|
open: external_exports.number().int().nonnegative(),
|
|
16018
16164
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16281,6 +16427,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16281
16427
|
useCount: true,
|
|
16282
16428
|
conditions: true
|
|
16283
16429
|
});
|
|
16430
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16284
16431
|
|
|
16285
16432
|
// ../../packages/schema/src/zod/rule.ts
|
|
16286
16433
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17091,6 +17238,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17091
17238
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17092
17239
|
}).meta({ id: "EgressWriteSummary" });
|
|
17093
17240
|
|
|
17241
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17242
|
+
var confirmation = external_exports.string().optional();
|
|
17243
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17244
|
+
reference: external_exports.string(),
|
|
17245
|
+
scope: external_exports.string(),
|
|
17246
|
+
reason: external_exports.string(),
|
|
17247
|
+
confirmation
|
|
17248
|
+
});
|
|
17249
|
+
var AddExceptionInput = external_exports.object({
|
|
17250
|
+
ruleId: external_exports.string(),
|
|
17251
|
+
value: external_exports.string(),
|
|
17252
|
+
scope: external_exports.string(),
|
|
17253
|
+
reason: external_exports.string(),
|
|
17254
|
+
confirmation
|
|
17255
|
+
});
|
|
17256
|
+
var GrantRevealInput = external_exports.object({
|
|
17257
|
+
pointer: external_exports.string(),
|
|
17258
|
+
scope: external_exports.string(),
|
|
17259
|
+
justification: external_exports.string(),
|
|
17260
|
+
confirmation
|
|
17261
|
+
});
|
|
17262
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17263
|
+
id: external_exports.string(),
|
|
17264
|
+
reason: external_exports.string()
|
|
17265
|
+
});
|
|
17266
|
+
var RotateKeyInput = external_exports.object({
|
|
17267
|
+
confirmation: external_exports.string()
|
|
17268
|
+
});
|
|
17269
|
+
|
|
17094
17270
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17095
17271
|
function toApiAction(dbVal) {
|
|
17096
17272
|
const map2 = {
|
|
@@ -17146,6 +17322,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17146
17322
|
repo: r.repo,
|
|
17147
17323
|
file: r.file,
|
|
17148
17324
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17325
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17326
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17149
17327
|
action: toApiAction(effectiveDbAction),
|
|
17150
17328
|
detectedAt: r.occurredAt,
|
|
17151
17329
|
confidence: r.confidence,
|
|
@@ -17277,14 +17455,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17277
17455
|
}
|
|
17278
17456
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17279
17457
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17458
|
+
function compareFindingGroupOrder(a, b) {
|
|
17459
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17460
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17461
|
+
const severityDiff = rankA - rankB;
|
|
17462
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17463
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17464
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17465
|
+
return a.id.localeCompare(b.id);
|
|
17466
|
+
}
|
|
17280
17467
|
function sortFindingGroups(groups) {
|
|
17281
|
-
return [...groups].sort(
|
|
17282
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17283
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17284
|
-
const severityDiff = rankA - rankB;
|
|
17285
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17286
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17287
|
-
});
|
|
17468
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17288
17469
|
}
|
|
17289
17470
|
function computeFindingFacets(allGroups, opts) {
|
|
17290
17471
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17340,15 +17521,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17340
17521
|
for (const g of forStatus) {
|
|
17341
17522
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17342
17523
|
}
|
|
17343
|
-
const
|
|
17524
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17525
|
+
return {
|
|
17526
|
+
severity: toItems2(severityMap),
|
|
17527
|
+
provider: toItems2(providerMap),
|
|
17528
|
+
action: toItems2(actionMap),
|
|
17529
|
+
subtype: toItems2(subtypeMap),
|
|
17530
|
+
status: toItems2(statusMap)
|
|
17531
|
+
};
|
|
17532
|
+
}
|
|
17533
|
+
|
|
17534
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17535
|
+
function rowHaystack(row) {
|
|
17536
|
+
return [
|
|
17537
|
+
row.ruleId,
|
|
17538
|
+
row.category,
|
|
17539
|
+
row.maskedMatch,
|
|
17540
|
+
row.repo,
|
|
17541
|
+
row.file,
|
|
17542
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17543
|
+
row.id
|
|
17544
|
+
].join(" ").toLowerCase();
|
|
17545
|
+
}
|
|
17546
|
+
function matchesDimension(row, opts, dimension) {
|
|
17547
|
+
switch (dimension) {
|
|
17548
|
+
case "severity":
|
|
17549
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17550
|
+
case "subtype":
|
|
17551
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17552
|
+
case "providers":
|
|
17553
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17554
|
+
case "actions":
|
|
17555
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17556
|
+
case "statuses":
|
|
17557
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17558
|
+
case "tools":
|
|
17559
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17560
|
+
case "repo":
|
|
17561
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17562
|
+
case "file":
|
|
17563
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17564
|
+
case "q":
|
|
17565
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17566
|
+
}
|
|
17567
|
+
}
|
|
17568
|
+
var DIMENSIONS = [
|
|
17569
|
+
"severity",
|
|
17570
|
+
"subtype",
|
|
17571
|
+
"providers",
|
|
17572
|
+
"actions",
|
|
17573
|
+
"statuses",
|
|
17574
|
+
"tools",
|
|
17575
|
+
"repo",
|
|
17576
|
+
"file",
|
|
17577
|
+
"q"
|
|
17578
|
+
];
|
|
17579
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17580
|
+
for (const dimension of DIMENSIONS) {
|
|
17581
|
+
if (dimension === except) continue;
|
|
17582
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17583
|
+
}
|
|
17584
|
+
return true;
|
|
17585
|
+
}
|
|
17586
|
+
function toItems(counts) {
|
|
17587
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17588
|
+
}
|
|
17589
|
+
function bump(counts, value) {
|
|
17590
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17591
|
+
}
|
|
17592
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17593
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17594
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17595
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17596
|
+
const action = /* @__PURE__ */ new Map();
|
|
17597
|
+
const status = /* @__PURE__ */ new Map();
|
|
17598
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17344
17599
|
return {
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17600
|
+
add(row) {
|
|
17601
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17603
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17604
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17605
|
+
}
|
|
17606
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17607
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17608
|
+
bump(status, row.status);
|
|
17609
|
+
}
|
|
17610
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17611
|
+
bump(tool, row.toolName);
|
|
17612
|
+
}
|
|
17613
|
+
},
|
|
17614
|
+
facets: () => ({
|
|
17615
|
+
severity: toItems(severity),
|
|
17616
|
+
subtype: toItems(subtype),
|
|
17617
|
+
provider: toItems(provider),
|
|
17618
|
+
action: toItems(action),
|
|
17619
|
+
status: toItems(status),
|
|
17620
|
+
tool: toItems(tool)
|
|
17621
|
+
})
|
|
17622
|
+
};
|
|
17623
|
+
}
|
|
17624
|
+
function toInstanceDetail(row) {
|
|
17625
|
+
const category = toApiCategory(row.category);
|
|
17626
|
+
return {
|
|
17627
|
+
id: row.id,
|
|
17628
|
+
provider: toApiProvider(row.sourceTool),
|
|
17629
|
+
repo: row.repo,
|
|
17630
|
+
file: row.file,
|
|
17631
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17632
|
+
eventId: row.eventId,
|
|
17633
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17634
|
+
action: toApiAction(row.actionTaken),
|
|
17635
|
+
detectedAt: row.occurredAt,
|
|
17636
|
+
confidence: row.confidence,
|
|
17637
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17638
|
+
groupId: row.ruleId,
|
|
17639
|
+
category,
|
|
17640
|
+
subtype: row.ruleId,
|
|
17641
|
+
severity: row.severity,
|
|
17642
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17643
|
+
detection: { id: row.ruleId, name: null },
|
|
17644
|
+
policy: { id: `category:${category}`, name: category }
|
|
17645
|
+
};
|
|
17646
|
+
}
|
|
17647
|
+
var SEVERITY_ORDER2 = {
|
|
17648
|
+
critical: 0,
|
|
17649
|
+
high: 1,
|
|
17650
|
+
medium: 2,
|
|
17651
|
+
low: 3
|
|
17652
|
+
};
|
|
17653
|
+
function newLocationAccumulator() {
|
|
17654
|
+
return {
|
|
17655
|
+
instanceCount: 0,
|
|
17656
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17657
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17658
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17659
|
+
maxSeverity: "low",
|
|
17660
|
+
latestDetectedAt: "",
|
|
17661
|
+
statuses: [],
|
|
17662
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17350
17663
|
};
|
|
17351
17664
|
}
|
|
17665
|
+
function addToLocation(acc, row) {
|
|
17666
|
+
acc.instanceCount += 1;
|
|
17667
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17668
|
+
if (rank < acc.maxSeverityRank) {
|
|
17669
|
+
acc.maxSeverityRank = rank;
|
|
17670
|
+
acc.maxSeverity = row.severity;
|
|
17671
|
+
}
|
|
17672
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17673
|
+
acc.statuses.push(row.status);
|
|
17674
|
+
acc.ruleIds.add(row.ruleId);
|
|
17675
|
+
}
|
|
17352
17676
|
|
|
17353
17677
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17354
17678
|
var InstalledPack = external_exports.object({
|
|
@@ -17489,6 +17813,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17489
17813
|
revealGrantId: external_exports.string().nullable(),
|
|
17490
17814
|
sightings: external_exports.array(VaultSighting)
|
|
17491
17815
|
});
|
|
17816
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17817
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17818
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17819
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17820
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17821
|
+
// Opaque; names the last row of the page just served.
|
|
17822
|
+
cursor: external_exports.string().optional()
|
|
17823
|
+
});
|
|
17824
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17825
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17826
|
+
// independent, so paging never changes what the count claims.
|
|
17827
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17828
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17829
|
+
// `null` once the last page is reached.
|
|
17830
|
+
nextCursor: external_exports.string().nullable()
|
|
17831
|
+
});
|
|
17832
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17833
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17834
|
+
cursor: external_exports.string().optional()
|
|
17835
|
+
});
|
|
17836
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17837
|
+
// Reused values across the whole store — the number the section's claim
|
|
17838
|
+
// ("values detected in more than one place") is about.
|
|
17839
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17840
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17841
|
+
nextCursor: external_exports.string().nullable()
|
|
17842
|
+
});
|
|
17843
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17844
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17845
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17846
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17847
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17848
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17849
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17850
|
+
cursor: external_exports.string().optional()
|
|
17851
|
+
});
|
|
17852
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17853
|
+
items: external_exports.array(VaultDeref),
|
|
17854
|
+
nextCursor: external_exports.string().nullable(),
|
|
17855
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17856
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17857
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17858
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17859
|
+
});
|
|
17492
17860
|
var VaultKeyCustody = external_exports.string();
|
|
17493
17861
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17494
17862
|
var VAULT_CONSENT_VERSION = 1;
|
|
@@ -17865,7 +18233,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17865
18233
|
// Omit for both kinds.
|
|
17866
18234
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17867
18235
|
});
|
|
17868
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18236
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17869
18237
|
var ScanCoverageProvider = external_exports.object({
|
|
17870
18238
|
provider: Provider,
|
|
17871
18239
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18118,6 +18486,195 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18118
18486
|
);
|
|
18119
18487
|
}
|
|
18120
18488
|
|
|
18489
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18490
|
+
import { randomUUID } from "crypto";
|
|
18491
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18492
|
+
import { basename, dirname, join } from "path";
|
|
18493
|
+
|
|
18494
|
+
// ../../packages/persistence/src/paths.ts
|
|
18495
|
+
import {
|
|
18496
|
+
chmodSync,
|
|
18497
|
+
linkSync,
|
|
18498
|
+
lstatSync,
|
|
18499
|
+
mkdirSync,
|
|
18500
|
+
renameSync,
|
|
18501
|
+
rmSync,
|
|
18502
|
+
writeFileSync
|
|
18503
|
+
} from "fs";
|
|
18504
|
+
import { threadId } from "worker_threads";
|
|
18505
|
+
var DATA_DIR_MODE = 448;
|
|
18506
|
+
var DATA_FILE_MODE = 384;
|
|
18507
|
+
var DB_FILENAME = "aka.db";
|
|
18508
|
+
function isSymlink(path) {
|
|
18509
|
+
try {
|
|
18510
|
+
return lstatSync(path).isSymbolicLink();
|
|
18511
|
+
} catch {
|
|
18512
|
+
return false;
|
|
18513
|
+
}
|
|
18514
|
+
}
|
|
18515
|
+
function chmodBestEffort(path, mode) {
|
|
18516
|
+
if (isSymlink(path)) return;
|
|
18517
|
+
try {
|
|
18518
|
+
chmodSync(path, mode);
|
|
18519
|
+
} catch {
|
|
18520
|
+
}
|
|
18521
|
+
}
|
|
18522
|
+
function tightenDir(dir) {
|
|
18523
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18524
|
+
}
|
|
18525
|
+
function ensureDataDirSync(dir) {
|
|
18526
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18527
|
+
tightenDir(dir);
|
|
18528
|
+
}
|
|
18529
|
+
function dbSidecars(file2) {
|
|
18530
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18531
|
+
}
|
|
18532
|
+
function tightenFile(file2) {
|
|
18533
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18534
|
+
}
|
|
18535
|
+
function tightenPerms(file2) {
|
|
18536
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18537
|
+
}
|
|
18538
|
+
function classifyOccupant(file2) {
|
|
18539
|
+
try {
|
|
18540
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
18541
|
+
return { kind: "gone" };
|
|
18542
|
+
} catch (err) {
|
|
18543
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
18544
|
+
return { kind: "unknown", cause: err };
|
|
18545
|
+
}
|
|
18546
|
+
}
|
|
18547
|
+
var KeyUnclaimableError = class extends Error {
|
|
18548
|
+
code = "key-unclaimable";
|
|
18549
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
18550
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
18551
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
18552
|
+
// captured and then lost, which is worse than its plain absence.
|
|
18553
|
+
constructor(message, cause) {
|
|
18554
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
18555
|
+
this.name = "KeyUnclaimableError";
|
|
18556
|
+
}
|
|
18557
|
+
};
|
|
18558
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
18559
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
18560
|
+
try {
|
|
18561
|
+
rmSync(tmp, { force: true });
|
|
18562
|
+
} catch {
|
|
18563
|
+
}
|
|
18564
|
+
let created;
|
|
18565
|
+
try {
|
|
18566
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18567
|
+
created = publishByLink(tmp, file2, data);
|
|
18568
|
+
} finally {
|
|
18569
|
+
try {
|
|
18570
|
+
rmSync(tmp, { force: true });
|
|
18571
|
+
} catch {
|
|
18572
|
+
}
|
|
18573
|
+
}
|
|
18574
|
+
if (created) tightenFile(file2);
|
|
18575
|
+
return created;
|
|
18576
|
+
}
|
|
18577
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
18578
|
+
function publishByLink(tmp, file2, data) {
|
|
18579
|
+
try {
|
|
18580
|
+
linkSync(tmp, file2);
|
|
18581
|
+
return true;
|
|
18582
|
+
} catch (err) {
|
|
18583
|
+
const code = err.code;
|
|
18584
|
+
if (code === "EEXIST") return false;
|
|
18585
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18586
|
+
}
|
|
18587
|
+
try {
|
|
18588
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18589
|
+
return true;
|
|
18590
|
+
} catch (err) {
|
|
18591
|
+
if (err.code === "EEXIST") return false;
|
|
18592
|
+
throw err;
|
|
18593
|
+
}
|
|
18594
|
+
}
|
|
18595
|
+
|
|
18596
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18597
|
+
function backupPath(file2, tag) {
|
|
18598
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18599
|
+
}
|
|
18600
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18601
|
+
function reapStalePartials(file2) {
|
|
18602
|
+
const dir = dirname(file2);
|
|
18603
|
+
const prefix = `${basename(file2)}.`;
|
|
18604
|
+
let entries;
|
|
18605
|
+
try {
|
|
18606
|
+
entries = readdirSync(dir);
|
|
18607
|
+
} catch {
|
|
18608
|
+
return;
|
|
18609
|
+
}
|
|
18610
|
+
for (const name of entries) {
|
|
18611
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18612
|
+
const partial2 = join(dir, name);
|
|
18613
|
+
try {
|
|
18614
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18615
|
+
rmSync2(partial2, { force: true });
|
|
18616
|
+
}
|
|
18617
|
+
} catch {
|
|
18618
|
+
}
|
|
18619
|
+
}
|
|
18620
|
+
}
|
|
18621
|
+
function snapshotStore(db, backup) {
|
|
18622
|
+
const partial2 = `${backup}.partial`;
|
|
18623
|
+
try {
|
|
18624
|
+
rmSync2(partial2, { force: true });
|
|
18625
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18626
|
+
tightenFile(partial2);
|
|
18627
|
+
renameSync2(partial2, backup);
|
|
18628
|
+
} catch (error51) {
|
|
18629
|
+
try {
|
|
18630
|
+
rmSync2(partial2, { force: true });
|
|
18631
|
+
} catch {
|
|
18632
|
+
}
|
|
18633
|
+
throw error51;
|
|
18634
|
+
}
|
|
18635
|
+
}
|
|
18636
|
+
function moveStoreAside(file2, backup) {
|
|
18637
|
+
const undo = [];
|
|
18638
|
+
renameSync2(file2, backup);
|
|
18639
|
+
undo.push([backup, file2]);
|
|
18640
|
+
try {
|
|
18641
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18642
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18643
|
+
try {
|
|
18644
|
+
renameSync2(sidecar, moved);
|
|
18645
|
+
undo.push([moved, sidecar]);
|
|
18646
|
+
} catch {
|
|
18647
|
+
rmSync2(sidecar, { force: true });
|
|
18648
|
+
}
|
|
18649
|
+
}
|
|
18650
|
+
} catch (error51) {
|
|
18651
|
+
for (const [from, to] of undo.reverse()) {
|
|
18652
|
+
try {
|
|
18653
|
+
renameSync2(from, to);
|
|
18654
|
+
} catch {
|
|
18655
|
+
}
|
|
18656
|
+
}
|
|
18657
|
+
throw error51;
|
|
18658
|
+
}
|
|
18659
|
+
tightenPerms(backup);
|
|
18660
|
+
}
|
|
18661
|
+
function discardStore(file2, backup) {
|
|
18662
|
+
try {
|
|
18663
|
+
rmSync2(file2, { force: true });
|
|
18664
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18665
|
+
rmSync2(sidecar, { force: true });
|
|
18666
|
+
}
|
|
18667
|
+
} catch (error51) {
|
|
18668
|
+
if (existsSync(file2)) {
|
|
18669
|
+
try {
|
|
18670
|
+
rmSync2(backup, { force: true });
|
|
18671
|
+
} catch {
|
|
18672
|
+
}
|
|
18673
|
+
}
|
|
18674
|
+
throw error51;
|
|
18675
|
+
}
|
|
18676
|
+
}
|
|
18677
|
+
|
|
18121
18678
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18122
18679
|
function escapeLikePattern(s) {
|
|
18123
18680
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18259,55 +18816,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18259
18816
|
return out;
|
|
18260
18817
|
}
|
|
18261
18818
|
|
|
18262
|
-
// ../../packages/persistence/src/paths.ts
|
|
18263
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18264
|
-
var DATA_DIR_MODE = 448;
|
|
18265
|
-
var DATA_FILE_MODE = 384;
|
|
18266
|
-
var DB_FILENAME = "aka.db";
|
|
18267
|
-
function chmodBestEffort(path, mode) {
|
|
18268
|
-
try {
|
|
18269
|
-
chmodSync(path, mode);
|
|
18270
|
-
} catch {
|
|
18271
|
-
}
|
|
18272
|
-
}
|
|
18273
|
-
function tightenDir(dir) {
|
|
18274
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18275
|
-
}
|
|
18276
|
-
function ensureDataDirSync(dir) {
|
|
18277
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18278
|
-
tightenDir(dir);
|
|
18279
|
-
}
|
|
18280
|
-
function dbSidecars(file2) {
|
|
18281
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18282
|
-
}
|
|
18283
|
-
function tightenFile(file2) {
|
|
18284
|
-
try {
|
|
18285
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18286
|
-
} catch {
|
|
18287
|
-
}
|
|
18288
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18289
|
-
}
|
|
18290
|
-
function tightenPerms(file2) {
|
|
18291
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18292
|
-
}
|
|
18293
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18294
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18295
|
-
try {
|
|
18296
|
-
rmSync(tmp, { force: true });
|
|
18297
|
-
} catch {
|
|
18298
|
-
}
|
|
18299
|
-
try {
|
|
18300
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18301
|
-
renameSync(tmp, file2);
|
|
18302
|
-
} finally {
|
|
18303
|
-
try {
|
|
18304
|
-
rmSync(tmp, { force: true });
|
|
18305
|
-
} catch {
|
|
18306
|
-
}
|
|
18307
|
-
}
|
|
18308
|
-
tightenFile(file2);
|
|
18309
|
-
}
|
|
18310
|
-
|
|
18311
18819
|
// ../../packages/persistence/src/migrations.ts
|
|
18312
18820
|
function describeObject(object2) {
|
|
18313
18821
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18423,9 +18931,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18423
18931
|
}
|
|
18424
18932
|
}
|
|
18425
18933
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18426
|
-
|
|
18427
|
-
|
|
18428
|
-
|
|
18934
|
+
reapStalePartials(file2);
|
|
18935
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18936
|
+
snapshotStore(db, backup);
|
|
18429
18937
|
return backup;
|
|
18430
18938
|
}
|
|
18431
18939
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18769,6 +19277,25 @@ function parseJsonObject(s) {
|
|
|
18769
19277
|
return void 0;
|
|
18770
19278
|
}
|
|
18771
19279
|
|
|
19280
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19281
|
+
function encodeKeysetCursor(payload) {
|
|
19282
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19283
|
+
}
|
|
19284
|
+
function decodeKeysetCursor(cursor) {
|
|
19285
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19286
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19287
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19288
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19289
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19290
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19291
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19292
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19293
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19294
|
+
return parsed;
|
|
19295
|
+
}
|
|
19296
|
+
return null;
|
|
19297
|
+
}
|
|
19298
|
+
|
|
18772
19299
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18773
19300
|
var DAY_MS = 864e5;
|
|
18774
19301
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18814,16 +19341,6 @@ function utcWindow(nowMs) {
|
|
|
18814
19341
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18815
19342
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18816
19343
|
}
|
|
18817
|
-
function encodeCursor(payload) {
|
|
18818
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18819
|
-
}
|
|
18820
|
-
function decodeCursor(cursor) {
|
|
18821
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18822
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18823
|
-
return parsed;
|
|
18824
|
-
}
|
|
18825
|
-
return null;
|
|
18826
|
-
}
|
|
18827
19344
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18828
19345
|
session: "session",
|
|
18829
19346
|
prompt: "prompt",
|
|
@@ -18968,7 +19485,7 @@ var SqliteActivityRepository = class {
|
|
|
18968
19485
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18969
19486
|
}
|
|
18970
19487
|
listSessions(query) {
|
|
18971
|
-
const cursor = query.cursor ?
|
|
19488
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18972
19489
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18973
19490
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18974
19491
|
const conditions = [SESSION_ROOT];
|
|
@@ -19042,7 +19559,7 @@ var SqliteActivityRepository = class {
|
|
|
19042
19559
|
)
|
|
19043
19560
|
);
|
|
19044
19561
|
const last = page[page.length - 1];
|
|
19045
|
-
const nextCursor = hasMore && last ?
|
|
19562
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19046
19563
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19047
19564
|
}
|
|
19048
19565
|
getSession(sessionId) {
|
|
@@ -19915,7 +20432,7 @@ var SqliteEventsRepository = class {
|
|
|
19915
20432
|
};
|
|
19916
20433
|
|
|
19917
20434
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19918
|
-
import { randomUUID } from "crypto";
|
|
20435
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19919
20436
|
|
|
19920
20437
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19921
20438
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19951,8 +20468,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19951
20468
|
AND conditions IS NULL
|
|
19952
20469
|
AND ${ACTIVE_PREDICATE}`;
|
|
19953
20470
|
var SqliteExceptionsRepository = class {
|
|
19954
|
-
constructor(db) {
|
|
20471
|
+
constructor(db, now = () => Date.now()) {
|
|
19955
20472
|
this.db = db;
|
|
20473
|
+
this.now = now;
|
|
19956
20474
|
this.consumeStmt = db.prepare(
|
|
19957
20475
|
`UPDATE exceptions
|
|
19958
20476
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19970,6 +20488,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19970
20488
|
);
|
|
19971
20489
|
}
|
|
19972
20490
|
db;
|
|
20491
|
+
now;
|
|
19973
20492
|
consumeStmt;
|
|
19974
20493
|
insertBlockedStmt;
|
|
19975
20494
|
sweepBlockedStmt;
|
|
@@ -19996,8 +20515,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19996
20515
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19997
20516
|
);
|
|
19998
20517
|
}
|
|
19999
|
-
const id =
|
|
20000
|
-
const now =
|
|
20518
|
+
const id = randomUUID2();
|
|
20519
|
+
const now = this.now();
|
|
20001
20520
|
try {
|
|
20002
20521
|
this.insertExceptionRow(id, input, now);
|
|
20003
20522
|
} catch (err) {
|
|
@@ -20075,7 +20594,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20075
20594
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20076
20595
|
const rows = allRows(
|
|
20077
20596
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20078
|
-
opts?.includeTerminal ? {} : { now:
|
|
20597
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20079
20598
|
);
|
|
20080
20599
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20081
20600
|
return Promise.resolve(exceptions);
|
|
@@ -20110,7 +20629,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20110
20629
|
* already revoked.
|
|
20111
20630
|
*/
|
|
20112
20631
|
revoke(id, revokedBy, reason) {
|
|
20113
|
-
const now =
|
|
20632
|
+
const now = this.now();
|
|
20114
20633
|
const result = this.db.prepare(
|
|
20115
20634
|
`UPDATE exceptions
|
|
20116
20635
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20124,7 +20643,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20124
20643
|
* callers must treat identically — means it does not and the detection is
|
|
20125
20644
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20126
20645
|
*/
|
|
20127
|
-
consume(id, now =
|
|
20646
|
+
consume(id, now = this.now()) {
|
|
20128
20647
|
const result = this.consumeStmt.run({ id, now });
|
|
20129
20648
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20130
20649
|
}
|
|
@@ -20133,7 +20652,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20133
20652
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20134
20653
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20135
20654
|
*/
|
|
20136
|
-
activeBundleEntries(keyVersion, now =
|
|
20655
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20137
20656
|
const rows = allRows(
|
|
20138
20657
|
this.db.prepare(
|
|
20139
20658
|
`SELECT * FROM exceptions
|
|
@@ -20165,7 +20684,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20165
20684
|
* than the retention window on every write, so the ledger self-limits.
|
|
20166
20685
|
*/
|
|
20167
20686
|
recordBlocked(entry) {
|
|
20168
|
-
const now =
|
|
20687
|
+
const now = this.now();
|
|
20169
20688
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20170
20689
|
this.insertBlockedStmt.run({
|
|
20171
20690
|
reference: entry.reference,
|
|
@@ -20188,7 +20707,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20188
20707
|
WHERE blocked_at > :cutoff
|
|
20189
20708
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20190
20709
|
),
|
|
20191
|
-
{ cutoff:
|
|
20710
|
+
{ cutoff: this.now() - windowMs }
|
|
20192
20711
|
);
|
|
20193
20712
|
return Promise.resolve(
|
|
20194
20713
|
rows.map((row) => ({
|
|
@@ -20216,8 +20735,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20216
20735
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20217
20736
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20218
20737
|
*/
|
|
20219
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20738
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20220
20739
|
try {
|
|
20740
|
+
const at = now ?? this.now();
|
|
20221
20741
|
const row = getRow(
|
|
20222
20742
|
this.db.prepare(
|
|
20223
20743
|
`SELECT id FROM exceptions
|
|
@@ -20226,7 +20746,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20226
20746
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20227
20747
|
LIMIT 1`
|
|
20228
20748
|
),
|
|
20229
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20749
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20230
20750
|
);
|
|
20231
20751
|
return Promise.resolve(row ?? null);
|
|
20232
20752
|
} catch (err) {
|
|
@@ -20240,7 +20760,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20240
20760
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20241
20761
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20242
20762
|
*/
|
|
20243
|
-
sweepTerminal(retentionMs, now =
|
|
20763
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20244
20764
|
const result = this.db.prepare(
|
|
20245
20765
|
`DELETE FROM exceptions
|
|
20246
20766
|
WHERE updated_at < :cutoff
|
|
@@ -20303,6 +20823,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20303
20823
|
|
|
20304
20824
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20305
20825
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20826
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20827
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20828
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20829
|
+
function compareLocationOrder(a, b) {
|
|
20830
|
+
return compareFindingGroupOrder(
|
|
20831
|
+
{
|
|
20832
|
+
severity: a.maxSeverity,
|
|
20833
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20834
|
+
id: ""
|
|
20835
|
+
},
|
|
20836
|
+
{
|
|
20837
|
+
severity: b.maxSeverity,
|
|
20838
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20839
|
+
id: ""
|
|
20840
|
+
}
|
|
20841
|
+
);
|
|
20842
|
+
}
|
|
20306
20843
|
var CONCAT_SEP = ",";
|
|
20307
20844
|
var TUPLE_SEP = "|";
|
|
20308
20845
|
function splitConcat(value) {
|
|
@@ -20315,6 +20852,33 @@ function deriveInstanceStatus(row) {
|
|
|
20315
20852
|
latestResolutionStatus: row.latest_status
|
|
20316
20853
|
});
|
|
20317
20854
|
}
|
|
20855
|
+
function encodeGroupCursor(group) {
|
|
20856
|
+
const payload = {
|
|
20857
|
+
sev: group.severity,
|
|
20858
|
+
t: group.latestDetectedAt,
|
|
20859
|
+
id: group.id
|
|
20860
|
+
};
|
|
20861
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20862
|
+
}
|
|
20863
|
+
function decodeGroupCursor(cursor) {
|
|
20864
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20865
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20866
|
+
return {
|
|
20867
|
+
severity: parsed.sev,
|
|
20868
|
+
latestDetectedAt: parsed.t,
|
|
20869
|
+
id: parsed.id
|
|
20870
|
+
};
|
|
20871
|
+
}
|
|
20872
|
+
return null;
|
|
20873
|
+
}
|
|
20874
|
+
function firstAfter(sorted, cursor) {
|
|
20875
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20876
|
+
return index === -1 ? sorted.length : index;
|
|
20877
|
+
}
|
|
20878
|
+
function findDeepLinked(sorted, page, id) {
|
|
20879
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20880
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20881
|
+
}
|
|
20318
20882
|
var DAY_MS3 = 864e5;
|
|
20319
20883
|
var SqliteFindingsRepository = class {
|
|
20320
20884
|
constructor(db) {
|
|
@@ -20424,8 +20988,13 @@ var SqliteFindingsRepository = class {
|
|
|
20424
20988
|
*/
|
|
20425
20989
|
listGroupedFindings(query) {
|
|
20426
20990
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20427
|
-
const
|
|
20428
|
-
const
|
|
20991
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20992
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20993
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20994
|
+
const sessionParams = {
|
|
20995
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20996
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20997
|
+
};
|
|
20429
20998
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20430
20999
|
predicate,
|
|
20431
21000
|
params: sessionParams
|
|
@@ -20433,7 +21002,8 @@ var SqliteFindingsRepository = class {
|
|
|
20433
21002
|
const rows = allRows(
|
|
20434
21003
|
this.db.prepare(
|
|
20435
21004
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20436
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
21005
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
21006
|
+
kind, finding_key, latest_status
|
|
20437
21007
|
FROM (
|
|
20438
21008
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20439
21009
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20443,6 +21013,7 @@ var SqliteFindingsRepository = class {
|
|
|
20443
21013
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20444
21014
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20445
21015
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21016
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20446
21017
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20447
21018
|
latest.status AS latest_status,
|
|
20448
21019
|
ROW_NUMBER() OVER (
|
|
@@ -20474,6 +21045,8 @@ var SqliteFindingsRepository = class {
|
|
|
20474
21045
|
repo: r.repo ?? "",
|
|
20475
21046
|
file: r.file ?? "",
|
|
20476
21047
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21048
|
+
eventId: r.event_id,
|
|
21049
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20477
21050
|
status: deriveInstanceStatus(r)
|
|
20478
21051
|
}));
|
|
20479
21052
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20497,18 +21070,23 @@ var SqliteFindingsRepository = class {
|
|
|
20497
21070
|
groups: sorted.length
|
|
20498
21071
|
};
|
|
20499
21072
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21073
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21074
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21075
|
+
const page = sorted.slice(start, start + limit);
|
|
21076
|
+
const lastOnPage = page.at(-1);
|
|
21077
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21078
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20500
21079
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20501
|
-
const
|
|
20502
|
-
|
|
20503
|
-
|
|
20504
|
-
|
|
20505
|
-
|
|
20506
|
-
);
|
|
21080
|
+
const narrow = (g) => statusSet ? {
|
|
21081
|
+
...g,
|
|
21082
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21083
|
+
} : g;
|
|
21084
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20507
21085
|
return Promise.resolve({
|
|
20508
21086
|
totals,
|
|
20509
21087
|
facets,
|
|
20510
21088
|
items,
|
|
20511
|
-
nextCursor
|
|
21089
|
+
nextCursor,
|
|
20512
21090
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20513
21091
|
});
|
|
20514
21092
|
}
|
|
@@ -20540,6 +21118,266 @@ var SqliteFindingsRepository = class {
|
|
|
20540
21118
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20541
21119
|
* path repeating across tuples.)
|
|
20542
21120
|
*/
|
|
21121
|
+
/**
|
|
21122
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21123
|
+
* paged by keyset.
|
|
21124
|
+
*
|
|
21125
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21126
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21127
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21128
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21129
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21130
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21131
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21132
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21133
|
+
* none of the mappers names", which no IN-list can say.
|
|
21134
|
+
*
|
|
21135
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21136
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21137
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21138
|
+
* while the counting runs, and only the page itself is retained.
|
|
21139
|
+
*/
|
|
21140
|
+
listFindingInstances(query) {
|
|
21141
|
+
const opts = {
|
|
21142
|
+
severity: query.severity,
|
|
21143
|
+
subtype: query.subtype,
|
|
21144
|
+
providers: query.provider,
|
|
21145
|
+
actions: query.action,
|
|
21146
|
+
statuses: query.status,
|
|
21147
|
+
tools: query.tool,
|
|
21148
|
+
repo: query.repo,
|
|
21149
|
+
file: query.file,
|
|
21150
|
+
q: query.q
|
|
21151
|
+
};
|
|
21152
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21153
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21154
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21155
|
+
const items = [];
|
|
21156
|
+
let total = 0;
|
|
21157
|
+
let last;
|
|
21158
|
+
let hasMore = false;
|
|
21159
|
+
for (const row of this.scanFindingRows({
|
|
21160
|
+
sessionId: query.sessionId,
|
|
21161
|
+
from: query.from
|
|
21162
|
+
})) {
|
|
21163
|
+
accumulator.add(row);
|
|
21164
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21165
|
+
total += 1;
|
|
21166
|
+
if (items.length < limit) {
|
|
21167
|
+
items.push(toInstanceDetail(row));
|
|
21168
|
+
last = row;
|
|
21169
|
+
} else {
|
|
21170
|
+
hasMore = true;
|
|
21171
|
+
}
|
|
21172
|
+
}
|
|
21173
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21174
|
+
if (cursor !== null) {
|
|
21175
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21176
|
+
return Promise.resolve({
|
|
21177
|
+
totals: { findings: total },
|
|
21178
|
+
facets: accumulator.facets(),
|
|
21179
|
+
items: resumed.items,
|
|
21180
|
+
nextCursor: resumed.nextCursor
|
|
21181
|
+
});
|
|
21182
|
+
}
|
|
21183
|
+
return Promise.resolve({
|
|
21184
|
+
totals: { findings: total },
|
|
21185
|
+
facets: accumulator.facets(),
|
|
21186
|
+
items,
|
|
21187
|
+
nextCursor
|
|
21188
|
+
});
|
|
21189
|
+
}
|
|
21190
|
+
/**
|
|
21191
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21192
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21193
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21194
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21195
|
+
*/
|
|
21196
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21197
|
+
const items = [];
|
|
21198
|
+
let last;
|
|
21199
|
+
let hasMore = false;
|
|
21200
|
+
for (const row of this.scanFindingRows({
|
|
21201
|
+
sessionId: query.sessionId,
|
|
21202
|
+
from: query.from,
|
|
21203
|
+
after: cursor
|
|
21204
|
+
})) {
|
|
21205
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21206
|
+
if (items.length < limit) {
|
|
21207
|
+
items.push(toInstanceDetail(row));
|
|
21208
|
+
last = row;
|
|
21209
|
+
} else {
|
|
21210
|
+
hasMore = true;
|
|
21211
|
+
break;
|
|
21212
|
+
}
|
|
21213
|
+
}
|
|
21214
|
+
return {
|
|
21215
|
+
items,
|
|
21216
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21217
|
+
};
|
|
21218
|
+
}
|
|
21219
|
+
/**
|
|
21220
|
+
* The same findings folded by location: repository, then file within it.
|
|
21221
|
+
*
|
|
21222
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21223
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21224
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21225
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21226
|
+
* filter can name it.
|
|
21227
|
+
*/
|
|
21228
|
+
listFindingLocations(query) {
|
|
21229
|
+
const opts = {
|
|
21230
|
+
severity: query.severity,
|
|
21231
|
+
subtype: query.subtype,
|
|
21232
|
+
providers: query.provider,
|
|
21233
|
+
actions: query.action,
|
|
21234
|
+
statuses: query.status,
|
|
21235
|
+
tools: query.tool,
|
|
21236
|
+
q: query.q
|
|
21237
|
+
};
|
|
21238
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21239
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21240
|
+
let total = 0;
|
|
21241
|
+
for (const row of this.scanFindingRows({
|
|
21242
|
+
sessionId: query.sessionId,
|
|
21243
|
+
from: query.from
|
|
21244
|
+
})) {
|
|
21245
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21246
|
+
total += 1;
|
|
21247
|
+
let files = byRepo.get(row.repo);
|
|
21248
|
+
if (files === void 0) {
|
|
21249
|
+
files = /* @__PURE__ */ new Map();
|
|
21250
|
+
byRepo.set(row.repo, files);
|
|
21251
|
+
}
|
|
21252
|
+
let acc = files.get(row.file);
|
|
21253
|
+
if (acc === void 0) {
|
|
21254
|
+
acc = newLocationAccumulator();
|
|
21255
|
+
files.set(row.file, acc);
|
|
21256
|
+
}
|
|
21257
|
+
addToLocation(acc, row);
|
|
21258
|
+
}
|
|
21259
|
+
let fileCount = 0;
|
|
21260
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21261
|
+
fileCount += files.size;
|
|
21262
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21263
|
+
file: file2,
|
|
21264
|
+
instanceCount: acc.instanceCount,
|
|
21265
|
+
maxSeverity: acc.maxSeverity,
|
|
21266
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21267
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21268
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21269
|
+
})).sort(compareLocationOrder);
|
|
21270
|
+
const rollup = fileRows.reduce(
|
|
21271
|
+
(a, f) => ({
|
|
21272
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21273
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21274
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21275
|
+
}),
|
|
21276
|
+
{
|
|
21277
|
+
instanceCount: 0,
|
|
21278
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21279
|
+
latestDetectedAt: ""
|
|
21280
|
+
}
|
|
21281
|
+
);
|
|
21282
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21283
|
+
const folded = foldGroupStatus(statuses);
|
|
21284
|
+
return {
|
|
21285
|
+
repo,
|
|
21286
|
+
instanceCount: rollup.instanceCount,
|
|
21287
|
+
maxSeverity: rollup.maxSeverity,
|
|
21288
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21289
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21290
|
+
files: fileRows
|
|
21291
|
+
};
|
|
21292
|
+
});
|
|
21293
|
+
repos.sort(compareLocationOrder);
|
|
21294
|
+
return Promise.resolve({
|
|
21295
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21296
|
+
items: repos.slice(0, limit),
|
|
21297
|
+
hasMore: repos.length > limit
|
|
21298
|
+
});
|
|
21299
|
+
}
|
|
21300
|
+
/**
|
|
21301
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21302
|
+
*
|
|
21303
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21304
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21305
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21306
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21307
|
+
* rather than one unbounded result set.
|
|
21308
|
+
*
|
|
21309
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21310
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21311
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21312
|
+
* a window over the whole resolution table once per batch.
|
|
21313
|
+
*
|
|
21314
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21315
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21316
|
+
* dimension — see listFindingInstances.
|
|
21317
|
+
*/
|
|
21318
|
+
*scanFindingRows(scope) {
|
|
21319
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21320
|
+
const params = [];
|
|
21321
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21322
|
+
conditions.push("e.root_session_id = ?");
|
|
21323
|
+
params.push(scope.sessionId);
|
|
21324
|
+
}
|
|
21325
|
+
if (scope.from !== void 0) {
|
|
21326
|
+
conditions.push("e.started_at >= ?");
|
|
21327
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21328
|
+
}
|
|
21329
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21330
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21331
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21332
|
+
e.started_at AS occurred_at,
|
|
21333
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21334
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21335
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21336
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21337
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21338
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21339
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21340
|
+
FROM inspection_findings f
|
|
21341
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21342
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21343
|
+
WHERE ${conditions.join(" AND ")}
|
|
21344
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21345
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21346
|
+
LIMIT ?`;
|
|
21347
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21348
|
+
for (; ; ) {
|
|
21349
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21350
|
+
...params,
|
|
21351
|
+
after.startedAtMs,
|
|
21352
|
+
after.startedAtMs,
|
|
21353
|
+
after.id,
|
|
21354
|
+
SCAN_BATCH_ROWS
|
|
21355
|
+
]);
|
|
21356
|
+
for (const r of rows) {
|
|
21357
|
+
yield {
|
|
21358
|
+
id: r.id,
|
|
21359
|
+
ruleId: r.rule_id,
|
|
21360
|
+
category: r.category,
|
|
21361
|
+
severity: r.severity,
|
|
21362
|
+
maskedMatch: r.masked_match,
|
|
21363
|
+
actionTaken: r.action_taken,
|
|
21364
|
+
confidence: r.confidence,
|
|
21365
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21366
|
+
sourceTool: r.source_tool,
|
|
21367
|
+
repo: r.repo ?? "",
|
|
21368
|
+
file: r.file ?? "",
|
|
21369
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21370
|
+
eventId: r.event_id,
|
|
21371
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21372
|
+
status: deriveInstanceStatus(r)
|
|
21373
|
+
};
|
|
21374
|
+
}
|
|
21375
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21376
|
+
const lastRow = rows[rows.length - 1];
|
|
21377
|
+
if (lastRow === void 0) return;
|
|
21378
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21379
|
+
}
|
|
21380
|
+
}
|
|
20543
21381
|
groupAggregates(withSearchText, scope) {
|
|
20544
21382
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20545
21383
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20800,7 +21638,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20800
21638
|
};
|
|
20801
21639
|
|
|
20802
21640
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20803
|
-
import { createHash as createHash2, randomUUID as
|
|
21641
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20804
21642
|
|
|
20805
21643
|
// ../../packages/persistence/src/semver.ts
|
|
20806
21644
|
function parse3(version2) {
|
|
@@ -20951,7 +21789,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20951
21789
|
let behind = false;
|
|
20952
21790
|
for (const row of rows) {
|
|
20953
21791
|
const params = {
|
|
20954
|
-
id:
|
|
21792
|
+
id: randomUUID3(),
|
|
20955
21793
|
namespace: row.namespace,
|
|
20956
21794
|
packId: row.packId,
|
|
20957
21795
|
version: row.version,
|
|
@@ -20963,7 +21801,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20963
21801
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20964
21802
|
this.upsertAvailableStmt.run({
|
|
20965
21803
|
...params,
|
|
20966
|
-
id:
|
|
21804
|
+
id: randomUUID3(),
|
|
20967
21805
|
recordedBy: meta3?.recordedBy ?? null
|
|
20968
21806
|
});
|
|
20969
21807
|
} else {
|
|
@@ -21286,14 +22124,15 @@ var SqliteInventoryRepository = class {
|
|
|
21286
22124
|
};
|
|
21287
22125
|
|
|
21288
22126
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21289
|
-
import { randomUUID as
|
|
22127
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21290
22128
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21291
22129
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21292
22130
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21293
22131
|
var HARNESS_LABELS = {
|
|
21294
22132
|
claudecode: "Claude Code",
|
|
21295
22133
|
cursor: "Cursor",
|
|
21296
|
-
codex: "Codex"
|
|
22134
|
+
codex: "Codex",
|
|
22135
|
+
antigravity: "Antigravity"
|
|
21297
22136
|
};
|
|
21298
22137
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21299
22138
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21308,6 +22147,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21308
22147
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21309
22148
|
if (t.includes("cursor")) return "cursor";
|
|
21310
22149
|
if (t.includes("codex")) return "codex";
|
|
22150
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21311
22151
|
return null;
|
|
21312
22152
|
}
|
|
21313
22153
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21766,7 +22606,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21766
22606
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21767
22607
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21768
22608
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21769
|
-
).run({ id:
|
|
22609
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21770
22610
|
}
|
|
21771
22611
|
return true;
|
|
21772
22612
|
}
|
|
@@ -21787,7 +22627,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21787
22627
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21788
22628
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21789
22629
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21790
|
-
).run({ id:
|
|
22630
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21791
22631
|
}
|
|
21792
22632
|
this.configRowsCache = void 0;
|
|
21793
22633
|
return "ok";
|
|
@@ -22084,7 +22924,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22084
22924
|
};
|
|
22085
22925
|
|
|
22086
22926
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22087
|
-
import { randomUUID as
|
|
22927
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22088
22928
|
var SqlitePoliciesRepository = class {
|
|
22089
22929
|
constructor(db) {
|
|
22090
22930
|
this.db = db;
|
|
@@ -22119,7 +22959,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22119
22959
|
failOpenTransaction(this.db, () => {
|
|
22120
22960
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22121
22961
|
stmt.run({
|
|
22122
|
-
id:
|
|
22962
|
+
id: randomUUID5(),
|
|
22123
22963
|
target: JSON.stringify({ category }),
|
|
22124
22964
|
action,
|
|
22125
22965
|
now: Date.now()
|
|
@@ -22139,7 +22979,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22139
22979
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22140
22980
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22141
22981
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22142
|
-
).run({ id:
|
|
22982
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22143
22983
|
}
|
|
22144
22984
|
// Caps every global per-category policy currently set to block/redact down
|
|
22145
22985
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22207,7 +23047,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22207
23047
|
};
|
|
22208
23048
|
|
|
22209
23049
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22210
|
-
import { randomUUID as
|
|
23050
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22211
23051
|
var SqliteProjectFilesRepository = class {
|
|
22212
23052
|
constructor(db) {
|
|
22213
23053
|
this.db = db;
|
|
@@ -22239,7 +23079,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22239
23079
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22240
23080
|
for (const file2 of scan2.files) {
|
|
22241
23081
|
this.upsertStmt.run({
|
|
22242
|
-
id:
|
|
23082
|
+
id: randomUUID6(),
|
|
22243
23083
|
projectId,
|
|
22244
23084
|
path: file2.path,
|
|
22245
23085
|
name: file2.name,
|
|
@@ -22253,7 +23093,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22253
23093
|
};
|
|
22254
23094
|
|
|
22255
23095
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22256
|
-
import { randomUUID as
|
|
23096
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22257
23097
|
var SqliteResolutionsRepository = class {
|
|
22258
23098
|
constructor(db, now = () => Date.now()) {
|
|
22259
23099
|
this.db = db;
|
|
@@ -22307,7 +23147,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22307
23147
|
*/
|
|
22308
23148
|
insertResolution(r) {
|
|
22309
23149
|
this.insertStmt.run({
|
|
22310
|
-
id:
|
|
23150
|
+
id: randomUUID7(),
|
|
22311
23151
|
findingKey: r.findingKey,
|
|
22312
23152
|
status: FindingStatus.parse(r.status),
|
|
22313
23153
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22366,13 +23206,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22366
23206
|
this.readStmt = db.prepare(
|
|
22367
23207
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22368
23208
|
);
|
|
23209
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23210
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23211
|
+
);
|
|
23212
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23213
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23214
|
+
);
|
|
22369
23215
|
}
|
|
22370
23216
|
db;
|
|
22371
23217
|
upsertStmt;
|
|
22372
23218
|
readStmt;
|
|
23219
|
+
countQuarantinedStmt;
|
|
23220
|
+
clearQuarantinedStmt;
|
|
22373
23221
|
getVerdict(ruleKey) {
|
|
22374
23222
|
return getRow(this.readStmt, { ruleKey });
|
|
22375
23223
|
}
|
|
23224
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23225
|
+
countQuarantined() {
|
|
23226
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23227
|
+
}
|
|
23228
|
+
/**
|
|
23229
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23230
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23231
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23232
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23233
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23234
|
+
*
|
|
23235
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23236
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23237
|
+
*
|
|
23238
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23239
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23240
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23241
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23242
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23243
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23244
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23245
|
+
* to restore are silently still disabled.
|
|
23246
|
+
*/
|
|
23247
|
+
clearQuarantined() {
|
|
23248
|
+
const before = this.countQuarantined();
|
|
23249
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23250
|
+
this.clearQuarantinedStmt.run();
|
|
23251
|
+
});
|
|
23252
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23253
|
+
}
|
|
22376
23254
|
setVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
22377
23255
|
failOpenTransaction(this.db, () => {
|
|
22378
23256
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs: worstProbeMs2, checkedAt: Date.now() });
|
|
@@ -22427,7 +23305,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22427
23305
|
};
|
|
22428
23306
|
|
|
22429
23307
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22430
|
-
import { randomUUID as
|
|
23308
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23309
|
+
function pageLimit(requested, fallback) {
|
|
23310
|
+
if (requested === void 0) return fallback;
|
|
23311
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23312
|
+
}
|
|
23313
|
+
function encodeReuseCursor(payload) {
|
|
23314
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23315
|
+
}
|
|
23316
|
+
function decodeReuseCursor(cursor) {
|
|
23317
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23318
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23319
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23320
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23321
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23322
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23323
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23324
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23325
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23326
|
+
}
|
|
23327
|
+
return null;
|
|
23328
|
+
}
|
|
23329
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23330
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23331
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23332
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23333
|
+
function toSighting(row) {
|
|
23334
|
+
return {
|
|
23335
|
+
location: row.location,
|
|
23336
|
+
kind: row.kind,
|
|
23337
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23338
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23339
|
+
};
|
|
23340
|
+
}
|
|
22431
23341
|
var SELECT_COLUMNS = `
|
|
22432
23342
|
pointer_id AS pointerId,
|
|
22433
23343
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22611,39 +23521,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22611
23521
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22612
23522
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22613
23523
|
).run({
|
|
22614
|
-
id:
|
|
23524
|
+
id: randomUUID8(),
|
|
22615
23525
|
pointerId: entry.pointerId,
|
|
22616
23526
|
location: entry.location,
|
|
22617
23527
|
kind: entry.kind,
|
|
22618
23528
|
now
|
|
22619
23529
|
});
|
|
22620
23530
|
}
|
|
22621
|
-
|
|
23531
|
+
/**
|
|
23532
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23533
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23534
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23535
|
+
*
|
|
23536
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23537
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23538
|
+
*/
|
|
23539
|
+
sightingsFor(pointerIds) {
|
|
23540
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23541
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22622
23542
|
const rows = allRows(
|
|
22623
23543
|
this.db.prepare(
|
|
22624
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22625
|
-
|
|
23544
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23545
|
+
FROM secret_vault_sighting
|
|
23546
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23547
|
+
ORDER BY last_seen DESC`
|
|
22626
23548
|
),
|
|
22627
|
-
|
|
23549
|
+
pointerIds
|
|
22628
23550
|
);
|
|
23551
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23552
|
+
return byPointer;
|
|
23553
|
+
}
|
|
23554
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23555
|
+
toInventoryEntries(rows) {
|
|
23556
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22629
23557
|
return rows.map((r) => ({
|
|
22630
|
-
|
|
22631
|
-
|
|
23558
|
+
pointerId: r.pointer_id,
|
|
23559
|
+
category: r.category,
|
|
23560
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23561
|
+
maskedMatch: r.masked_match,
|
|
23562
|
+
occurrences: r.occurrence_count,
|
|
22632
23563
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22633
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23564
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23565
|
+
revealGrantId: r.grant_id,
|
|
23566
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22634
23567
|
}));
|
|
22635
23568
|
}
|
|
22636
23569
|
/**
|
|
22637
|
-
* The dashboard inventory
|
|
22638
|
-
* its sightings and the active
|
|
22639
|
-
* Raw-free by construction — neither
|
|
22640
|
-
* columns are selected.
|
|
23570
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23571
|
+
* value's descriptor data joined with its sightings and the active
|
|
23572
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23573
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23574
|
+
*
|
|
23575
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23576
|
+
* sees never depends on how far they have paged.
|
|
22641
23577
|
*/
|
|
22642
|
-
listInventory(now = Date.now()) {
|
|
23578
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23579
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23580
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23581
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22643
23582
|
const rows = allRows(
|
|
22644
23583
|
this.db.prepare(
|
|
22645
|
-
`SELECT
|
|
22646
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23584
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22647
23585
|
(SELECT e.id FROM exceptions e
|
|
22648
23586
|
WHERE e.rule_id = v.rule_id
|
|
22649
23587
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22651,45 +23589,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22651
23589
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22652
23590
|
LIMIT 1) AS grant_id
|
|
22653
23591
|
FROM secret_vault v
|
|
22654
|
-
|
|
23592
|
+
${where}
|
|
23593
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23594
|
+
LIMIT :limit`
|
|
22655
23595
|
),
|
|
22656
|
-
{
|
|
23596
|
+
bindParams({
|
|
23597
|
+
now,
|
|
23598
|
+
limit: limit + 1,
|
|
23599
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23600
|
+
})
|
|
22657
23601
|
);
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
23602
|
+
const hasMore = rows.length > limit;
|
|
23603
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23604
|
+
const last = page[page.length - 1];
|
|
23605
|
+
return {
|
|
23606
|
+
totals: { values: this.countEntries() },
|
|
23607
|
+
items: this.toInventoryEntries(page),
|
|
23608
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23609
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23610
|
+
};
|
|
23611
|
+
}
|
|
23612
|
+
/**
|
|
23613
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23614
|
+
* than one location — most-reused first, one page at a time.
|
|
23615
|
+
*
|
|
23616
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23617
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23618
|
+
* under-report exactly the values a reader most needs to see.
|
|
23619
|
+
*/
|
|
23620
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23621
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23622
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23623
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23624
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23625
|
+
const rows = allRows(
|
|
23626
|
+
this.db.prepare(
|
|
23627
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23628
|
+
(SELECT e.id FROM exceptions e
|
|
23629
|
+
WHERE e.rule_id = v.rule_id
|
|
23630
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23631
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23632
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23633
|
+
LIMIT 1) AS grant_id
|
|
23634
|
+
FROM secret_vault v
|
|
23635
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23636
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23637
|
+
LIMIT :limit`
|
|
23638
|
+
),
|
|
23639
|
+
bindParams({
|
|
23640
|
+
now,
|
|
23641
|
+
limit: limit + 1,
|
|
23642
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23643
|
+
})
|
|
23644
|
+
);
|
|
23645
|
+
const hasMore = rows.length > limit;
|
|
23646
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23647
|
+
const last = page[page.length - 1];
|
|
23648
|
+
return {
|
|
23649
|
+
totals: { reused: this.countReused() },
|
|
23650
|
+
items: this.toInventoryEntries(page),
|
|
23651
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23652
|
+
};
|
|
22669
23653
|
}
|
|
22670
23654
|
/**
|
|
22671
|
-
* The de-reference trail, newest first. By default the
|
|
22672
|
-
* reasons (display, view-render) are hidden and counted
|
|
22673
|
-
* that matter as a signal are the model crossings, and
|
|
22674
|
-
* render noise would defeat the audit's purpose.
|
|
23655
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23656
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23657
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23658
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23659
|
+
*
|
|
23660
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23661
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23662
|
+
* the reader pages.
|
|
22675
23663
|
*/
|
|
22676
|
-
listDerefs(
|
|
22677
|
-
const limit =
|
|
22678
|
-
const
|
|
23664
|
+
listDerefs(query = {}) {
|
|
23665
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23666
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23667
|
+
const conditions = [];
|
|
23668
|
+
if (query.includeBatched !== true) {
|
|
23669
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23670
|
+
}
|
|
23671
|
+
if (cursor !== null) {
|
|
23672
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23673
|
+
}
|
|
23674
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22679
23675
|
const rows = allRows(
|
|
22680
23676
|
this.db.prepare(
|
|
22681
23677
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22682
23678
|
FROM secret_vault_deref ${where}
|
|
22683
|
-
ORDER BY at DESC,
|
|
23679
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22684
23680
|
),
|
|
22685
|
-
{
|
|
23681
|
+
bindParams({
|
|
23682
|
+
limit: limit + 1,
|
|
23683
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23684
|
+
})
|
|
22686
23685
|
);
|
|
22687
|
-
const
|
|
23686
|
+
const hasMore = rows.length > limit;
|
|
23687
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23688
|
+
const last = page[page.length - 1];
|
|
23689
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22688
23690
|
this.db,
|
|
22689
23691
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22690
23692
|
);
|
|
22691
23693
|
return {
|
|
22692
|
-
|
|
23694
|
+
items: page.map((r) => ({
|
|
22693
23695
|
id: r.id,
|
|
22694
23696
|
pointerId: r.pointer_id,
|
|
22695
23697
|
at: new Date(r.at).toISOString(),
|
|
@@ -22699,12 +23701,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22699
23701
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22700
23702
|
pointerCount: r.pointer_count
|
|
22701
23703
|
})),
|
|
23704
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22702
23705
|
hiddenBatched
|
|
22703
23706
|
};
|
|
22704
23707
|
}
|
|
22705
23708
|
countEntries() {
|
|
22706
23709
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22707
23710
|
}
|
|
23711
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23712
|
+
countReused() {
|
|
23713
|
+
return countScalar(
|
|
23714
|
+
this.db,
|
|
23715
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23716
|
+
);
|
|
23717
|
+
}
|
|
22708
23718
|
};
|
|
22709
23719
|
|
|
22710
23720
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22719,7 +23729,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22719
23729
|
var SCAN_COVERAGE = [
|
|
22720
23730
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22721
23731
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22722
|
-
{ provider: "codex", coverage:
|
|
23732
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23733
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23734
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22723
23735
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22724
23736
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22725
23737
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23052,7 +24064,7 @@ var SqliteSecurityRepository = class {
|
|
|
23052
24064
|
};
|
|
23053
24065
|
|
|
23054
24066
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23055
|
-
import { randomUUID as
|
|
24067
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23056
24068
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23057
24069
|
var IN_CHUNK = 500;
|
|
23058
24070
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23308,7 +24320,7 @@ var SqliteSharesRepository = class {
|
|
|
23308
24320
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23309
24321
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23310
24322
|
).run({
|
|
23311
|
-
id:
|
|
24323
|
+
id: randomUUID9(),
|
|
23312
24324
|
destinationId,
|
|
23313
24325
|
host: dest.host,
|
|
23314
24326
|
decision,
|
|
@@ -23457,7 +24469,7 @@ var SqliteSharesRepository = class {
|
|
|
23457
24469
|
let destinationId = destIds.get(hit.host);
|
|
23458
24470
|
if (destinationId === void 0) {
|
|
23459
24471
|
destStmt.run({
|
|
23460
|
-
id:
|
|
24472
|
+
id: randomUUID9(),
|
|
23461
24473
|
kind: hit.kind,
|
|
23462
24474
|
name: hit.name,
|
|
23463
24475
|
host: hit.host,
|
|
@@ -23473,7 +24485,7 @@ var SqliteSharesRepository = class {
|
|
|
23473
24485
|
let endpointId = endpointIds.get(endpointKey);
|
|
23474
24486
|
if (endpointId === void 0) {
|
|
23475
24487
|
endpointStmt.run({
|
|
23476
|
-
id:
|
|
24488
|
+
id: randomUUID9(),
|
|
23477
24489
|
destinationId,
|
|
23478
24490
|
method: hit.method,
|
|
23479
24491
|
transport: hit.transport,
|
|
@@ -23486,7 +24498,7 @@ var SqliteSharesRepository = class {
|
|
|
23486
24498
|
endpointIds.set(endpointKey, endpointId);
|
|
23487
24499
|
}
|
|
23488
24500
|
siteStmt.run({
|
|
23489
|
-
id:
|
|
24501
|
+
id: randomUUID9(),
|
|
23490
24502
|
endpointId,
|
|
23491
24503
|
project: input.project,
|
|
23492
24504
|
projectKey: input.projectKey,
|
|
@@ -23851,6 +24863,9 @@ function purgeSampleData(db) {
|
|
|
23851
24863
|
}
|
|
23852
24864
|
|
|
23853
24865
|
// ../../packages/persistence/src/database.ts
|
|
24866
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24867
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24868
|
+
);
|
|
23854
24869
|
function linkHost(input, hostId) {
|
|
23855
24870
|
return hostId ? { ...input, hostId } : input;
|
|
23856
24871
|
}
|
|
@@ -23872,21 +24887,34 @@ function openWithPragmas(file2) {
|
|
|
23872
24887
|
}
|
|
23873
24888
|
return db;
|
|
23874
24889
|
}
|
|
23875
|
-
function backupLegacyStore(file2) {
|
|
23876
|
-
|
|
23877
|
-
|
|
23878
|
-
|
|
23879
|
-
|
|
23880
|
-
|
|
24890
|
+
function backupLegacyStore(db, file2) {
|
|
24891
|
+
reapStalePartials(file2);
|
|
24892
|
+
const backup = backupPath(file2, "legacy");
|
|
24893
|
+
let snapshotted = false;
|
|
24894
|
+
let snapshotError;
|
|
24895
|
+
try {
|
|
24896
|
+
snapshotStore(db, backup);
|
|
24897
|
+
snapshotted = true;
|
|
24898
|
+
} catch (error51) {
|
|
24899
|
+
snapshotError = error51;
|
|
24900
|
+
} finally {
|
|
24901
|
+
db.close();
|
|
24902
|
+
}
|
|
24903
|
+
if (!snapshotted) {
|
|
24904
|
+
akaWarn(
|
|
24905
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24906
|
+
);
|
|
24907
|
+
moveStoreAside(file2, backup);
|
|
24908
|
+
return backup;
|
|
23881
24909
|
}
|
|
24910
|
+
discardStore(file2, backup);
|
|
23882
24911
|
return backup;
|
|
23883
24912
|
}
|
|
23884
24913
|
function openAndInitialize(file2) {
|
|
23885
24914
|
let db = openWithPragmas(file2);
|
|
23886
24915
|
try {
|
|
23887
24916
|
if (isForeignSqliteLineage(db)) {
|
|
23888
|
-
db
|
|
23889
|
-
const backup = backupLegacyStore(file2);
|
|
24917
|
+
const backup = backupLegacyStore(db, file2);
|
|
23890
24918
|
db = openWithPragmas(file2);
|
|
23891
24919
|
akaWarn(
|
|
23892
24920
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23930,7 +24958,7 @@ function openAndInitialize(file2) {
|
|
|
23930
24958
|
}
|
|
23931
24959
|
function openLocalDatabase(dir) {
|
|
23932
24960
|
ensureDataDirSync(dir);
|
|
23933
|
-
const file2 =
|
|
24961
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23934
24962
|
const {
|
|
23935
24963
|
db,
|
|
23936
24964
|
events,
|
|
@@ -24047,7 +25075,7 @@ function openLocalDatabase(dir) {
|
|
|
24047
25075
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24048
25076
|
if (!definitionId) continue;
|
|
24049
25077
|
inspectionFindings.insertFinding({
|
|
24050
|
-
id:
|
|
25078
|
+
id: randomUUID10(),
|
|
24051
25079
|
auditEventId: record2.scanEvent.id,
|
|
24052
25080
|
inspectionDefinitionId: definitionId,
|
|
24053
25081
|
span: finding.span,
|
|
@@ -24153,7 +25181,9 @@ function openLocalDatabase(dir) {
|
|
|
24153
25181
|
transaction,
|
|
24154
25182
|
close: () => {
|
|
24155
25183
|
db.close();
|
|
24156
|
-
}
|
|
25184
|
+
},
|
|
25185
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25186
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24157
25187
|
};
|
|
24158
25188
|
}
|
|
24159
25189
|
|
|
@@ -24177,6 +25207,20 @@ var UserGrantPolicyProvider = class {
|
|
|
24177
25207
|
}
|
|
24178
25208
|
};
|
|
24179
25209
|
|
|
25210
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25211
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25212
|
+
import {
|
|
25213
|
+
closeSync,
|
|
25214
|
+
existsSync as existsSync2,
|
|
25215
|
+
openSync,
|
|
25216
|
+
readFileSync,
|
|
25217
|
+
rmSync as rmSync3,
|
|
25218
|
+
statSync as statSync2,
|
|
25219
|
+
writeFileSync as writeFileSync2
|
|
25220
|
+
} from "fs";
|
|
25221
|
+
import { hostname as hostname3 } from "os";
|
|
25222
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25223
|
+
|
|
24180
25224
|
// ../../packages/persistence/src/finding-key.ts
|
|
24181
25225
|
import { createHash as createHash3 } from "crypto";
|
|
24182
25226
|
function normalizeFilePath(filePath) {
|
|
@@ -24189,13 +25233,13 @@ function computeFindingKey(input) {
|
|
|
24189
25233
|
|
|
24190
25234
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24191
25235
|
import { createHmac, randomBytes } from "crypto";
|
|
24192
|
-
import { existsSync as
|
|
24193
|
-
import { join as
|
|
25236
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25237
|
+
import { join as join3 } from "path";
|
|
24194
25238
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24195
|
-
var
|
|
25239
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24196
25240
|
var KEY_MATERIAL_BYTES = 32;
|
|
24197
25241
|
function keyFilePath(dataDir2) {
|
|
24198
|
-
return
|
|
25242
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24199
25243
|
}
|
|
24200
25244
|
function parseKeyFile(raw) {
|
|
24201
25245
|
const parsed = JSON.parse(raw);
|
|
@@ -24233,8 +25277,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
24233
25277
|
}
|
|
24234
25278
|
};
|
|
24235
25279
|
function storedKeyVersionFloor(dataDir2) {
|
|
24236
|
-
const file2 =
|
|
24237
|
-
if (!
|
|
25280
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25281
|
+
if (!existsSync3(file2)) return 0;
|
|
24238
25282
|
let db;
|
|
24239
25283
|
try {
|
|
24240
25284
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -24259,18 +25303,36 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
24259
25303
|
db?.close();
|
|
24260
25304
|
}
|
|
24261
25305
|
}
|
|
24262
|
-
function
|
|
25306
|
+
function serializeKey(key) {
|
|
25307
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
25308
|
+
}
|
|
25309
|
+
function createKeyFile(dataDir2, key) {
|
|
24263
25310
|
ensureDataDirSync(dataDir2);
|
|
24264
25311
|
const file2 = keyFilePath(dataDir2);
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
25312
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
25313
|
+
`)) return key;
|
|
25314
|
+
const winner = readFingerprintKey(dataDir2);
|
|
25315
|
+
if (winner) {
|
|
25316
|
+
tightenFile(file2);
|
|
25317
|
+
return winner;
|
|
25318
|
+
}
|
|
25319
|
+
const occupant = classifyOccupant(file2);
|
|
25320
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
25321
|
+
}
|
|
25322
|
+
function occupantMessage(file2, kind) {
|
|
25323
|
+
switch (kind) {
|
|
25324
|
+
case "symlink":
|
|
25325
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
25326
|
+
case "gone":
|
|
25327
|
+
return "exception key file was removed while it was being created";
|
|
25328
|
+
case "unknown":
|
|
25329
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
25330
|
+
}
|
|
24269
25331
|
}
|
|
24270
25332
|
function readFingerprintKey(dataDir2) {
|
|
24271
25333
|
let raw;
|
|
24272
25334
|
try {
|
|
24273
|
-
raw =
|
|
25335
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24274
25336
|
} catch (err) {
|
|
24275
25337
|
if (err.code === "ENOENT") return null;
|
|
24276
25338
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24283,7 +25345,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
24283
25345
|
tightenFile(keyFilePath(dataDir2));
|
|
24284
25346
|
return existing;
|
|
24285
25347
|
}
|
|
24286
|
-
return
|
|
25348
|
+
return createKeyFile(dataDir2, {
|
|
24287
25349
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
24288
25350
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
24289
25351
|
});
|
|
@@ -24296,21 +25358,21 @@ function fingerprintValue(key, raw) {
|
|
|
24296
25358
|
import { renameSync as renameSync3 } from "fs";
|
|
24297
25359
|
import { mkdir } from "fs/promises";
|
|
24298
25360
|
import { homedir } from "os";
|
|
24299
|
-
import { join as
|
|
25361
|
+
import { join as join4 } from "path";
|
|
24300
25362
|
function defaultDataDir() {
|
|
24301
|
-
return
|
|
25363
|
+
return join4(homedir(), ".aka");
|
|
24302
25364
|
}
|
|
24303
25365
|
function settingsDir(base = defaultDataDir()) {
|
|
24304
|
-
return
|
|
25366
|
+
return join4(base, "settings");
|
|
24305
25367
|
}
|
|
24306
25368
|
function dataDir(base = defaultDataDir()) {
|
|
24307
|
-
return
|
|
25369
|
+
return join4(base, "data");
|
|
24308
25370
|
}
|
|
24309
25371
|
function dbPath(base = defaultDataDir()) {
|
|
24310
|
-
return
|
|
25372
|
+
return join4(dataDir(base), "aka.db");
|
|
24311
25373
|
}
|
|
24312
25374
|
function keysDir(base = defaultDataDir()) {
|
|
24313
|
-
return
|
|
25375
|
+
return join4(base, "keys");
|
|
24314
25376
|
}
|
|
24315
25377
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24316
25378
|
ensureDataDirSync(dir);
|
|
@@ -24323,8 +25385,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24323
25385
|
for (const { name, dest } of moves) {
|
|
24324
25386
|
try {
|
|
24325
25387
|
ensureDataDirSync(dest);
|
|
24326
|
-
const moved =
|
|
24327
|
-
renameSync3(
|
|
25388
|
+
const moved = join4(dest, name);
|
|
25389
|
+
renameSync3(join4(base, name), moved);
|
|
24328
25390
|
tightenFile(moved);
|
|
24329
25391
|
} catch {
|
|
24330
25392
|
}
|
|
@@ -24332,10 +25394,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24332
25394
|
}
|
|
24333
25395
|
|
|
24334
25396
|
// ../../packages/persistence/src/settings.ts
|
|
24335
|
-
import { readFileSync as
|
|
24336
|
-
import { join as
|
|
25397
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25398
|
+
import { join as join5 } from "path";
|
|
25399
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24337
25400
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24338
|
-
const record2 = readJson(
|
|
25401
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24339
25402
|
if (!record2) return defaultWorkspaceSettings();
|
|
24340
25403
|
try {
|
|
24341
25404
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24346,7 +25409,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24346
25409
|
function readJson(file2) {
|
|
24347
25410
|
let text;
|
|
24348
25411
|
try {
|
|
24349
|
-
text =
|
|
25412
|
+
text = readFileSync3(file2, "utf8");
|
|
24350
25413
|
} catch {
|
|
24351
25414
|
return null;
|
|
24352
25415
|
}
|
|
@@ -24467,13 +25530,18 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24467
25530
|
import {
|
|
24468
25531
|
chmodSync as chmodSync2,
|
|
24469
25532
|
mkdirSync as mkdirSync2,
|
|
24470
|
-
readFileSync as
|
|
25533
|
+
readFileSync as readFileSync4,
|
|
24471
25534
|
renameSync as renameSync4,
|
|
24472
|
-
rmSync as
|
|
24473
|
-
statSync,
|
|
24474
|
-
writeFileSync as
|
|
25535
|
+
rmSync as rmSync4,
|
|
25536
|
+
statSync as statSync3,
|
|
25537
|
+
writeFileSync as writeFileSync3
|
|
24475
25538
|
} from "fs";
|
|
24476
|
-
import { join as
|
|
25539
|
+
import { join as join6 } from "path";
|
|
25540
|
+
var VAULT_OCCUPANT_REASON = {
|
|
25541
|
+
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
25542
|
+
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
25543
|
+
unknown: "the path is occupied but cannot be inspected; check the permissions on its directory"
|
|
25544
|
+
};
|
|
24477
25545
|
var VaultKeyEpochMissingError = class extends Error {
|
|
24478
25546
|
version;
|
|
24479
25547
|
constructor(version2) {
|
|
@@ -24566,28 +25634,28 @@ function claimRotationLock(lock, owner) {
|
|
|
24566
25634
|
throw asError(err);
|
|
24567
25635
|
}
|
|
24568
25636
|
try {
|
|
24569
|
-
|
|
25637
|
+
writeFileSync3(join6(lock, LOCK_OWNER_FILE), `${owner}
|
|
24570
25638
|
`, { mode: DATA_FILE_MODE });
|
|
24571
25639
|
return true;
|
|
24572
25640
|
} catch (err) {
|
|
24573
|
-
|
|
25641
|
+
rmSync4(lock, { recursive: true, force: true });
|
|
24574
25642
|
throw asError(err);
|
|
24575
25643
|
}
|
|
24576
25644
|
}
|
|
24577
25645
|
function acquireRotationLock(keysDir2) {
|
|
24578
|
-
const lock =
|
|
25646
|
+
const lock = join6(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
24579
25647
|
const owner = randomBytes2(16).toString("hex");
|
|
24580
25648
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
24581
25649
|
let held;
|
|
24582
25650
|
try {
|
|
24583
|
-
held =
|
|
25651
|
+
held = statSync3(lock);
|
|
24584
25652
|
} catch {
|
|
24585
25653
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24586
25654
|
}
|
|
24587
25655
|
if (Date.now() - held.mtimeMs < ROTATION_LOCK_STALE_MS) throw new Error(ROTATION_IN_PROGRESS);
|
|
24588
25656
|
const aside = `${lock}.stale.${owner}`;
|
|
24589
25657
|
try {
|
|
24590
|
-
const now =
|
|
25658
|
+
const now = statSync3(lock);
|
|
24591
25659
|
if (now.ino !== held.ino || now.mtimeMs !== held.mtimeMs) {
|
|
24592
25660
|
throw new Error(ROTATION_IN_PROGRESS);
|
|
24593
25661
|
}
|
|
@@ -24596,17 +25664,17 @@ function acquireRotationLock(keysDir2) {
|
|
|
24596
25664
|
if (err instanceof Error && err.message === ROTATION_IN_PROGRESS) throw err;
|
|
24597
25665
|
throw new Error(ROTATION_IN_PROGRESS, { cause: err });
|
|
24598
25666
|
}
|
|
24599
|
-
|
|
25667
|
+
rmSync4(aside, { recursive: true, force: true });
|
|
24600
25668
|
if (!claimRotationLock(lock, owner)) throw new Error(ROTATION_IN_PROGRESS);
|
|
24601
25669
|
return { lock, owner };
|
|
24602
25670
|
}
|
|
24603
25671
|
function releaseRotationLock(lease) {
|
|
24604
25672
|
try {
|
|
24605
|
-
if (
|
|
25673
|
+
if (readFileSync4(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
24606
25674
|
} catch {
|
|
24607
25675
|
return;
|
|
24608
25676
|
}
|
|
24609
|
-
|
|
25677
|
+
rmSync4(lease.lock, { recursive: true, force: true });
|
|
24610
25678
|
}
|
|
24611
25679
|
function withRotationLock(keysDir2, work) {
|
|
24612
25680
|
ensureDataDirSync(keysDir2);
|
|
@@ -24623,7 +25691,7 @@ var FileKeyProvider = class {
|
|
|
24623
25691
|
this.#keysDir = keysDir2;
|
|
24624
25692
|
}
|
|
24625
25693
|
get filePath() {
|
|
24626
|
-
return
|
|
25694
|
+
return join6(this.#keysDir, VAULT_KEY_FILENAME);
|
|
24627
25695
|
}
|
|
24628
25696
|
loadOrCreate() {
|
|
24629
25697
|
return asAsync(() => {
|
|
@@ -24653,7 +25721,7 @@ var FileKeyProvider = class {
|
|
|
24653
25721
|
#read() {
|
|
24654
25722
|
let raw;
|
|
24655
25723
|
try {
|
|
24656
|
-
raw =
|
|
25724
|
+
raw = readFileSync4(this.filePath, "utf8");
|
|
24657
25725
|
} catch (err) {
|
|
24658
25726
|
if (err.code === "ENOENT") return null;
|
|
24659
25727
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24661,34 +25729,32 @@ var FileKeyProvider = class {
|
|
|
24661
25729
|
return parseKeyring(raw);
|
|
24662
25730
|
}
|
|
24663
25731
|
/**
|
|
24664
|
-
* First mint: the keyring is
|
|
24665
|
-
*
|
|
24666
|
-
*
|
|
24667
|
-
*
|
|
24668
|
-
*
|
|
24669
|
-
*
|
|
24670
|
-
*
|
|
24671
|
-
*
|
|
25732
|
+
* First mint: the keyring is CREATED, never replaced, so two processes racing
|
|
25733
|
+
* a fresh machine cannot each mint a different epoch 1 — with tmp + rename
|
|
25734
|
+
* the loser's replace would orphan everything the winner had already sealed.
|
|
25735
|
+
* The loser re-reads and adopts the winner's keyring; it minted nothing.
|
|
25736
|
+
*
|
|
25737
|
+
* `createOwnerOnlyFileSync` publishes by link rather than by an exclusive open
|
|
25738
|
+
* at the final path, so the keyring never exists at zero length: a reader —
|
|
25739
|
+
* including the loser, re-reading in order to adopt — sees the file absent or
|
|
25740
|
+
* whole, and never mistakes a live keyring for a corrupt one. A corrupt file
|
|
25741
|
+
* still throws from the parse and is never re-minted over.
|
|
24672
25742
|
*/
|
|
24673
25743
|
#createExclusive() {
|
|
24674
25744
|
ensureDataDirSync(this.#keysDir);
|
|
24675
25745
|
const keyring = mintKeyring();
|
|
24676
|
-
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
|
|
24682
|
-
|
|
24683
|
-
|
|
24684
|
-
|
|
24685
|
-
if (!winner) {
|
|
24686
|
-
throw new Error("vault: key file vanished during first mint", { cause: err });
|
|
24687
|
-
}
|
|
24688
|
-
return winner;
|
|
25746
|
+
if (createOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
25747
|
+
`)) return keyring;
|
|
25748
|
+
const winner = this.#read();
|
|
25749
|
+
if (!winner) {
|
|
25750
|
+
const occupant = classifyOccupant(this.filePath);
|
|
25751
|
+
throw new KeyUnclaimableError(
|
|
25752
|
+
`vault: cannot create a key file at ${this.filePath} \u2014 ${VAULT_OCCUPANT_REASON[occupant.kind]}`,
|
|
25753
|
+
occupant.cause
|
|
25754
|
+
);
|
|
24689
25755
|
}
|
|
24690
25756
|
tightenFileMode(this.filePath);
|
|
24691
|
-
return
|
|
25757
|
+
return winner;
|
|
24692
25758
|
}
|
|
24693
25759
|
/**
|
|
24694
25760
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
@@ -24699,7 +25765,7 @@ var FileKeyProvider = class {
|
|
|
24699
25765
|
ensureDataDirSync(this.#keysDir);
|
|
24700
25766
|
const file2 = this.filePath;
|
|
24701
25767
|
const tmp = `${file2}.tmp`;
|
|
24702
|
-
|
|
25768
|
+
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
24703
25769
|
`, { mode: DATA_FILE_MODE });
|
|
24704
25770
|
renameSync4(tmp, file2);
|
|
24705
25771
|
tightenFileMode(file2);
|
|
@@ -24825,7 +25891,7 @@ function createKeyProvider(custody, keysDir2) {
|
|
|
24825
25891
|
}
|
|
24826
25892
|
|
|
24827
25893
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24828
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25894
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24829
25895
|
var CONSENT_ABSENT = /* @__PURE__ */ Symbol("aka.vault.consentAbsent");
|
|
24830
25896
|
var UNAVAILABLE = /* @__PURE__ */ Symbol("aka.vault.unavailable");
|
|
24831
25897
|
var VAULT_PURGE_POINTER_ID = "*";
|
|
@@ -24852,14 +25918,12 @@ function parsePointer(token) {
|
|
|
24852
25918
|
var SecretVault = class {
|
|
24853
25919
|
#repo;
|
|
24854
25920
|
#keys;
|
|
24855
|
-
#fingerprintKey;
|
|
24856
25921
|
#isConsented;
|
|
24857
25922
|
#verifyGrant;
|
|
24858
25923
|
#now;
|
|
24859
25924
|
constructor(deps) {
|
|
24860
25925
|
this.#repo = deps.repo;
|
|
24861
25926
|
this.#keys = deps.keys;
|
|
24862
|
-
this.#fingerprintKey = deps.fingerprintKey;
|
|
24863
25927
|
this.#isConsented = deps.isConsented;
|
|
24864
25928
|
this.#verifyGrant = deps.verifyGrant;
|
|
24865
25929
|
this.#now = deps.now ?? (() => Date.now());
|
|
@@ -24868,10 +25932,23 @@ var SecretVault = class {
|
|
|
24868
25932
|
* Store a value and return the pointer that stands for it. The same value
|
|
24869
25933
|
* always yields the same pointer on this machine — one row, one pointer id,
|
|
24870
25934
|
* one category — which is what makes dedup and reuse counting work.
|
|
25935
|
+
*
|
|
25936
|
+
* `fingerprintKey` is the exception-key epoch this value's fingerprint is
|
|
25937
|
+
* derived under — a different key from the vault's, with different rotation
|
|
25938
|
+
* semantics. It is a parameter of the WRITE rather than a constructor dep,
|
|
25939
|
+
* and a thunk rather than a value, so that the only way to reach a key is to
|
|
25940
|
+
* store something: a read-only caller never names it, and a caller whose
|
|
25941
|
+
* source mints on absence mints only once consent has actually opened the
|
|
25942
|
+
* write. `refreshFingerprints` takes its key the same way, for the same
|
|
25943
|
+
* reason.
|
|
25944
|
+
*
|
|
25945
|
+
* Resolved once per call, so the fingerprint and the version it is recorded
|
|
25946
|
+
* under can never come from two different epochs.
|
|
24871
25947
|
*/
|
|
24872
|
-
async tokenize(raw, meta3) {
|
|
25948
|
+
async tokenize(raw, meta3, fingerprintKey) {
|
|
24873
25949
|
if (!this.#isConsented()) return CONSENT_ABSENT;
|
|
24874
|
-
const
|
|
25950
|
+
const fpKey = fingerprintKey();
|
|
25951
|
+
const valueFingerprint = fingerprintValue(fpKey, raw);
|
|
24875
25952
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
24876
25953
|
const now = this.#now();
|
|
24877
25954
|
if (existing) {
|
|
@@ -24887,7 +25964,7 @@ var SecretVault = class {
|
|
|
24887
25964
|
{
|
|
24888
25965
|
pointerId: base32Encode(pointerId),
|
|
24889
25966
|
valueFingerprint,
|
|
24890
|
-
fingerprintKeyVersion:
|
|
25967
|
+
fingerprintKeyVersion: fpKey.version,
|
|
24891
25968
|
keyVersion: version2,
|
|
24892
25969
|
// Recorded so the row stays OPENABLE if the wire-format constant ever
|
|
24893
25970
|
// moves: it is part of this row's AEAD AAD. It is not a tag input —
|
|
@@ -25131,7 +26208,7 @@ var SecretVault = class {
|
|
|
25131
26208
|
purgeVault() {
|
|
25132
26209
|
const destroyed = this.#repo.purgeAll();
|
|
25133
26210
|
this.#repo.recordDeref({
|
|
25134
|
-
id:
|
|
26211
|
+
id: randomUUID12(),
|
|
25135
26212
|
pointerId: VAULT_PURGE_POINTER_ID,
|
|
25136
26213
|
at: this.#now(),
|
|
25137
26214
|
target: "human",
|
|
@@ -25200,7 +26277,7 @@ var SecretVault = class {
|
|
|
25200
26277
|
}
|
|
25201
26278
|
#audit(pointerId, opts, outcome) {
|
|
25202
26279
|
this.#repo.recordDeref({
|
|
25203
|
-
id:
|
|
26280
|
+
id: randomUUID12(),
|
|
25204
26281
|
pointerId,
|
|
25205
26282
|
at: this.#now(),
|
|
25206
26283
|
target: opts.target,
|
|
@@ -25215,22 +26292,22 @@ var SecretVault = class {
|
|
|
25215
26292
|
};
|
|
25216
26293
|
|
|
25217
26294
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
25218
|
-
import { existsSync as
|
|
25219
|
-
import { join as
|
|
26295
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
26296
|
+
import { join as join7 } from "path";
|
|
25220
26297
|
var MARKER = "warn-era-capped";
|
|
25221
26298
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
25222
26299
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
25223
|
-
const marker =
|
|
25224
|
-
if (
|
|
26300
|
+
const marker = join7(dataDir2, MARKER);
|
|
26301
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
25225
26302
|
const capped = db.policies.capCategoryActions();
|
|
25226
|
-
|
|
26303
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
25227
26304
|
`, { mode: DATA_FILE_MODE });
|
|
25228
26305
|
return { capped };
|
|
25229
26306
|
}
|
|
25230
26307
|
|
|
25231
26308
|
// ../../packages/plugin-sdk/src/config.ts
|
|
25232
|
-
import { existsSync as
|
|
25233
|
-
import { join as
|
|
26309
|
+
import { existsSync as existsSync5 } from "fs";
|
|
26310
|
+
import { join as join8 } from "path";
|
|
25234
26311
|
|
|
25235
26312
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
25236
26313
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -25281,11 +26358,11 @@ function resolveProvider() {
|
|
|
25281
26358
|
}
|
|
25282
26359
|
|
|
25283
26360
|
// ../../packages/plugin-sdk/src/config.ts
|
|
25284
|
-
function loadConfig(base = defaultDataDir()) {
|
|
26361
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
25285
26362
|
try {
|
|
25286
26363
|
ensureLayoutDirSync(base);
|
|
25287
|
-
const settingsFile =
|
|
25288
|
-
if (
|
|
26364
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
26365
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
25289
26366
|
} catch {
|
|
25290
26367
|
}
|
|
25291
26368
|
migrateLegacyLayout(base);
|
|
@@ -25296,21 +26373,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
25296
26373
|
dbPath: dbPath(base),
|
|
25297
26374
|
settingsDir: settingsDir(base),
|
|
25298
26375
|
onboarded: settings.onboardedAt != null,
|
|
25299
|
-
provider: resolveProviderSafe()
|
|
26376
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
25300
26377
|
};
|
|
25301
26378
|
}
|
|
25302
|
-
function resolveProviderSafe() {
|
|
26379
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
25303
26380
|
try {
|
|
25304
|
-
return
|
|
26381
|
+
return resolveProviderFn();
|
|
25305
26382
|
} catch {
|
|
25306
26383
|
return { provider: "anthropic" };
|
|
25307
26384
|
}
|
|
25308
26385
|
}
|
|
25309
26386
|
|
|
25310
26387
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
25311
|
-
import { readdirSync, readFileSync as
|
|
26388
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
25312
26389
|
import { homedir as homedir2 } from "os";
|
|
25313
|
-
import { basename as
|
|
26390
|
+
import { basename as basename3, join as join10 } from "path";
|
|
25314
26391
|
|
|
25315
26392
|
// ../../packages/detections/src/egress/registry.ts
|
|
25316
26393
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -27967,7 +29044,7 @@ var gcp_service_account_default = {
|
|
|
27967
29044
|
severity: "critical",
|
|
27968
29045
|
matcher: {
|
|
27969
29046
|
type: "regex",
|
|
27970
|
-
pattern: "
|
|
29047
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27971
29048
|
flags: "g"
|
|
27972
29049
|
},
|
|
27973
29050
|
examples: [
|
|
@@ -28346,15 +29423,15 @@ function bundledDetections() {
|
|
|
28346
29423
|
}
|
|
28347
29424
|
|
|
28348
29425
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
28349
|
-
import { existsSync as
|
|
28350
|
-
import { basename, dirname, isAbsolute, join as
|
|
29426
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29427
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
28351
29428
|
function resolveRepo(cwd) {
|
|
28352
29429
|
try {
|
|
28353
29430
|
const root = findGitRoot(cwd);
|
|
28354
29431
|
if (!root) return void 0;
|
|
28355
29432
|
const ctx = resolveGitContext(root);
|
|
28356
29433
|
const url2 = ctx ? remoteUrl(ctx) : void 0;
|
|
28357
|
-
return (url2 ? slugFromUrl(url2) : void 0) ??
|
|
29434
|
+
return (url2 ? slugFromUrl(url2) : void 0) ?? basename2(ctx?.headRoot ?? root);
|
|
28358
29435
|
} catch {
|
|
28359
29436
|
return void 0;
|
|
28360
29437
|
}
|
|
@@ -28369,36 +29446,36 @@ function resolveWorktreeRoot(cwd) {
|
|
|
28369
29446
|
function findGitRoot(start) {
|
|
28370
29447
|
let dir = start;
|
|
28371
29448
|
for (; ; ) {
|
|
28372
|
-
if (
|
|
28373
|
-
const parent =
|
|
29449
|
+
if (existsSync6(join9(dir, ".git"))) return dir;
|
|
29450
|
+
const parent = dirname2(dir);
|
|
28374
29451
|
if (parent === dir) return void 0;
|
|
28375
29452
|
dir = parent;
|
|
28376
29453
|
}
|
|
28377
29454
|
}
|
|
28378
29455
|
function resolveGitContext(root) {
|
|
28379
|
-
const dotGit =
|
|
29456
|
+
const dotGit = join9(root, ".git");
|
|
28380
29457
|
try {
|
|
28381
|
-
if (
|
|
28382
|
-
return { configPath:
|
|
29458
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
29459
|
+
return { configPath: join9(dotGit, "config"), headRoot: root };
|
|
28383
29460
|
}
|
|
28384
29461
|
} catch {
|
|
28385
29462
|
return void 0;
|
|
28386
29463
|
}
|
|
28387
29464
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
28388
29465
|
if (!target) return void 0;
|
|
28389
|
-
const gitdir = isAbsolute(target) ? target :
|
|
28390
|
-
if (
|
|
28391
|
-
return { configPath:
|
|
29466
|
+
const gitdir = isAbsolute(target) ? target : join9(root, target);
|
|
29467
|
+
if (existsSync6(join9(gitdir, "config"))) {
|
|
29468
|
+
return { configPath: join9(gitdir, "config"), headRoot: root };
|
|
28392
29469
|
}
|
|
28393
|
-
const commonRaw = safeRead(
|
|
29470
|
+
const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
|
|
28394
29471
|
if (!commonRaw) return void 0;
|
|
28395
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
28396
|
-
const headRoot =
|
|
28397
|
-
return { configPath:
|
|
29472
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
|
|
29473
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
|
|
29474
|
+
return { configPath: join9(commonGitDir, "config"), headRoot };
|
|
28398
29475
|
}
|
|
28399
29476
|
function safeRead(path) {
|
|
28400
29477
|
try {
|
|
28401
|
-
return
|
|
29478
|
+
return readFileSync5(path, "utf8");
|
|
28402
29479
|
} catch {
|
|
28403
29480
|
return void 0;
|
|
28404
29481
|
}
|
|
@@ -28436,13 +29513,13 @@ function slugFromUrl(url2) {
|
|
|
28436
29513
|
}
|
|
28437
29514
|
|
|
28438
29515
|
// ../../packages/plugin-sdk/src/events.ts
|
|
28439
|
-
import { createHash as createHash4, randomUUID as
|
|
29516
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28440
29517
|
function contentHashOf(text) {
|
|
28441
29518
|
return createHash4("sha256").update(text).digest("hex");
|
|
28442
29519
|
}
|
|
28443
29520
|
function buildIngestEvent(input) {
|
|
28444
29521
|
return {
|
|
28445
|
-
id:
|
|
29522
|
+
id: randomUUID13(),
|
|
28446
29523
|
sourceTool: input.sourceTool,
|
|
28447
29524
|
kind: input.kind,
|
|
28448
29525
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -28453,102 +29530,527 @@ function buildIngestEvent(input) {
|
|
|
28453
29530
|
// SDK boot in the fail-open hook path). Preserve any id the caller already set.
|
|
28454
29531
|
metadata: {
|
|
28455
29532
|
...input.metadata,
|
|
28456
|
-
correlationId: input.metadata?.correlationId ??
|
|
29533
|
+
correlationId: input.metadata?.correlationId ?? randomUUID13()
|
|
28457
29534
|
}
|
|
28458
29535
|
};
|
|
28459
29536
|
}
|
|
28460
29537
|
|
|
28461
|
-
// ../../packages/plugin-sdk/src/
|
|
28462
|
-
import {
|
|
28463
|
-
|
|
28464
|
-
|
|
28465
|
-
|
|
28466
|
-
|
|
28467
|
-
|
|
28468
|
-
|
|
28469
|
-
|
|
28470
|
-
|
|
28471
|
-
|
|
28472
|
-
|
|
28473
|
-
|
|
28474
|
-
|
|
28475
|
-
|
|
28476
|
-
|
|
28477
|
-
|
|
28478
|
-
|
|
29538
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29539
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29540
|
+
import { fileURLToPath } from "url";
|
|
29541
|
+
import { Worker } from "worker_threads";
|
|
29542
|
+
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
29543
|
+
var ISOLATED_PROBE_BUDGET_MS = 1e3;
|
|
29544
|
+
var ISOLATED_START_BUDGET_MS = 5e3;
|
|
29545
|
+
var ATTRIBUTION_MIN_RULE_MS = 500;
|
|
29546
|
+
var ATTRIBUTION_MIN_SHARE = 0.5;
|
|
29547
|
+
var resolvedWorkerUrl;
|
|
29548
|
+
function resolveWorkerUrl() {
|
|
29549
|
+
if (resolvedWorkerUrl !== void 0) return resolvedWorkerUrl ?? void 0;
|
|
29550
|
+
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
29551
|
+
const candidate = new URL(name, import.meta.url);
|
|
29552
|
+
try {
|
|
29553
|
+
if (existsSync7(fileURLToPath(candidate))) {
|
|
29554
|
+
resolvedWorkerUrl = candidate;
|
|
29555
|
+
return candidate;
|
|
29556
|
+
}
|
|
29557
|
+
} catch {
|
|
29558
|
+
}
|
|
28479
29559
|
}
|
|
29560
|
+
resolvedWorkerUrl = null;
|
|
29561
|
+
return void 0;
|
|
28480
29562
|
}
|
|
28481
|
-
|
|
28482
|
-
|
|
28483
|
-
|
|
28484
|
-
|
|
28485
|
-
|
|
28486
|
-
|
|
28487
|
-
|
|
28488
|
-
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
28492
|
-
|
|
29563
|
+
function messageOf(error51) {
|
|
29564
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
29565
|
+
}
|
|
29566
|
+
function createIsolatedScanner(data, opts = {}) {
|
|
29567
|
+
const budgetMs = opts.budgetMs ?? ISOLATED_SCAN_BUDGET_MS;
|
|
29568
|
+
const probeBudgetMs = opts.probeBudgetMs ?? ISOLATED_PROBE_BUDGET_MS;
|
|
29569
|
+
const startBudgetMs = opts.startBudgetMs ?? ISOLATED_START_BUDGET_MS;
|
|
29570
|
+
const minAttributionMs = opts.minAttributionMs ?? ATTRIBUTION_MIN_RULE_MS;
|
|
29571
|
+
let worker;
|
|
29572
|
+
let readyWorker;
|
|
29573
|
+
let broken;
|
|
29574
|
+
let closed = false;
|
|
29575
|
+
let nextJobId = 1;
|
|
29576
|
+
let pending;
|
|
29577
|
+
const terminating = /* @__PURE__ */ new Set();
|
|
29578
|
+
let chain = Promise.resolve();
|
|
29579
|
+
function clearTimers(job) {
|
|
29580
|
+
if (job.startupTimer !== void 0) clearTimeout(job.startupTimer);
|
|
29581
|
+
if (job.timer !== void 0) clearTimeout(job.timer);
|
|
29582
|
+
}
|
|
29583
|
+
function take() {
|
|
29584
|
+
const job = pending;
|
|
29585
|
+
if (!job) return void 0;
|
|
29586
|
+
pending = void 0;
|
|
29587
|
+
clearTimers(job);
|
|
29588
|
+
worker?.unref();
|
|
29589
|
+
return job;
|
|
29590
|
+
}
|
|
29591
|
+
function failPending(outcome) {
|
|
29592
|
+
take()?.fail(outcome);
|
|
29593
|
+
}
|
|
29594
|
+
function kill(dead) {
|
|
29595
|
+
if (worker === dead) worker = void 0;
|
|
29596
|
+
if (readyWorker === dead) readyWorker = void 0;
|
|
29597
|
+
const done = dead.terminate().catch(() => void 0);
|
|
29598
|
+
terminating.add(done);
|
|
29599
|
+
void done.finally(() => terminating.delete(done));
|
|
29600
|
+
}
|
|
29601
|
+
function onDeadline(job) {
|
|
29602
|
+
if (pending !== job) return;
|
|
29603
|
+
const now = performance.now();
|
|
29604
|
+
const runningMs = now - job.progressAt;
|
|
29605
|
+
const elapsedMs = now - job.startedAt;
|
|
29606
|
+
const blamed = job.progressIndex >= 0 && runningMs >= minAttributionMs && runningMs >= elapsedMs * ATTRIBUTION_MIN_SHARE;
|
|
29607
|
+
const culpritIndex = blamed ? job.progressIndex : void 0;
|
|
29608
|
+
kill(job.worker);
|
|
29609
|
+
failPending({ status: "timeout", culpritIndex, elapsedMs });
|
|
29610
|
+
}
|
|
29611
|
+
function ensureWorker() {
|
|
29612
|
+
if (worker) return worker;
|
|
29613
|
+
const url2 = opts.workerUrl ?? resolveWorkerUrl();
|
|
29614
|
+
if (!url2) {
|
|
29615
|
+
return {
|
|
29616
|
+
error: "the scan worker script was not found next to this bundle"
|
|
29617
|
+
};
|
|
29618
|
+
}
|
|
29619
|
+
let started;
|
|
29620
|
+
try {
|
|
29621
|
+
started = new Worker(url2, { workerData: data });
|
|
29622
|
+
} catch (error51) {
|
|
29623
|
+
return { error: `could not start the scan worker: ${messageOf(error51)}` };
|
|
29624
|
+
}
|
|
29625
|
+
opts.onWorkerStart?.(started.threadId);
|
|
29626
|
+
started.on("message", (message) => {
|
|
29627
|
+
if (worker !== started) return;
|
|
29628
|
+
if (message.kind === "ready") {
|
|
29629
|
+
readyWorker = started;
|
|
29630
|
+
if (pending?.worker === started) beginDeadline(pending);
|
|
29631
|
+
return;
|
|
29632
|
+
}
|
|
29633
|
+
if (message.kind === "progress") {
|
|
29634
|
+
if (pending?.worker === started) {
|
|
29635
|
+
pending.progressIndex = message.index;
|
|
29636
|
+
pending.progressAt = performance.now();
|
|
29637
|
+
}
|
|
29638
|
+
return;
|
|
29639
|
+
}
|
|
29640
|
+
if (pending?.id !== message.id) return;
|
|
29641
|
+
if (message.kind === "failed") {
|
|
29642
|
+
failPending({
|
|
29643
|
+
status: "unavailable",
|
|
29644
|
+
reason: `the scan worker failed: ${message.message}`
|
|
29645
|
+
});
|
|
29646
|
+
return;
|
|
29647
|
+
}
|
|
29648
|
+
const job = take();
|
|
29649
|
+
if (job && !job.reply(message)) {
|
|
29650
|
+
job.fail({ status: "unavailable", reason: "the scan worker answered the wrong job" });
|
|
29651
|
+
}
|
|
29652
|
+
});
|
|
29653
|
+
started.on("error", (error51) => {
|
|
29654
|
+
if (worker !== started) return;
|
|
29655
|
+
broken = messageOf(error51);
|
|
29656
|
+
worker = void 0;
|
|
29657
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29658
|
+
failPending({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29659
|
+
});
|
|
29660
|
+
started.on("exit", () => {
|
|
29661
|
+
if (worker !== started) return;
|
|
29662
|
+
broken ??= "the scan worker exited before answering";
|
|
29663
|
+
worker = void 0;
|
|
29664
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29665
|
+
failPending({ status: "unavailable", reason: "the scan worker exited before answering" });
|
|
29666
|
+
});
|
|
29667
|
+
started.unref();
|
|
29668
|
+
worker = started;
|
|
29669
|
+
return started;
|
|
29670
|
+
}
|
|
29671
|
+
function beginDeadline(job) {
|
|
29672
|
+
if (job.startupTimer !== void 0) {
|
|
29673
|
+
clearTimeout(job.startupTimer);
|
|
29674
|
+
job.startupTimer = void 0;
|
|
29675
|
+
}
|
|
29676
|
+
if (job.timer !== void 0) return;
|
|
29677
|
+
job.startedAt = performance.now();
|
|
29678
|
+
job.progressAt = job.startedAt;
|
|
29679
|
+
job.timer = setTimeout(() => {
|
|
29680
|
+
onDeadline(job);
|
|
29681
|
+
}, job.budgetMs);
|
|
29682
|
+
}
|
|
29683
|
+
function runOne(spec, fail2) {
|
|
29684
|
+
if (closed) {
|
|
29685
|
+
fail2({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29686
|
+
return;
|
|
29687
|
+
}
|
|
29688
|
+
if (broken !== void 0) {
|
|
29689
|
+
fail2({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29690
|
+
return;
|
|
29691
|
+
}
|
|
29692
|
+
const started = ensureWorker();
|
|
29693
|
+
if (!(started instanceof Worker)) {
|
|
29694
|
+
broken = started.error;
|
|
29695
|
+
fail2({ status: "unavailable", reason: started.error });
|
|
29696
|
+
return;
|
|
29697
|
+
}
|
|
29698
|
+
const id = nextJobId++;
|
|
29699
|
+
const now = performance.now();
|
|
29700
|
+
const job = {
|
|
29701
|
+
id,
|
|
29702
|
+
worker: started,
|
|
29703
|
+
budgetMs: spec.budgetMs,
|
|
29704
|
+
startedAt: now,
|
|
29705
|
+
progressIndex: -1,
|
|
29706
|
+
progressAt: now,
|
|
29707
|
+
startupTimer: void 0,
|
|
29708
|
+
timer: void 0,
|
|
29709
|
+
reply: spec.reply,
|
|
29710
|
+
fail: fail2
|
|
29711
|
+
};
|
|
29712
|
+
pending = job;
|
|
29713
|
+
started.ref();
|
|
29714
|
+
if (readyWorker === started) {
|
|
29715
|
+
beginDeadline(job);
|
|
29716
|
+
} else {
|
|
29717
|
+
job.startupTimer = setTimeout(() => {
|
|
29718
|
+
if (pending !== job) return;
|
|
29719
|
+
kill(job.worker);
|
|
29720
|
+
failPending({
|
|
29721
|
+
status: "unavailable",
|
|
29722
|
+
reason: `the scan worker did not start within ${String(startBudgetMs)}ms`
|
|
29723
|
+
});
|
|
29724
|
+
}, startBudgetMs);
|
|
29725
|
+
}
|
|
29726
|
+
try {
|
|
29727
|
+
started.postMessage(spec.build(id));
|
|
29728
|
+
} catch (error51) {
|
|
29729
|
+
failPending({
|
|
29730
|
+
// The thread went away between the ref and the post.
|
|
29731
|
+
status: "unavailable",
|
|
29732
|
+
reason: `could not reach the scan worker: ${messageOf(error51)}`
|
|
29733
|
+
});
|
|
29734
|
+
}
|
|
28493
29735
|
}
|
|
28494
|
-
|
|
29736
|
+
function enqueue(spec) {
|
|
29737
|
+
const next = chain.then(
|
|
29738
|
+
() => new Promise((resolve2) => {
|
|
29739
|
+
spec(resolve2);
|
|
29740
|
+
})
|
|
29741
|
+
);
|
|
29742
|
+
chain = next.then(
|
|
29743
|
+
() => void 0,
|
|
29744
|
+
() => void 0
|
|
29745
|
+
);
|
|
29746
|
+
return next;
|
|
29747
|
+
}
|
|
29748
|
+
return {
|
|
29749
|
+
scan(text, context, scanOpts) {
|
|
29750
|
+
return enqueue((resolve2) => {
|
|
29751
|
+
runOne(
|
|
29752
|
+
{
|
|
29753
|
+
budgetMs,
|
|
29754
|
+
build: (id) => ({
|
|
29755
|
+
kind: "scan",
|
|
29756
|
+
id,
|
|
29757
|
+
text,
|
|
29758
|
+
filePath: context?.filePath,
|
|
29759
|
+
attribute: scanOpts?.attribute === true
|
|
29760
|
+
}),
|
|
29761
|
+
reply: (message) => {
|
|
29762
|
+
if (message.kind !== "result") return false;
|
|
29763
|
+
resolve2({ status: "ok", findings: message.findings });
|
|
29764
|
+
return true;
|
|
29765
|
+
}
|
|
29766
|
+
},
|
|
29767
|
+
resolve2
|
|
29768
|
+
);
|
|
29769
|
+
});
|
|
29770
|
+
},
|
|
29771
|
+
probe(rule) {
|
|
29772
|
+
return enqueue((resolve2) => {
|
|
29773
|
+
runOne(
|
|
29774
|
+
{
|
|
29775
|
+
budgetMs: probeBudgetMs,
|
|
29776
|
+
build: (id) => ({ kind: "probe", id, rule }),
|
|
29777
|
+
reply: (message) => {
|
|
29778
|
+
if (message.kind !== "probed") return false;
|
|
29779
|
+
resolve2({ status: "ok", safe: message.safe, worstMs: message.worstMs });
|
|
29780
|
+
return true;
|
|
29781
|
+
}
|
|
29782
|
+
},
|
|
29783
|
+
resolve2
|
|
29784
|
+
);
|
|
29785
|
+
});
|
|
29786
|
+
},
|
|
29787
|
+
async close() {
|
|
29788
|
+
closed = true;
|
|
29789
|
+
const live = worker;
|
|
29790
|
+
worker = void 0;
|
|
29791
|
+
readyWorker = void 0;
|
|
29792
|
+
failPending({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29793
|
+
if (live) kill(live);
|
|
29794
|
+
await Promise.all([...terminating]);
|
|
29795
|
+
}
|
|
29796
|
+
};
|
|
28495
29797
|
}
|
|
28496
29798
|
|
|
28497
29799
|
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
28498
29800
|
var PASS_BUDGET_MS = 2e3;
|
|
29801
|
+
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
28499
29802
|
function ruleProbeKey(rule) {
|
|
28500
29803
|
if (rule.matcher.type !== "regex") return void 0;
|
|
28501
29804
|
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
28502
29805
|
}
|
|
28503
|
-
function
|
|
28504
|
-
const
|
|
29806
|
+
function warn(rule, verb, detail, recoverable) {
|
|
29807
|
+
const hint = recoverable ? ` (${UNQUARANTINE_HINT})` : "";
|
|
29808
|
+
process.stderr.write(`[aka] ${verb} rule "${rule.id}": ${detail}${hint}
|
|
29809
|
+
`);
|
|
29810
|
+
}
|
|
29811
|
+
function warnQuarantined(rule, worstMs, cached2) {
|
|
29812
|
+
warn(
|
|
29813
|
+
rule,
|
|
29814
|
+
"quarantined",
|
|
29815
|
+
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.",
|
|
29816
|
+
cached2
|
|
29817
|
+
);
|
|
29818
|
+
}
|
|
29819
|
+
function warnUnmeasured(rule) {
|
|
29820
|
+
warn(
|
|
29821
|
+
rule,
|
|
29822
|
+
"skipped",
|
|
29823
|
+
"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.",
|
|
29824
|
+
false
|
|
29825
|
+
);
|
|
29826
|
+
}
|
|
29827
|
+
function warnUnmeasurable(reason, count) {
|
|
28505
29828
|
process.stderr.write(
|
|
28506
|
-
`[aka]
|
|
29829
|
+
`[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.
|
|
28507
29830
|
`
|
|
28508
29831
|
);
|
|
28509
29832
|
}
|
|
29833
|
+
async function quarantineRule(gateway, rule, worstMs, detail) {
|
|
29834
|
+
const key = ruleProbeKey(rule);
|
|
29835
|
+
let cached2 = false;
|
|
29836
|
+
if (key !== void 0) {
|
|
29837
|
+
try {
|
|
29838
|
+
await gateway.setRuleProbeVerdict(key, "quarantined", worstMs);
|
|
29839
|
+
cached2 = true;
|
|
29840
|
+
} catch {
|
|
29841
|
+
}
|
|
29842
|
+
}
|
|
29843
|
+
warn(rule, "quarantined", detail, cached2);
|
|
29844
|
+
}
|
|
28510
29845
|
async function filterUnsafeRules(rules, gateway, opts) {
|
|
28511
29846
|
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
29847
|
+
const prober = opts?.prober;
|
|
28512
29848
|
const passStart = performance.now();
|
|
28513
29849
|
const safe = [];
|
|
28514
|
-
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
|
|
28518
|
-
|
|
29850
|
+
const unmeasurable = /* @__PURE__ */ new Map();
|
|
29851
|
+
try {
|
|
29852
|
+
for (const rule of rules) {
|
|
29853
|
+
const key = ruleProbeKey(rule);
|
|
29854
|
+
if (key === void 0) {
|
|
29855
|
+
safe.push(rule);
|
|
29856
|
+
continue;
|
|
29857
|
+
}
|
|
29858
|
+
let cached2;
|
|
29859
|
+
try {
|
|
29860
|
+
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
29861
|
+
} catch {
|
|
29862
|
+
cached2 = void 0;
|
|
29863
|
+
}
|
|
29864
|
+
if (cached2) {
|
|
29865
|
+
if (cached2.verdict === "safe") safe.push(rule);
|
|
29866
|
+
else warnQuarantined(rule, cached2.worstProbeMs, true);
|
|
29867
|
+
continue;
|
|
29868
|
+
}
|
|
29869
|
+
if (performance.now() - passStart >= passBudgetMs) {
|
|
29870
|
+
warnUnmeasured(rule);
|
|
29871
|
+
continue;
|
|
29872
|
+
}
|
|
29873
|
+
let isSafe;
|
|
29874
|
+
let worstMs;
|
|
29875
|
+
if (prober) {
|
|
29876
|
+
const outcome = await prober.probe(rule);
|
|
29877
|
+
if (outcome.status === "unavailable") {
|
|
29878
|
+
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29879
|
+
continue;
|
|
29880
|
+
}
|
|
29881
|
+
isSafe = outcome.status === "ok" ? outcome.safe : false;
|
|
29882
|
+
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
29883
|
+
} else {
|
|
29884
|
+
try {
|
|
29885
|
+
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
29886
|
+
} catch {
|
|
29887
|
+
isSafe = false;
|
|
29888
|
+
worstMs = Number.POSITIVE_INFINITY;
|
|
29889
|
+
}
|
|
29890
|
+
}
|
|
29891
|
+
let persisted = false;
|
|
29892
|
+
try {
|
|
29893
|
+
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
29894
|
+
persisted = true;
|
|
29895
|
+
} catch {
|
|
29896
|
+
}
|
|
29897
|
+
if (isSafe) safe.push(rule);
|
|
29898
|
+
else warnQuarantined(rule, worstMs, persisted);
|
|
28519
29899
|
}
|
|
28520
|
-
|
|
29900
|
+
} finally {
|
|
29901
|
+
for (const [reason, count] of unmeasurable) warnUnmeasurable(reason, count);
|
|
29902
|
+
}
|
|
29903
|
+
return safe;
|
|
29904
|
+
}
|
|
29905
|
+
|
|
29906
|
+
// ../../packages/plugin-sdk/src/guarded-scan.ts
|
|
29907
|
+
var DEFAULT_DEGRADE_SCOPE = "the rest of this process";
|
|
29908
|
+
function warnDegraded(scope, dropped, detail) {
|
|
29909
|
+
process.stderr.write(
|
|
29910
|
+
`[aka] isolated scanning is off for ${scope}: ${detail}. ${String(dropped)} pulled/custom-pack rule(s) are excluded; the built-in packs still run.
|
|
29911
|
+
`
|
|
29912
|
+
);
|
|
29913
|
+
}
|
|
29914
|
+
function createGuardedScanner(partition, gateway, opts) {
|
|
29915
|
+
const degradeScope = opts?.degradeScope ?? DEFAULT_DEGRADE_SCOPE;
|
|
29916
|
+
const verified = partition.verified;
|
|
29917
|
+
let unverified = partition.unverified;
|
|
29918
|
+
let isolated = unverified.length > 0 ? createIsolatedScanner({ verified, unverified }, opts) : void 0;
|
|
29919
|
+
let retired = false;
|
|
29920
|
+
function inProcess(text, context) {
|
|
29921
|
+
return scan(text, verified, context);
|
|
29922
|
+
}
|
|
29923
|
+
async function retire() {
|
|
29924
|
+
const live = isolated;
|
|
29925
|
+
isolated = void 0;
|
|
29926
|
+
unverified = [];
|
|
29927
|
+
if (live) await live.close();
|
|
29928
|
+
}
|
|
29929
|
+
async function degrade() {
|
|
29930
|
+
retired = true;
|
|
29931
|
+
await retire();
|
|
29932
|
+
}
|
|
29933
|
+
async function attempt(active, text, context, attribute) {
|
|
28521
29934
|
try {
|
|
28522
|
-
|
|
28523
|
-
} catch {
|
|
28524
|
-
|
|
28525
|
-
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
else warnQuarantined(rule, cached2.worstProbeMs);
|
|
28529
|
-
continue;
|
|
29935
|
+
return await active.scan(text, context, { attribute });
|
|
29936
|
+
} catch (error51) {
|
|
29937
|
+
return {
|
|
29938
|
+
status: "unavailable",
|
|
29939
|
+
reason: error51 instanceof Error ? error51.message : "the scan worker failed unexpectedly"
|
|
29940
|
+
};
|
|
28530
29941
|
}
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
29942
|
+
}
|
|
29943
|
+
async function guardedScan(text, context) {
|
|
29944
|
+
const active = isolated;
|
|
29945
|
+
if (!active) return inProcess(text, context);
|
|
29946
|
+
let outcome = await attempt(active, text, context, false);
|
|
29947
|
+
if (outcome.status === "ok") return outcome.findings;
|
|
29948
|
+
if (outcome.status === "timeout") outcome = await attempt(active, text, context, true);
|
|
29949
|
+
const dropped = unverified.length;
|
|
29950
|
+
if (outcome.status === "ok") {
|
|
29951
|
+
warnDegraded(
|
|
29952
|
+
degradeScope,
|
|
29953
|
+
dropped,
|
|
29954
|
+
"a scan overran its bound once and no rule could be held responsible"
|
|
29955
|
+
);
|
|
29956
|
+
const findings = outcome.findings;
|
|
29957
|
+
await degrade();
|
|
29958
|
+
return findings;
|
|
29959
|
+
}
|
|
29960
|
+
if (outcome.status === "timeout") {
|
|
29961
|
+
const culprit = outcome.culpritIndex === void 0 ? void 0 : unverified[outcome.culpritIndex];
|
|
29962
|
+
if (culprit) {
|
|
29963
|
+
await quarantineRule(
|
|
29964
|
+
gateway,
|
|
29965
|
+
culprit,
|
|
29966
|
+
outcome.elapsedMs,
|
|
29967
|
+
`it did not finish within the ${outcome.elapsedMs.toFixed(0)}ms isolated-scan bound and was terminated; excluded from every later scan.`
|
|
29968
|
+
);
|
|
29969
|
+
}
|
|
29970
|
+
warnDegraded(
|
|
29971
|
+
degradeScope,
|
|
29972
|
+
dropped,
|
|
29973
|
+
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`
|
|
29974
|
+
);
|
|
29975
|
+
} else {
|
|
29976
|
+
warnDegraded(degradeScope, dropped, outcome.reason);
|
|
28534
29977
|
}
|
|
28535
|
-
|
|
28536
|
-
|
|
28537
|
-
|
|
28538
|
-
|
|
28539
|
-
|
|
28540
|
-
|
|
28541
|
-
|
|
29978
|
+
await degrade();
|
|
29979
|
+
return inProcess(text, context);
|
|
29980
|
+
}
|
|
29981
|
+
return {
|
|
29982
|
+
scan: guardedScan,
|
|
29983
|
+
degraded: () => retired,
|
|
29984
|
+
async close() {
|
|
29985
|
+
await retire();
|
|
28542
29986
|
}
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
|
|
29987
|
+
};
|
|
29988
|
+
}
|
|
29989
|
+
|
|
29990
|
+
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
29991
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
29992
|
+
|
|
29993
|
+
// ../../packages/plugin-sdk/src/nudge.ts
|
|
29994
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
29995
|
+
import { join as join11 } from "path";
|
|
29996
|
+
|
|
29997
|
+
// ../../packages/plugin-sdk/src/paths.ts
|
|
29998
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
29999
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
30000
|
+
|
|
30001
|
+
// ../../packages/plugin-sdk/src/posture.ts
|
|
30002
|
+
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
30003
|
+
for (const category of Object.keys(posture)) {
|
|
30004
|
+
const policyId = posture[category];
|
|
30005
|
+
if (!policyId) continue;
|
|
30006
|
+
if (mode === "fill-gaps" && repo.getCategoryAction(category) !== void 0) continue;
|
|
30007
|
+
repo.upsertCategoryAction(category, builtinPolicyToAction(policyId));
|
|
28546
30008
|
}
|
|
28547
|
-
|
|
30009
|
+
}
|
|
30010
|
+
|
|
30011
|
+
// ../../packages/plugin-sdk/src/project-files.ts
|
|
30012
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
30013
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
30014
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
30015
|
+
|
|
30016
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30017
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
30018
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30019
|
+
return v;
|
|
30020
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30021
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
30022
|
+
if (typeof v !== "string") return false;
|
|
30023
|
+
const normalized = v.trim().toLowerCase();
|
|
30024
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
30025
|
+
}, external_exports.boolean()).catch(false);
|
|
30026
|
+
var antigravityProviderEnvShape = {
|
|
30027
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
30028
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
30029
|
+
};
|
|
30030
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
30031
|
+
|
|
30032
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
30033
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
30034
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30035
|
+
return v;
|
|
30036
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30037
|
+
var codexProviderEnvShape = {
|
|
30038
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
30039
|
+
};
|
|
30040
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
30041
|
+
|
|
30042
|
+
// ../../packages/plugin-sdk/src/raw-egress.ts
|
|
30043
|
+
var MIN_RAW_LEN = 4;
|
|
30044
|
+
function safeMaskedMatch(rawMatch) {
|
|
30045
|
+
const masked = maskMatch(rawMatch);
|
|
30046
|
+
if (masked === rawMatch || rawMatch.length >= MIN_RAW_LEN && masked.includes(rawMatch)) {
|
|
30047
|
+
return "***";
|
|
30048
|
+
}
|
|
30049
|
+
return masked;
|
|
28548
30050
|
}
|
|
28549
30051
|
|
|
28550
30052
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28551
|
-
import { randomUUID as
|
|
30053
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28552
30054
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
28553
30055
|
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
28554
30056
|
function entryIsActive(entry, now) {
|
|
@@ -28573,6 +30075,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28573
30075
|
const dataDir2 = opts?.dataDir;
|
|
28574
30076
|
let policies = [];
|
|
28575
30077
|
let rules = [];
|
|
30078
|
+
let scanner;
|
|
28576
30079
|
let bundleExceptions = [];
|
|
28577
30080
|
let initialized = false;
|
|
28578
30081
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
@@ -28598,8 +30101,24 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28598
30101
|
return key !== void 0 && bundledProbeKeys.has(key);
|
|
28599
30102
|
});
|
|
28600
30103
|
const needsGate = incoming.filter((rule) => !ciVerified.includes(rule));
|
|
28601
|
-
|
|
28602
|
-
|
|
30104
|
+
let prober;
|
|
30105
|
+
const gated = await filterUnsafeRules(needsGate, gateway, {
|
|
30106
|
+
prober: {
|
|
30107
|
+
probe: (rule) => {
|
|
30108
|
+
prober ??= createIsolatedScanner({ verified: [], unverified: [] }, opts?.scanIsolation);
|
|
30109
|
+
return prober.probe(rule);
|
|
30110
|
+
}
|
|
30111
|
+
}
|
|
30112
|
+
});
|
|
30113
|
+
await prober?.close();
|
|
30114
|
+
const verified = bundle.rulesComplete ? [...ciVerified] : [...getLoadedRules(), ...ciVerified];
|
|
30115
|
+
const unverified = [];
|
|
30116
|
+
for (const rule of gated) {
|
|
30117
|
+
if (rule.matcher.type === "regex") unverified.push(rule);
|
|
30118
|
+
else verified.push(rule);
|
|
30119
|
+
}
|
|
30120
|
+
rules = [...verified, ...unverified];
|
|
30121
|
+
scanner = createGuardedScanner({ verified, unverified }, gateway, opts?.scanIsolation);
|
|
28603
30122
|
bundleExceptions = bundle.exceptions ?? [];
|
|
28604
30123
|
initialized = true;
|
|
28605
30124
|
}
|
|
@@ -28739,7 +30258,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28739
30258
|
const pair = `${finding.ruleId}:${fp}`;
|
|
28740
30259
|
if (seen.has(pair)) continue;
|
|
28741
30260
|
seen.add(pair);
|
|
28742
|
-
const reference =
|
|
30261
|
+
const reference = randomUUID14().replaceAll("-", "").slice(0, 6);
|
|
28743
30262
|
const maskedValue = maskMatch(finding.rawMatch);
|
|
28744
30263
|
try {
|
|
28745
30264
|
await gateway.recordBlockedDetection({
|
|
@@ -28763,8 +30282,10 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28763
30282
|
async function evaluate(text, context, ctx) {
|
|
28764
30283
|
try {
|
|
28765
30284
|
await ensureInitialized();
|
|
30285
|
+
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
28766
30286
|
const shielded = shieldPointers(text);
|
|
28767
|
-
const
|
|
30287
|
+
const matched = await scanner.scan(shielded.text, context);
|
|
30288
|
+
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
28768
30289
|
const fpCache = /* @__PURE__ */ new Map();
|
|
28769
30290
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
28770
30291
|
const decision = decide(findings, text, excepted);
|
|
@@ -28821,7 +30342,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28821
30342
|
valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
|
|
28822
30343
|
}) : void 0;
|
|
28823
30344
|
return {
|
|
28824
|
-
id:
|
|
30345
|
+
id: randomUUID14(),
|
|
28825
30346
|
eventId: event.id,
|
|
28826
30347
|
ruleId: match.ruleId,
|
|
28827
30348
|
category: match.category,
|
|
@@ -28847,21 +30368,28 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28847
30368
|
const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
28848
30369
|
return contentHashOf(JSON.stringify(sorted));
|
|
28849
30370
|
} catch {
|
|
28850
|
-
return `unresolved-${
|
|
30371
|
+
return `unresolved-${randomUUID14()}`;
|
|
28851
30372
|
}
|
|
28852
30373
|
}
|
|
30374
|
+
function scanIsolationDegraded() {
|
|
30375
|
+
return scanner?.degraded() ?? false;
|
|
30376
|
+
}
|
|
28853
30377
|
async function close() {
|
|
30378
|
+
try {
|
|
30379
|
+
await scanner?.close();
|
|
30380
|
+
} catch {
|
|
30381
|
+
}
|
|
28854
30382
|
await gateway.close();
|
|
28855
30383
|
}
|
|
28856
|
-
return { processText, capture, rulesetFingerprint, close };
|
|
30384
|
+
return { processText, capture, rulesetFingerprint, scanIsolationDegraded, close };
|
|
28857
30385
|
}
|
|
28858
30386
|
|
|
28859
30387
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28860
30388
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28861
30389
|
|
|
28862
30390
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28863
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
28864
|
-
import { join as
|
|
30391
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
30392
|
+
import { join as join13 } from "path";
|
|
28865
30393
|
|
|
28866
30394
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
28867
30395
|
function redactedPlaceholder(category) {
|
|
@@ -29103,7 +30631,6 @@ function createVaultGlue(options) {
|
|
|
29103
30631
|
const vault = new SecretVault({
|
|
29104
30632
|
repo: db.secretVault,
|
|
29105
30633
|
keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
|
|
29106
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
29107
30634
|
// Read live so a revocation applies to the very next call, not the next
|
|
29108
30635
|
// process.
|
|
29109
30636
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
|
|
@@ -29124,8 +30651,10 @@ function createVaultGlue(options) {
|
|
|
29124
30651
|
return decision.allow;
|
|
29125
30652
|
}
|
|
29126
30653
|
});
|
|
30654
|
+
let fingerprintKey;
|
|
30655
|
+
const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
|
|
29127
30656
|
const vaultWithSightings = {
|
|
29128
|
-
tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
|
|
30657
|
+
tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
|
|
29129
30658
|
detokenize: (token, opts) => vault.detokenize(token, opts),
|
|
29130
30659
|
describePointer: (token) => vault.describePointer(token),
|
|
29131
30660
|
resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
|
|
@@ -29158,132 +30687,7 @@ var UNOPENABLE_VAULT = {
|
|
|
29158
30687
|
resolvePointerIdentity: () => Promise.resolve(null)
|
|
29159
30688
|
};
|
|
29160
30689
|
|
|
29161
|
-
// src/
|
|
29162
|
-
import { readdirSync as readdirSync4 } from "fs";
|
|
29163
|
-
import { fileURLToPath } from "url";
|
|
29164
|
-
var COMMAND_NAMESPACE = "aka";
|
|
29165
|
-
var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
|
|
29166
|
-
function readRegisteredCommands() {
|
|
29167
|
-
return readdirSync4(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
29168
|
-
}
|
|
29169
|
-
function selectRegisteredCommands(curated, registry2) {
|
|
29170
|
-
const registered = new Set(registry2);
|
|
29171
|
-
const missing = curated.filter((c) => !registered.has(c));
|
|
29172
|
-
if (missing.length > 0) {
|
|
29173
|
-
throw new Error(
|
|
29174
|
-
`Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
|
|
29175
|
-
);
|
|
29176
|
-
}
|
|
29177
|
-
return [...curated];
|
|
29178
|
-
}
|
|
29179
|
-
var SECRET_SCAN_CONTINUATION = ["/aka:secretscan", "/aka:scan"];
|
|
29180
|
-
function selectSecretScanContinuation(registry2 = readRegisteredCommands()) {
|
|
29181
|
-
const curated = SECRET_SCAN_CONTINUATION.find((c) => registry2.includes(c));
|
|
29182
|
-
if (curated === void 0) {
|
|
29183
|
-
throw new Error(
|
|
29184
|
-
`No secret-scan continuation command registered in the installed plugin (looked for ${SECRET_SCAN_CONTINUATION.join(", ")})`
|
|
29185
|
-
);
|
|
29186
|
-
}
|
|
29187
|
-
const [selected = curated] = selectRegisteredCommands([curated], registry2);
|
|
29188
|
-
return selected;
|
|
29189
|
-
}
|
|
29190
|
-
|
|
29191
|
-
// src/setup-frame-json.ts
|
|
29192
|
-
var FRAME_JSON_BEGIN = "<<<AKA_FRAME_JSON";
|
|
29193
|
-
var FRAME_JSON_END = "AKA_FRAME_JSON>>>";
|
|
29194
|
-
function frameJsonBlock(payload) {
|
|
29195
|
-
return `${FRAME_JSON_BEGIN}
|
|
29196
|
-
${JSON.stringify(payload)}
|
|
29197
|
-
${FRAME_JSON_END}
|
|
29198
|
-
`;
|
|
29199
|
-
}
|
|
29200
|
-
function readFrameJsonBlock(stdout) {
|
|
29201
|
-
const begin = stdout.indexOf(FRAME_JSON_BEGIN);
|
|
29202
|
-
if (begin === -1) return void 0;
|
|
29203
|
-
const from = begin + FRAME_JSON_BEGIN.length;
|
|
29204
|
-
const end = stdout.indexOf(FRAME_JSON_END, from);
|
|
29205
|
-
if (end === -1) return void 0;
|
|
29206
|
-
try {
|
|
29207
|
-
return JSON.parse(stdout.slice(from, end).trim());
|
|
29208
|
-
} catch {
|
|
29209
|
-
return void 0;
|
|
29210
|
-
}
|
|
29211
|
-
}
|
|
29212
|
-
|
|
29213
|
-
// src/setup-show.ts
|
|
29214
|
-
var SHOW_BEGIN = "<<<AKA_SHOW";
|
|
29215
|
-
var SHOW_END = "AKA_SHOW>>>";
|
|
29216
|
-
function showBlock(body) {
|
|
29217
|
-
return `${SHOW_BEGIN}
|
|
29218
|
-
${body}
|
|
29219
|
-
${SHOW_END}
|
|
29220
|
-
`;
|
|
29221
|
-
}
|
|
29222
|
-
|
|
29223
|
-
// src/present.ts
|
|
29224
|
-
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
29225
|
-
function visibleLength(text) {
|
|
29226
|
-
return text.replace(ANSI_RE, "").length;
|
|
29227
|
-
}
|
|
29228
|
-
var fg = (hex3) => (text) => {
|
|
29229
|
-
const r = Number.parseInt(hex3.slice(1, 3), 16);
|
|
29230
|
-
const g = Number.parseInt(hex3.slice(3, 5), 16);
|
|
29231
|
-
const b = Number.parseInt(hex3.slice(5, 7), 16);
|
|
29232
|
-
return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
|
|
29233
|
-
};
|
|
29234
|
-
var paint = {
|
|
29235
|
-
brand: fg("#33e6c6"),
|
|
29236
|
-
// --color-brand · ▸▸ AKA wordmark (accent text)
|
|
29237
|
-
dim: fg("#838995"),
|
|
29238
|
-
// --color-text-3 · separators · "/100" · the "unreviewed" label
|
|
29239
|
-
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
29240
|
-
// the health score number
|
|
29241
|
-
ok: fg("#0db15f"),
|
|
29242
|
-
// --color-ok · healthy ● dot
|
|
29243
|
-
critical: fg("#e63448"),
|
|
29244
|
-
// --color-sev-critical · ■ and the open-findings flag
|
|
29245
|
-
high: fg("#e97a0a"),
|
|
29246
|
-
// --color-sev-high · ■ and the mid-health dot
|
|
29247
|
-
medium: fg("#f7bd00"),
|
|
29248
|
-
// --color-sev-medium · ■
|
|
29249
|
-
low: fg("#0581d4")
|
|
29250
|
-
// --color-sev-low · ■ (azure blue, not purple)
|
|
29251
|
-
};
|
|
29252
|
-
function padEnd(text, width) {
|
|
29253
|
-
const pad = width - visibleLength(text);
|
|
29254
|
-
return pad > 0 ? text + " ".repeat(pad) : text;
|
|
29255
|
-
}
|
|
29256
|
-
function table(headers, rows, opts = {}) {
|
|
29257
|
-
const gap = opts.gap ?? 3;
|
|
29258
|
-
const widths = headers.map(
|
|
29259
|
-
(h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
|
|
29260
|
-
);
|
|
29261
|
-
const sep5 = " ".repeat(gap);
|
|
29262
|
-
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
|
|
29263
|
-
const headerLine = fmt(headers.map((h) => h.toUpperCase()));
|
|
29264
|
-
if (opts.rowSep === true) {
|
|
29265
|
-
const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
29266
|
-
const rule = "\u2500".repeat(fullWidth);
|
|
29267
|
-
const body = [];
|
|
29268
|
-
rows.forEach((row, i) => {
|
|
29269
|
-
if (i > 0) body.push(rule);
|
|
29270
|
-
body.push(fmt(row));
|
|
29271
|
-
});
|
|
29272
|
-
return [headerLine, rule, ...body].join("\n");
|
|
29273
|
-
}
|
|
29274
|
-
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep5);
|
|
29275
|
-
return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
|
|
29276
|
-
}
|
|
29277
|
-
function fenced(body) {
|
|
29278
|
-
const longestRun = Math.max(0, ...[...body.matchAll(/`+/g)].map((m) => m[0].length));
|
|
29279
|
-
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
29280
|
-
return [fence, body, fence].join("\n");
|
|
29281
|
-
}
|
|
29282
|
-
function show(body) {
|
|
29283
|
-
return showBlock(body);
|
|
29284
|
-
}
|
|
29285
|
-
|
|
29286
|
-
// src/remediation/chain.ts
|
|
30690
|
+
// ../../packages/setup-wizard/src/remediation/chain.ts
|
|
29287
30691
|
var REMEDIATION_OPTIONS = [
|
|
29288
30692
|
{ id: "redact-rotation-checklist", label: "Redact + rotation checklist" },
|
|
29289
30693
|
{ id: "redact-only", label: "Redact only" },
|
|
@@ -29322,9 +30726,9 @@ function routeRemediationOption(option, handlers) {
|
|
|
29322
30726
|
}
|
|
29323
30727
|
}
|
|
29324
30728
|
|
|
29325
|
-
// src/remediation/rotation-checklist.ts
|
|
29326
|
-
import { writeFileSync as
|
|
29327
|
-
import { join as
|
|
30729
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
30730
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
30731
|
+
import { join as join14 } from "path";
|
|
29328
30732
|
var GENERIC_CONSOLE_PATH = "rotate via the provider's own console";
|
|
29329
30733
|
var CONSOLE_PATHS = {
|
|
29330
30734
|
anthropic: "console.anthropic.com \u2192 Settings \u2192 API keys",
|
|
@@ -29409,7 +30813,7 @@ function renderRotationChecklistResolvedLine(location) {
|
|
|
29409
30813
|
return `\u2713 I drafted a rotation checklist for you (${location}).`;
|
|
29410
30814
|
}
|
|
29411
30815
|
function writeRotationChecklist(entries, targetDirectory) {
|
|
29412
|
-
|
|
30816
|
+
writeFileSync7(
|
|
29413
30817
|
`${targetDirectory}/rotation-checklist.md`,
|
|
29414
30818
|
renderChecklistMarkdown(entries),
|
|
29415
30819
|
"utf8"
|
|
@@ -29424,7 +30828,7 @@ function generateRotationChecklist(input) {
|
|
|
29424
30828
|
try {
|
|
29425
30829
|
const target = resolveRotationChecklistTarget(input.cwd);
|
|
29426
30830
|
targetDirectory = target.directory;
|
|
29427
|
-
const filePath =
|
|
30831
|
+
const filePath = join14(target.directory, "rotation-checklist.md");
|
|
29428
30832
|
writeRotationChecklist(input.entries, target.directory);
|
|
29429
30833
|
return {
|
|
29430
30834
|
status: "written",
|
|
@@ -29440,25 +30844,7 @@ function generateRotationChecklist(input) {
|
|
|
29440
30844
|
}
|
|
29441
30845
|
}
|
|
29442
30846
|
|
|
29443
|
-
// src/remediation/
|
|
29444
|
-
var RECOMMENDATION_LINE = "I'd redact them and get you rotating, most-exposed first";
|
|
29445
|
-
var STATE_LABEL = {
|
|
29446
|
-
"still-valid": "still valid",
|
|
29447
|
-
unknown: "unknown",
|
|
29448
|
-
invalid: "invalid"
|
|
29449
|
-
};
|
|
29450
|
-
function renderRemediationDecision(findings, moreCount, registry2) {
|
|
29451
|
-
const scanCommand = selectSecretScanContinuation(registry2);
|
|
29452
|
-
const rows = findings.map((f) => [
|
|
29453
|
-
f.provider,
|
|
29454
|
-
f.maskedToken,
|
|
29455
|
-
f.where.filePath,
|
|
29456
|
-
STATE_LABEL[f.state]
|
|
29457
|
-
]);
|
|
29458
|
-
const findingTable = table(["Provider", "Token", "Where", "State"], rows, { rowSep: true });
|
|
29459
|
-
const chainingLine = `${String(moreCount)} more worth a look \u2014 run ${scanCommand} when you're ready.`;
|
|
29460
|
-
return [findingTable, "", RECOMMENDATION_LINE, "", chainingLine].join("\n");
|
|
29461
|
-
}
|
|
30847
|
+
// ../../packages/setup-wizard/src/remediation/redaction-summary.ts
|
|
29462
30848
|
function renderRedactionConfirmation(redactedKeys) {
|
|
29463
30849
|
const noun = redactedKeys === 1 ? "key" : "keys";
|
|
29464
30850
|
return `\u2713 Redacted ${String(redactedKeys)} ${noun}`;
|
|
@@ -29480,8 +30866,8 @@ function renderRedactionOutcome(input) {
|
|
|
29480
30866
|
const totalKeys = input.findings.length;
|
|
29481
30867
|
const isComplete = input.redactedKeys === totalKeys && input.unredactedFindings.length === 0;
|
|
29482
30868
|
if (isComplete) {
|
|
29483
|
-
const
|
|
29484
|
-
return pointeredKeys === 0 ?
|
|
30869
|
+
const confirmation2 = `${renderRedactionConfirmation(input.redactedKeys)}.`;
|
|
30870
|
+
return pointeredKeys === 0 ? confirmation2 : `${confirmation2} ${renderPointeredNote(pointeredKeys)}`;
|
|
29485
30871
|
}
|
|
29486
30872
|
const partialLine = renderPartialRedactionLine(
|
|
29487
30873
|
input.redactedKeys,
|
|
@@ -29513,7 +30899,7 @@ function renderResolvedSummary(input) {
|
|
|
29513
30899
|
return ["Leaked secrets \u2014 resolved", redactionLine, checklistLine, "", preview].join("\n");
|
|
29514
30900
|
}
|
|
29515
30901
|
|
|
29516
|
-
// src/remediation/deliverable.ts
|
|
30902
|
+
// ../../packages/setup-wizard/src/remediation/deliverable.ts
|
|
29517
30903
|
function resolveRemediationDeliverable(input) {
|
|
29518
30904
|
const pointeredKeys = input.pointeredKeys ?? 0;
|
|
29519
30905
|
const unredactedFindings = input.unredactedFindings ?? [];
|
|
@@ -29537,6 +30923,192 @@ function resolveRemediationDeliverable(input) {
|
|
|
29537
30923
|
return { summary, writeResult, entries };
|
|
29538
30924
|
}
|
|
29539
30925
|
|
|
30926
|
+
// ../../packages/setup-wizard/src/remediation/posture.ts
|
|
30927
|
+
function writeStandingSecretPosture(level, policies) {
|
|
30928
|
+
try {
|
|
30929
|
+
applyCategoryPosture({ secret: level }, policies, "overwrite");
|
|
30930
|
+
return { persisted: true, level };
|
|
30931
|
+
} catch {
|
|
30932
|
+
return { persisted: false };
|
|
30933
|
+
}
|
|
30934
|
+
}
|
|
30935
|
+
|
|
30936
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
30937
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
30938
|
+
import { tmpdir } from "os";
|
|
30939
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
30940
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
30941
|
+
ruleId: external_exports.string(),
|
|
30942
|
+
category: DetectionCategory,
|
|
30943
|
+
valueFingerprint: external_exports.string(),
|
|
30944
|
+
keyVersion: external_exports.number(),
|
|
30945
|
+
maskedValue: external_exports.string(),
|
|
30946
|
+
justification: external_exports.string()
|
|
30947
|
+
});
|
|
30948
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
30949
|
+
category: DetectionCategory,
|
|
30950
|
+
action: BuiltinPolicyId,
|
|
30951
|
+
genuineCount: external_exports.number(),
|
|
30952
|
+
fpCount: external_exports.number(),
|
|
30953
|
+
reasoning: external_exports.string()
|
|
30954
|
+
});
|
|
30955
|
+
var JoinEntrySchema = external_exports.object({
|
|
30956
|
+
id: external_exports.string(),
|
|
30957
|
+
ruleId: external_exports.string(),
|
|
30958
|
+
category: DetectionCategory,
|
|
30959
|
+
valueFingerprint: external_exports.string().optional(),
|
|
30960
|
+
keyVersion: external_exports.number().optional(),
|
|
30961
|
+
maskedMatch: external_exports.string(),
|
|
30962
|
+
maskedContext: external_exports.string()
|
|
30963
|
+
});
|
|
30964
|
+
var PLAN_FILE_VERSION = 3;
|
|
30965
|
+
var PersistedPlanSchema = external_exports.object({
|
|
30966
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
30967
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
30968
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
30969
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
30970
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
30971
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
30972
|
+
join: external_exports.array(JoinEntrySchema),
|
|
30973
|
+
notes: external_exports.string(),
|
|
30974
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
30975
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
30976
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
30977
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
30978
|
+
});
|
|
30979
|
+
|
|
30980
|
+
// ../../packages/setup-wizard/src/triage/surfaced-secrets.ts
|
|
30981
|
+
function deriveProvider(ruleId) {
|
|
30982
|
+
const slug = (ruleId.split("/").pop() ?? ruleId).split(".").pop() ?? ruleId;
|
|
30983
|
+
const provider = slug.split("-")[0] ?? slug;
|
|
30984
|
+
return provider === "" ? "unknown" : provider;
|
|
30985
|
+
}
|
|
30986
|
+
|
|
30987
|
+
// src/command-registry.ts
|
|
30988
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
30989
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30990
|
+
var COMMAND_NAMESPACE = "aka";
|
|
30991
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
30992
|
+
function readRegisteredCommands() {
|
|
30993
|
+
return readdirSync5(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
30994
|
+
}
|
|
30995
|
+
function selectRegisteredCommands(curated, registry2) {
|
|
30996
|
+
const registered = new Set(registry2);
|
|
30997
|
+
const missing = curated.filter((c) => !registered.has(c));
|
|
30998
|
+
if (missing.length > 0) {
|
|
30999
|
+
throw new Error(
|
|
31000
|
+
`Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
|
|
31001
|
+
);
|
|
31002
|
+
}
|
|
31003
|
+
return [...curated];
|
|
31004
|
+
}
|
|
31005
|
+
var SECRET_SCAN_CONTINUATION = ["/aka:secretscan", "/aka:scan"];
|
|
31006
|
+
function selectSecretScanContinuation(registry2 = readRegisteredCommands()) {
|
|
31007
|
+
const curated = SECRET_SCAN_CONTINUATION.find((c) => registry2.includes(c));
|
|
31008
|
+
if (curated === void 0) {
|
|
31009
|
+
throw new Error(
|
|
31010
|
+
`No secret-scan continuation command registered in the installed plugin (looked for ${SECRET_SCAN_CONTINUATION.join(", ")})`
|
|
31011
|
+
);
|
|
31012
|
+
}
|
|
31013
|
+
const [selected = curated] = selectRegisteredCommands([curated], registry2);
|
|
31014
|
+
return selected;
|
|
31015
|
+
}
|
|
31016
|
+
|
|
31017
|
+
// src/setup-frame-json.ts
|
|
31018
|
+
var FRAME_JSON_BEGIN = "<<<AKA_FRAME_JSON";
|
|
31019
|
+
var FRAME_JSON_END = "AKA_FRAME_JSON>>>";
|
|
31020
|
+
function frameJsonBlock(payload) {
|
|
31021
|
+
return `${FRAME_JSON_BEGIN}
|
|
31022
|
+
${JSON.stringify(payload)}
|
|
31023
|
+
${FRAME_JSON_END}
|
|
31024
|
+
`;
|
|
31025
|
+
}
|
|
31026
|
+
function readFrameJsonBlock(stdout) {
|
|
31027
|
+
const begin = stdout.indexOf(FRAME_JSON_BEGIN);
|
|
31028
|
+
if (begin === -1) return void 0;
|
|
31029
|
+
const from = begin + FRAME_JSON_BEGIN.length;
|
|
31030
|
+
const end = stdout.indexOf(FRAME_JSON_END, from);
|
|
31031
|
+
if (end === -1) return void 0;
|
|
31032
|
+
try {
|
|
31033
|
+
return JSON.parse(stdout.slice(from, end).trim());
|
|
31034
|
+
} catch {
|
|
31035
|
+
return void 0;
|
|
31036
|
+
}
|
|
31037
|
+
}
|
|
31038
|
+
|
|
31039
|
+
// src/setup-show.ts
|
|
31040
|
+
var SHOW_BEGIN = "<<<AKA_SHOW";
|
|
31041
|
+
var SHOW_END = "AKA_SHOW>>>";
|
|
31042
|
+
function showBlock(body) {
|
|
31043
|
+
return `${SHOW_BEGIN}
|
|
31044
|
+
${body}
|
|
31045
|
+
${SHOW_END}
|
|
31046
|
+
`;
|
|
31047
|
+
}
|
|
31048
|
+
|
|
31049
|
+
// src/present.ts
|
|
31050
|
+
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
31051
|
+
function visibleLength(text) {
|
|
31052
|
+
return text.replace(ANSI_RE, "").length;
|
|
31053
|
+
}
|
|
31054
|
+
var fg = (hex3) => (text) => {
|
|
31055
|
+
const r = Number.parseInt(hex3.slice(1, 3), 16);
|
|
31056
|
+
const g = Number.parseInt(hex3.slice(3, 5), 16);
|
|
31057
|
+
const b = Number.parseInt(hex3.slice(5, 7), 16);
|
|
31058
|
+
return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
|
|
31059
|
+
};
|
|
31060
|
+
var paint = {
|
|
31061
|
+
brand: fg("#33e6c6"),
|
|
31062
|
+
// --color-brand · ▸▸ AKA wordmark (accent text)
|
|
31063
|
+
dim: fg("#838995"),
|
|
31064
|
+
// --color-text-3 · separators · "/100" · the "unreviewed" label
|
|
31065
|
+
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
31066
|
+
// the health score number
|
|
31067
|
+
ok: fg("#0db15f"),
|
|
31068
|
+
// --color-ok · healthy ● dot
|
|
31069
|
+
critical: fg("#e63448"),
|
|
31070
|
+
// --color-sev-critical · ■ and the open-findings flag
|
|
31071
|
+
high: fg("#e97a0a"),
|
|
31072
|
+
// --color-sev-high · ■ and the mid-health dot
|
|
31073
|
+
medium: fg("#f7bd00"),
|
|
31074
|
+
// --color-sev-medium · ■
|
|
31075
|
+
low: fg("#0581d4")
|
|
31076
|
+
// --color-sev-low · ■ (azure blue, not purple)
|
|
31077
|
+
};
|
|
31078
|
+
function padEnd(text, width) {
|
|
31079
|
+
const pad = width - visibleLength(text);
|
|
31080
|
+
return pad > 0 ? text + " ".repeat(pad) : text;
|
|
31081
|
+
}
|
|
31082
|
+
function table(headers, rows, opts = {}) {
|
|
31083
|
+
const gap = opts.gap ?? 3;
|
|
31084
|
+
const widths = headers.map(
|
|
31085
|
+
(h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
|
|
31086
|
+
);
|
|
31087
|
+
const sep5 = " ".repeat(gap);
|
|
31088
|
+
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
|
|
31089
|
+
const headerLine = fmt(headers.map((h) => h.toUpperCase()));
|
|
31090
|
+
if (opts.rowSep === true) {
|
|
31091
|
+
const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
31092
|
+
const rule = "\u2500".repeat(fullWidth);
|
|
31093
|
+
const body = [];
|
|
31094
|
+
rows.forEach((row, i) => {
|
|
31095
|
+
if (i > 0) body.push(rule);
|
|
31096
|
+
body.push(fmt(row));
|
|
31097
|
+
});
|
|
31098
|
+
return [headerLine, rule, ...body].join("\n");
|
|
31099
|
+
}
|
|
31100
|
+
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep5);
|
|
31101
|
+
return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
|
|
31102
|
+
}
|
|
31103
|
+
function fenced(body) {
|
|
31104
|
+
const longestRun = Math.max(0, ...[...body.matchAll(/`+/g)].map((m) => m[0].length));
|
|
31105
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
31106
|
+
return [fence, body, fence].join("\n");
|
|
31107
|
+
}
|
|
31108
|
+
function show(body) {
|
|
31109
|
+
return showBlock(body);
|
|
31110
|
+
}
|
|
31111
|
+
|
|
29540
31112
|
// src/remediation/findings.ts
|
|
29541
31113
|
function loadSecretLeakFindings(readCalibrationFrame) {
|
|
29542
31114
|
try {
|
|
@@ -29547,21 +31119,31 @@ function loadSecretLeakFindings(readCalibrationFrame) {
|
|
|
29547
31119
|
}
|
|
29548
31120
|
}
|
|
29549
31121
|
|
|
29550
|
-
// src/remediation/
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
|
|
29555
|
-
|
|
29556
|
-
|
|
29557
|
-
|
|
31122
|
+
// src/remediation/render.ts
|
|
31123
|
+
var RECOMMENDATION_LINE = "I'd redact them and get you rotating, most-exposed first";
|
|
31124
|
+
var STATE_LABEL = {
|
|
31125
|
+
"still-valid": "still valid",
|
|
31126
|
+
unknown: "unknown",
|
|
31127
|
+
invalid: "invalid"
|
|
31128
|
+
};
|
|
31129
|
+
function renderRemediationDecision(findings, moreCount, registry2) {
|
|
31130
|
+
const scanCommand = selectSecretScanContinuation(registry2);
|
|
31131
|
+
const rows = findings.map((f) => [
|
|
31132
|
+
f.provider,
|
|
31133
|
+
f.maskedToken,
|
|
31134
|
+
f.where.filePath,
|
|
31135
|
+
STATE_LABEL[f.state]
|
|
31136
|
+
]);
|
|
31137
|
+
const findingTable = table(["Provider", "Token", "Where", "State"], rows, { rowSep: true });
|
|
31138
|
+
const chainingLine = `${String(moreCount)} more worth a look \u2014 run ${scanCommand} when you're ready.`;
|
|
31139
|
+
return [findingTable, "", RECOMMENDATION_LINE, "", chainingLine].join("\n");
|
|
29558
31140
|
}
|
|
29559
31141
|
|
|
29560
31142
|
// src/remediation/surfaced-redact.ts
|
|
29561
|
-
import { readFileSync as
|
|
31143
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
29562
31144
|
|
|
29563
31145
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
29564
|
-
import { randomUUID as
|
|
31146
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
29565
31147
|
|
|
29566
31148
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
29567
31149
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -29723,7 +31305,7 @@ var StandaloneDataGateway = class {
|
|
|
29723
31305
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
29724
31306
|
const installed = this.installedScanRules();
|
|
29725
31307
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
29726
|
-
id:
|
|
31308
|
+
id: randomUUID15(),
|
|
29727
31309
|
scope: "global",
|
|
29728
31310
|
target: { ruleId },
|
|
29729
31311
|
action,
|
|
@@ -29876,27 +31458,20 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
29876
31458
|
}
|
|
29877
31459
|
|
|
29878
31460
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
29879
|
-
import { randomUUID as
|
|
31461
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
29880
31462
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
29881
31463
|
|
|
29882
31464
|
// src/history/transcripts.ts
|
|
29883
|
-
import { readdirSync as
|
|
31465
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync10 } from "fs";
|
|
29884
31466
|
import { homedir as homedir3 } from "os";
|
|
29885
|
-
import { join as
|
|
31467
|
+
import { join as join16 } from "path";
|
|
29886
31468
|
function transcriptsDir(home) {
|
|
29887
|
-
return
|
|
31469
|
+
return join16(home ?? homedir3(), ".claude", "projects");
|
|
29888
31470
|
}
|
|
29889
31471
|
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
29890
31472
|
|
|
29891
|
-
// src/triage/surfaced-secrets.ts
|
|
29892
|
-
function deriveProvider(ruleId) {
|
|
29893
|
-
const slug = (ruleId.split("/").pop() ?? ruleId).split(".").pop() ?? ruleId;
|
|
29894
|
-
const provider = slug.split("-")[0] ?? slug;
|
|
29895
|
-
return provider === "" ? "unknown" : provider;
|
|
29896
|
-
}
|
|
29897
|
-
|
|
29898
31473
|
// src/remediation/redact.ts
|
|
29899
|
-
import { readFileSync as
|
|
31474
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
29900
31475
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve } from "path";
|
|
29901
31476
|
var REDACTED_PLACEHOLDER = "[REDACTED:SECRET]";
|
|
29902
31477
|
var REPLACE_PATTERN_SEQUENCE = /\$[$&`'<0-9]/;
|
|
@@ -29944,7 +31519,7 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
|
|
|
29944
31519
|
for (const [filePath, fileTargets] of byFile) {
|
|
29945
31520
|
let content;
|
|
29946
31521
|
try {
|
|
29947
|
-
content =
|
|
31522
|
+
content = readFileSync11(filePath, "utf8");
|
|
29948
31523
|
} catch {
|
|
29949
31524
|
continue;
|
|
29950
31525
|
}
|
|
@@ -29974,11 +31549,11 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
|
|
|
29974
31549
|
if (struckHere.length === 0) continue;
|
|
29975
31550
|
const tmpPath = `${filePath}.aka-redact.tmp`;
|
|
29976
31551
|
try {
|
|
29977
|
-
|
|
31552
|
+
writeFileSync9(tmpPath, content);
|
|
29978
31553
|
renameSync5(tmpPath, filePath);
|
|
29979
31554
|
} catch {
|
|
29980
31555
|
try {
|
|
29981
|
-
|
|
31556
|
+
rmSync6(tmpPath, { force: true, recursive: true });
|
|
29982
31557
|
} catch {
|
|
29983
31558
|
}
|
|
29984
31559
|
continue;
|
|
@@ -30067,7 +31642,7 @@ async function redactSurfacedSecrets(findings, overrides = {}) {
|
|
|
30067
31642
|
for (const [filePath, fileFindings] of byFile) {
|
|
30068
31643
|
let content;
|
|
30069
31644
|
try {
|
|
30070
|
-
content =
|
|
31645
|
+
content = readFileSync12(filePath, "utf8");
|
|
30071
31646
|
} catch {
|
|
30072
31647
|
unrecovered.push(...fileFindings);
|
|
30073
31648
|
continue;
|
|
@@ -30228,11 +31803,11 @@ async function route(frameText, rawOption, rawPosture) {
|
|
|
30228
31803
|
break;
|
|
30229
31804
|
}
|
|
30230
31805
|
}
|
|
30231
|
-
if (process.argv[1] &&
|
|
31806
|
+
if (process.argv[1] && fileURLToPath3(import.meta.url) === process.argv[1]) {
|
|
30232
31807
|
try {
|
|
30233
31808
|
const argv = process.argv.slice(2);
|
|
30234
31809
|
const optionIndex = argv.indexOf("--option");
|
|
30235
|
-
const frameText =
|
|
31810
|
+
const frameText = readFileSync13(0, "utf8");
|
|
30236
31811
|
if (optionIndex === -1) {
|
|
30237
31812
|
present(frameText);
|
|
30238
31813
|
} else {
|