@akasecurity/ai-tc-claude-code 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -0
- package/commands/setup.md +23 -0
- package/package.json +4 -3
- package/scripts/apply-suppressions.js +1665 -544
- package/scripts/backfill.js +2032 -404
- package/scripts/filescan.js +1933 -367
- package/scripts/firstrun.js +1358 -230
- package/scripts/intro.js +418 -58
- package/scripts/message-display.js +1463 -306
- package/scripts/onboard.js +1580 -249
- package/scripts/post-tool-use.js +1946 -368
- package/scripts/pre-tool-use.js +1961 -377
- package/scripts/query.js +1359 -231
- package/scripts/reconcile.js +1494 -337
- package/scripts/remediate.js +2183 -559
- package/scripts/scan-worker.js +18055 -0
- package/scripts/session-start.js +1434 -342
- package/scripts/start-light.js +441 -81
- package/scripts/statusline.js +1356 -228
- package/scripts/stop.js +400 -83
- package/scripts/user-prompt-submit.js +2071 -475
package/scripts/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";
|
|
@@ -25884,6 +26961,40 @@ function escapeRegExp2(value) {
|
|
|
25884
26961
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25885
26962
|
}
|
|
25886
26963
|
|
|
26964
|
+
// ../../packages/detections/src/regex-cache.ts
|
|
26965
|
+
var singles = /* @__PURE__ */ new WeakMap();
|
|
26966
|
+
var keywordLists = /* @__PURE__ */ new WeakMap();
|
|
26967
|
+
var labelLists = /* @__PURE__ */ new WeakMap();
|
|
26968
|
+
function listCache(kind) {
|
|
26969
|
+
return kind === "keyword" ? keywordLists : labelLists;
|
|
26970
|
+
}
|
|
26971
|
+
function memoizedRegExp(owner, build) {
|
|
26972
|
+
const cached2 = singles.get(owner);
|
|
26973
|
+
if (cached2 !== void 0) {
|
|
26974
|
+
cached2.lastIndex = 0;
|
|
26975
|
+
return cached2;
|
|
26976
|
+
}
|
|
26977
|
+
const compiled = build();
|
|
26978
|
+
singles.set(owner, compiled);
|
|
26979
|
+
return compiled;
|
|
26980
|
+
}
|
|
26981
|
+
function memoizedRegExpList(kind, owner, build) {
|
|
26982
|
+
const cache = listCache(kind);
|
|
26983
|
+
const cached2 = cache.get(owner);
|
|
26984
|
+
if (cached2 !== void 0) {
|
|
26985
|
+
if (cached2.stateful) {
|
|
26986
|
+
for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
|
|
26987
|
+
}
|
|
26988
|
+
return cached2.entries;
|
|
26989
|
+
}
|
|
26990
|
+
const entries = build();
|
|
26991
|
+
cache.set(owner, {
|
|
26992
|
+
entries,
|
|
26993
|
+
stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
|
|
26994
|
+
});
|
|
26995
|
+
return entries;
|
|
26996
|
+
}
|
|
26997
|
+
|
|
25887
26998
|
// ../../packages/detections/src/matchers/limits.ts
|
|
25888
26999
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
25889
27000
|
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
@@ -25894,10 +27005,17 @@ var KeywordMatcher2 = class {
|
|
|
25894
27005
|
if (rule.matcher.type !== "keyword") return [];
|
|
25895
27006
|
const { keywords, caseSensitive } = rule.matcher;
|
|
25896
27007
|
const spans = [];
|
|
25897
|
-
|
|
25898
|
-
|
|
25899
|
-
|
|
25900
|
-
|
|
27008
|
+
const compiled = memoizedRegExpList(
|
|
27009
|
+
"keyword",
|
|
27010
|
+
rule.matcher,
|
|
27011
|
+
() => keywords.map((kw) => {
|
|
27012
|
+
if (kw.length === 0) return void 0;
|
|
27013
|
+
return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
27014
|
+
})
|
|
27015
|
+
);
|
|
27016
|
+
for (const re of compiled) {
|
|
27017
|
+
if (re === void 0) continue;
|
|
27018
|
+
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
25901
27019
|
let m;
|
|
25902
27020
|
while ((m = re.exec(text)) !== null) {
|
|
25903
27021
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -25913,7 +27031,10 @@ var RegexMatcher2 = class {
|
|
|
25913
27031
|
match(text, rule) {
|
|
25914
27032
|
if (rule.matcher.type !== "regex") return [];
|
|
25915
27033
|
const { pattern, flags, captureGroup } = rule.matcher;
|
|
25916
|
-
const re =
|
|
27034
|
+
const re = memoizedRegExp(
|
|
27035
|
+
rule.matcher,
|
|
27036
|
+
() => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
|
|
27037
|
+
);
|
|
25917
27038
|
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
25918
27039
|
const spans = [];
|
|
25919
27040
|
let m;
|
|
@@ -26021,11 +27142,15 @@ function isCorroborated(candidate, candidates, text) {
|
|
|
26021
27142
|
const labels = req.labels;
|
|
26022
27143
|
if (labels && labels.length > 0) {
|
|
26023
27144
|
const haystack = text.slice(Math.max(0, winStart), winEnd);
|
|
26024
|
-
for (const
|
|
26025
|
-
|
|
26026
|
-
|
|
26027
|
-
|
|
26028
|
-
|
|
27145
|
+
for (const re of memoizedRegExpList(
|
|
27146
|
+
"label",
|
|
27147
|
+
req,
|
|
27148
|
+
() => labels.map((label) => {
|
|
27149
|
+
const trimmed = label.trim();
|
|
27150
|
+
return trimmed.length === 0 ? void 0 : new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
|
|
27151
|
+
})
|
|
27152
|
+
)) {
|
|
27153
|
+
if (re?.test(haystack)) return true;
|
|
26029
27154
|
}
|
|
26030
27155
|
}
|
|
26031
27156
|
return false;
|
|
@@ -26293,13 +27418,14 @@ function probesFor(rule) {
|
|
|
26293
27418
|
const derived = rule.matcher.type === "regex" ? derivedProbes(rule.matcher.pattern) : [];
|
|
26294
27419
|
return [...derived, ...EXPONENTIAL_PROBES, ...POLYNOMIAL_PROBES];
|
|
26295
27420
|
}
|
|
26296
|
-
|
|
27421
|
+
var wallClock = () => performance.now();
|
|
27422
|
+
function worstProbeMs(rule, now = wallClock) {
|
|
26297
27423
|
let ms = 0;
|
|
26298
27424
|
let probe = "";
|
|
26299
27425
|
for (const text of probesFor(rule)) {
|
|
26300
|
-
const start =
|
|
27426
|
+
const start = now();
|
|
26301
27427
|
scan(text, [rule]);
|
|
26302
|
-
const elapsed =
|
|
27428
|
+
const elapsed = now() - start;
|
|
26303
27429
|
if (elapsed > ms) {
|
|
26304
27430
|
ms = elapsed;
|
|
26305
27431
|
probe = text;
|
|
@@ -27967,7 +29093,7 @@ var gcp_service_account_default = {
|
|
|
27967
29093
|
severity: "critical",
|
|
27968
29094
|
matcher: {
|
|
27969
29095
|
type: "regex",
|
|
27970
|
-
pattern: "
|
|
29096
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27971
29097
|
flags: "g"
|
|
27972
29098
|
},
|
|
27973
29099
|
examples: [
|
|
@@ -28346,15 +29472,15 @@ function bundledDetections() {
|
|
|
28346
29472
|
}
|
|
28347
29473
|
|
|
28348
29474
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
28349
|
-
import { existsSync as
|
|
28350
|
-
import { basename, dirname, isAbsolute, join as
|
|
29475
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29476
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
28351
29477
|
function resolveRepo(cwd) {
|
|
28352
29478
|
try {
|
|
28353
29479
|
const root = findGitRoot(cwd);
|
|
28354
29480
|
if (!root) return void 0;
|
|
28355
29481
|
const ctx = resolveGitContext(root);
|
|
28356
29482
|
const url2 = ctx ? remoteUrl(ctx) : void 0;
|
|
28357
|
-
return (url2 ? slugFromUrl(url2) : void 0) ??
|
|
29483
|
+
return (url2 ? slugFromUrl(url2) : void 0) ?? basename2(ctx?.headRoot ?? root);
|
|
28358
29484
|
} catch {
|
|
28359
29485
|
return void 0;
|
|
28360
29486
|
}
|
|
@@ -28369,36 +29495,36 @@ function resolveWorktreeRoot(cwd) {
|
|
|
28369
29495
|
function findGitRoot(start) {
|
|
28370
29496
|
let dir = start;
|
|
28371
29497
|
for (; ; ) {
|
|
28372
|
-
if (
|
|
28373
|
-
const parent =
|
|
29498
|
+
if (existsSync6(join9(dir, ".git"))) return dir;
|
|
29499
|
+
const parent = dirname2(dir);
|
|
28374
29500
|
if (parent === dir) return void 0;
|
|
28375
29501
|
dir = parent;
|
|
28376
29502
|
}
|
|
28377
29503
|
}
|
|
28378
29504
|
function resolveGitContext(root) {
|
|
28379
|
-
const dotGit =
|
|
29505
|
+
const dotGit = join9(root, ".git");
|
|
28380
29506
|
try {
|
|
28381
|
-
if (
|
|
28382
|
-
return { configPath:
|
|
29507
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
29508
|
+
return { configPath: join9(dotGit, "config"), headRoot: root };
|
|
28383
29509
|
}
|
|
28384
29510
|
} catch {
|
|
28385
29511
|
return void 0;
|
|
28386
29512
|
}
|
|
28387
29513
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
28388
29514
|
if (!target) return void 0;
|
|
28389
|
-
const gitdir = isAbsolute(target) ? target :
|
|
28390
|
-
if (
|
|
28391
|
-
return { configPath:
|
|
29515
|
+
const gitdir = isAbsolute(target) ? target : join9(root, target);
|
|
29516
|
+
if (existsSync6(join9(gitdir, "config"))) {
|
|
29517
|
+
return { configPath: join9(gitdir, "config"), headRoot: root };
|
|
28392
29518
|
}
|
|
28393
|
-
const commonRaw = safeRead(
|
|
29519
|
+
const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
|
|
28394
29520
|
if (!commonRaw) return void 0;
|
|
28395
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
28396
|
-
const headRoot =
|
|
28397
|
-
return { configPath:
|
|
29521
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
|
|
29522
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
|
|
29523
|
+
return { configPath: join9(commonGitDir, "config"), headRoot };
|
|
28398
29524
|
}
|
|
28399
29525
|
function safeRead(path) {
|
|
28400
29526
|
try {
|
|
28401
|
-
return
|
|
29527
|
+
return readFileSync5(path, "utf8");
|
|
28402
29528
|
} catch {
|
|
28403
29529
|
return void 0;
|
|
28404
29530
|
}
|
|
@@ -28436,13 +29562,13 @@ function slugFromUrl(url2) {
|
|
|
28436
29562
|
}
|
|
28437
29563
|
|
|
28438
29564
|
// ../../packages/plugin-sdk/src/events.ts
|
|
28439
|
-
import { createHash as createHash4, randomUUID as
|
|
29565
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28440
29566
|
function contentHashOf(text) {
|
|
28441
29567
|
return createHash4("sha256").update(text).digest("hex");
|
|
28442
29568
|
}
|
|
28443
29569
|
function buildIngestEvent(input) {
|
|
28444
29570
|
return {
|
|
28445
|
-
id:
|
|
29571
|
+
id: randomUUID13(),
|
|
28446
29572
|
sourceTool: input.sourceTool,
|
|
28447
29573
|
kind: input.kind,
|
|
28448
29574
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -28453,21 +29579,473 @@ function buildIngestEvent(input) {
|
|
|
28453
29579
|
// SDK boot in the fail-open hook path). Preserve any id the caller already set.
|
|
28454
29580
|
metadata: {
|
|
28455
29581
|
...input.metadata,
|
|
28456
|
-
correlationId: input.metadata?.correlationId ??
|
|
29582
|
+
correlationId: input.metadata?.correlationId ?? randomUUID13()
|
|
29583
|
+
}
|
|
29584
|
+
};
|
|
29585
|
+
}
|
|
29586
|
+
|
|
29587
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29588
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29589
|
+
import { fileURLToPath } from "url";
|
|
29590
|
+
import { Worker } from "worker_threads";
|
|
29591
|
+
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
29592
|
+
var ISOLATED_PROBE_BUDGET_MS = 1e3;
|
|
29593
|
+
var ISOLATED_START_BUDGET_MS = 5e3;
|
|
29594
|
+
var ATTRIBUTION_MIN_RULE_MS = 500;
|
|
29595
|
+
var ATTRIBUTION_MIN_SHARE = 0.5;
|
|
29596
|
+
var resolvedWorkerUrl;
|
|
29597
|
+
function resolveWorkerUrl() {
|
|
29598
|
+
if (resolvedWorkerUrl !== void 0) return resolvedWorkerUrl ?? void 0;
|
|
29599
|
+
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
29600
|
+
const candidate = new URL(name, import.meta.url);
|
|
29601
|
+
try {
|
|
29602
|
+
if (existsSync7(fileURLToPath(candidate))) {
|
|
29603
|
+
resolvedWorkerUrl = candidate;
|
|
29604
|
+
return candidate;
|
|
29605
|
+
}
|
|
29606
|
+
} catch {
|
|
29607
|
+
}
|
|
29608
|
+
}
|
|
29609
|
+
resolvedWorkerUrl = null;
|
|
29610
|
+
return void 0;
|
|
29611
|
+
}
|
|
29612
|
+
function messageOf(error51) {
|
|
29613
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
29614
|
+
}
|
|
29615
|
+
function createIsolatedScanner(data, opts = {}) {
|
|
29616
|
+
const budgetMs = opts.budgetMs ?? ISOLATED_SCAN_BUDGET_MS;
|
|
29617
|
+
const probeBudgetMs = opts.probeBudgetMs ?? ISOLATED_PROBE_BUDGET_MS;
|
|
29618
|
+
const startBudgetMs = opts.startBudgetMs ?? ISOLATED_START_BUDGET_MS;
|
|
29619
|
+
const minAttributionMs = opts.minAttributionMs ?? ATTRIBUTION_MIN_RULE_MS;
|
|
29620
|
+
let worker;
|
|
29621
|
+
let readyWorker;
|
|
29622
|
+
let broken;
|
|
29623
|
+
let closed = false;
|
|
29624
|
+
let nextJobId = 1;
|
|
29625
|
+
let pending;
|
|
29626
|
+
const terminating = /* @__PURE__ */ new Set();
|
|
29627
|
+
let chain = Promise.resolve();
|
|
29628
|
+
function clearTimers(job) {
|
|
29629
|
+
if (job.startupTimer !== void 0) clearTimeout(job.startupTimer);
|
|
29630
|
+
if (job.timer !== void 0) clearTimeout(job.timer);
|
|
29631
|
+
}
|
|
29632
|
+
function take() {
|
|
29633
|
+
const job = pending;
|
|
29634
|
+
if (!job) return void 0;
|
|
29635
|
+
pending = void 0;
|
|
29636
|
+
clearTimers(job);
|
|
29637
|
+
worker?.unref();
|
|
29638
|
+
return job;
|
|
29639
|
+
}
|
|
29640
|
+
function failPending(outcome) {
|
|
29641
|
+
take()?.fail(outcome);
|
|
29642
|
+
}
|
|
29643
|
+
function kill(dead) {
|
|
29644
|
+
if (worker === dead) worker = void 0;
|
|
29645
|
+
if (readyWorker === dead) readyWorker = void 0;
|
|
29646
|
+
const done = dead.terminate().catch(() => void 0);
|
|
29647
|
+
terminating.add(done);
|
|
29648
|
+
void done.finally(() => terminating.delete(done));
|
|
29649
|
+
}
|
|
29650
|
+
function onDeadline(job) {
|
|
29651
|
+
if (pending !== job) return;
|
|
29652
|
+
const now = performance.now();
|
|
29653
|
+
const runningMs = now - job.progressAt;
|
|
29654
|
+
const elapsedMs = now - job.startedAt;
|
|
29655
|
+
const blamed = job.progressIndex >= 0 && runningMs >= minAttributionMs && runningMs >= elapsedMs * ATTRIBUTION_MIN_SHARE;
|
|
29656
|
+
const culpritIndex = blamed ? job.progressIndex : void 0;
|
|
29657
|
+
kill(job.worker);
|
|
29658
|
+
failPending({ status: "timeout", culpritIndex, elapsedMs });
|
|
29659
|
+
}
|
|
29660
|
+
function ensureWorker() {
|
|
29661
|
+
if (worker) return worker;
|
|
29662
|
+
const url2 = opts.workerUrl ?? resolveWorkerUrl();
|
|
29663
|
+
if (!url2) {
|
|
29664
|
+
return {
|
|
29665
|
+
error: "the scan worker script was not found next to this bundle"
|
|
29666
|
+
};
|
|
29667
|
+
}
|
|
29668
|
+
let started;
|
|
29669
|
+
try {
|
|
29670
|
+
started = new Worker(url2, { workerData: data });
|
|
29671
|
+
} catch (error51) {
|
|
29672
|
+
return { error: `could not start the scan worker: ${messageOf(error51)}` };
|
|
29673
|
+
}
|
|
29674
|
+
opts.onWorkerStart?.(started.threadId);
|
|
29675
|
+
started.on("message", (message) => {
|
|
29676
|
+
if (worker !== started) return;
|
|
29677
|
+
if (message.kind === "ready") {
|
|
29678
|
+
readyWorker = started;
|
|
29679
|
+
if (pending?.worker === started) beginDeadline(pending);
|
|
29680
|
+
return;
|
|
29681
|
+
}
|
|
29682
|
+
if (message.kind === "progress") {
|
|
29683
|
+
if (pending?.worker === started) {
|
|
29684
|
+
pending.progressIndex = message.index;
|
|
29685
|
+
pending.progressAt = performance.now();
|
|
29686
|
+
}
|
|
29687
|
+
return;
|
|
29688
|
+
}
|
|
29689
|
+
if (pending?.id !== message.id) return;
|
|
29690
|
+
if (message.kind === "failed") {
|
|
29691
|
+
failPending({
|
|
29692
|
+
status: "unavailable",
|
|
29693
|
+
reason: `the scan worker failed: ${message.message}`
|
|
29694
|
+
});
|
|
29695
|
+
return;
|
|
29696
|
+
}
|
|
29697
|
+
const job = take();
|
|
29698
|
+
if (job && !job.reply(message)) {
|
|
29699
|
+
job.fail({ status: "unavailable", reason: "the scan worker answered the wrong job" });
|
|
29700
|
+
}
|
|
29701
|
+
});
|
|
29702
|
+
started.on("error", (error51) => {
|
|
29703
|
+
if (worker !== started) return;
|
|
29704
|
+
broken = messageOf(error51);
|
|
29705
|
+
worker = void 0;
|
|
29706
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29707
|
+
failPending({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29708
|
+
});
|
|
29709
|
+
started.on("exit", () => {
|
|
29710
|
+
if (worker !== started) return;
|
|
29711
|
+
broken ??= "the scan worker exited before answering";
|
|
29712
|
+
worker = void 0;
|
|
29713
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29714
|
+
failPending({ status: "unavailable", reason: "the scan worker exited before answering" });
|
|
29715
|
+
});
|
|
29716
|
+
started.unref();
|
|
29717
|
+
worker = started;
|
|
29718
|
+
return started;
|
|
29719
|
+
}
|
|
29720
|
+
function beginDeadline(job) {
|
|
29721
|
+
if (job.startupTimer !== void 0) {
|
|
29722
|
+
clearTimeout(job.startupTimer);
|
|
29723
|
+
job.startupTimer = void 0;
|
|
29724
|
+
}
|
|
29725
|
+
if (job.timer !== void 0) return;
|
|
29726
|
+
job.startedAt = performance.now();
|
|
29727
|
+
job.progressAt = job.startedAt;
|
|
29728
|
+
job.timer = setTimeout(() => {
|
|
29729
|
+
onDeadline(job);
|
|
29730
|
+
}, job.budgetMs);
|
|
29731
|
+
}
|
|
29732
|
+
function runOne(spec, fail2) {
|
|
29733
|
+
if (closed) {
|
|
29734
|
+
fail2({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29735
|
+
return;
|
|
29736
|
+
}
|
|
29737
|
+
if (broken !== void 0) {
|
|
29738
|
+
fail2({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29739
|
+
return;
|
|
29740
|
+
}
|
|
29741
|
+
const started = ensureWorker();
|
|
29742
|
+
if (!(started instanceof Worker)) {
|
|
29743
|
+
broken = started.error;
|
|
29744
|
+
fail2({ status: "unavailable", reason: started.error });
|
|
29745
|
+
return;
|
|
29746
|
+
}
|
|
29747
|
+
const id = nextJobId++;
|
|
29748
|
+
const now = performance.now();
|
|
29749
|
+
const job = {
|
|
29750
|
+
id,
|
|
29751
|
+
worker: started,
|
|
29752
|
+
budgetMs: spec.budgetMs,
|
|
29753
|
+
startedAt: now,
|
|
29754
|
+
progressIndex: -1,
|
|
29755
|
+
progressAt: now,
|
|
29756
|
+
startupTimer: void 0,
|
|
29757
|
+
timer: void 0,
|
|
29758
|
+
reply: spec.reply,
|
|
29759
|
+
fail: fail2
|
|
29760
|
+
};
|
|
29761
|
+
pending = job;
|
|
29762
|
+
started.ref();
|
|
29763
|
+
if (readyWorker === started) {
|
|
29764
|
+
beginDeadline(job);
|
|
29765
|
+
} else {
|
|
29766
|
+
job.startupTimer = setTimeout(() => {
|
|
29767
|
+
if (pending !== job) return;
|
|
29768
|
+
kill(job.worker);
|
|
29769
|
+
failPending({
|
|
29770
|
+
status: "unavailable",
|
|
29771
|
+
reason: `the scan worker did not start within ${String(startBudgetMs)}ms`
|
|
29772
|
+
});
|
|
29773
|
+
}, startBudgetMs);
|
|
29774
|
+
}
|
|
29775
|
+
try {
|
|
29776
|
+
started.postMessage(spec.build(id));
|
|
29777
|
+
} catch (error51) {
|
|
29778
|
+
failPending({
|
|
29779
|
+
// The thread went away between the ref and the post.
|
|
29780
|
+
status: "unavailable",
|
|
29781
|
+
reason: `could not reach the scan worker: ${messageOf(error51)}`
|
|
29782
|
+
});
|
|
29783
|
+
}
|
|
29784
|
+
}
|
|
29785
|
+
function enqueue(spec) {
|
|
29786
|
+
const next = chain.then(
|
|
29787
|
+
() => new Promise((resolve2) => {
|
|
29788
|
+
spec(resolve2);
|
|
29789
|
+
})
|
|
29790
|
+
);
|
|
29791
|
+
chain = next.then(
|
|
29792
|
+
() => void 0,
|
|
29793
|
+
() => void 0
|
|
29794
|
+
);
|
|
29795
|
+
return next;
|
|
29796
|
+
}
|
|
29797
|
+
return {
|
|
29798
|
+
scan(text, context, scanOpts) {
|
|
29799
|
+
return enqueue((resolve2) => {
|
|
29800
|
+
runOne(
|
|
29801
|
+
{
|
|
29802
|
+
budgetMs,
|
|
29803
|
+
build: (id) => ({
|
|
29804
|
+
kind: "scan",
|
|
29805
|
+
id,
|
|
29806
|
+
text,
|
|
29807
|
+
filePath: context?.filePath,
|
|
29808
|
+
attribute: scanOpts?.attribute === true
|
|
29809
|
+
}),
|
|
29810
|
+
reply: (message) => {
|
|
29811
|
+
if (message.kind !== "result") return false;
|
|
29812
|
+
resolve2({ status: "ok", findings: message.findings });
|
|
29813
|
+
return true;
|
|
29814
|
+
}
|
|
29815
|
+
},
|
|
29816
|
+
resolve2
|
|
29817
|
+
);
|
|
29818
|
+
});
|
|
29819
|
+
},
|
|
29820
|
+
probe(rule) {
|
|
29821
|
+
return enqueue((resolve2) => {
|
|
29822
|
+
runOne(
|
|
29823
|
+
{
|
|
29824
|
+
budgetMs: probeBudgetMs,
|
|
29825
|
+
build: (id) => ({ kind: "probe", id, rule }),
|
|
29826
|
+
reply: (message) => {
|
|
29827
|
+
if (message.kind !== "probed") return false;
|
|
29828
|
+
resolve2({ status: "ok", safe: message.safe, worstMs: message.worstMs });
|
|
29829
|
+
return true;
|
|
29830
|
+
}
|
|
29831
|
+
},
|
|
29832
|
+
resolve2
|
|
29833
|
+
);
|
|
29834
|
+
});
|
|
29835
|
+
},
|
|
29836
|
+
async close() {
|
|
29837
|
+
closed = true;
|
|
29838
|
+
const live = worker;
|
|
29839
|
+
worker = void 0;
|
|
29840
|
+
readyWorker = void 0;
|
|
29841
|
+
failPending({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29842
|
+
if (live) kill(live);
|
|
29843
|
+
await Promise.all([...terminating]);
|
|
29844
|
+
}
|
|
29845
|
+
};
|
|
29846
|
+
}
|
|
29847
|
+
|
|
29848
|
+
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
29849
|
+
var PASS_BUDGET_MS = 2e3;
|
|
29850
|
+
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
29851
|
+
function ruleProbeKey(rule) {
|
|
29852
|
+
if (rule.matcher.type !== "regex") return void 0;
|
|
29853
|
+
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
29854
|
+
}
|
|
29855
|
+
function warn(rule, verb, detail, recoverable) {
|
|
29856
|
+
const hint = recoverable ? ` (${UNQUARANTINE_HINT})` : "";
|
|
29857
|
+
process.stderr.write(`[aka] ${verb} rule "${rule.id}": ${detail}${hint}
|
|
29858
|
+
`);
|
|
29859
|
+
}
|
|
29860
|
+
function warnQuarantined(rule, worstMs, cached2) {
|
|
29861
|
+
warn(
|
|
29862
|
+
rule,
|
|
29863
|
+
"quarantined",
|
|
29864
|
+
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.",
|
|
29865
|
+
cached2
|
|
29866
|
+
);
|
|
29867
|
+
}
|
|
29868
|
+
function warnUnmeasured(rule) {
|
|
29869
|
+
warn(
|
|
29870
|
+
rule,
|
|
29871
|
+
"skipped",
|
|
29872
|
+
"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.",
|
|
29873
|
+
false
|
|
29874
|
+
);
|
|
29875
|
+
}
|
|
29876
|
+
function warnUnmeasurable(reason, count) {
|
|
29877
|
+
process.stderr.write(
|
|
29878
|
+
`[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.
|
|
29879
|
+
`
|
|
29880
|
+
);
|
|
29881
|
+
}
|
|
29882
|
+
async function quarantineRule(gateway, rule, worstMs, detail) {
|
|
29883
|
+
const key = ruleProbeKey(rule);
|
|
29884
|
+
let cached2 = false;
|
|
29885
|
+
if (key !== void 0) {
|
|
29886
|
+
try {
|
|
29887
|
+
await gateway.setRuleProbeVerdict(key, "quarantined", worstMs);
|
|
29888
|
+
cached2 = true;
|
|
29889
|
+
} catch {
|
|
29890
|
+
}
|
|
29891
|
+
}
|
|
29892
|
+
warn(rule, "quarantined", detail, cached2);
|
|
29893
|
+
}
|
|
29894
|
+
async function filterUnsafeRules(rules, gateway, opts) {
|
|
29895
|
+
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
29896
|
+
const prober = opts?.prober;
|
|
29897
|
+
const passStart = performance.now();
|
|
29898
|
+
const safe = [];
|
|
29899
|
+
const unmeasurable = /* @__PURE__ */ new Map();
|
|
29900
|
+
try {
|
|
29901
|
+
for (const rule of rules) {
|
|
29902
|
+
const key = ruleProbeKey(rule);
|
|
29903
|
+
if (key === void 0) {
|
|
29904
|
+
safe.push(rule);
|
|
29905
|
+
continue;
|
|
29906
|
+
}
|
|
29907
|
+
let cached2;
|
|
29908
|
+
try {
|
|
29909
|
+
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
29910
|
+
} catch {
|
|
29911
|
+
cached2 = void 0;
|
|
29912
|
+
}
|
|
29913
|
+
if (cached2) {
|
|
29914
|
+
if (cached2.verdict === "safe") safe.push(rule);
|
|
29915
|
+
else warnQuarantined(rule, cached2.worstProbeMs, true);
|
|
29916
|
+
continue;
|
|
29917
|
+
}
|
|
29918
|
+
if (performance.now() - passStart >= passBudgetMs) {
|
|
29919
|
+
warnUnmeasured(rule);
|
|
29920
|
+
continue;
|
|
29921
|
+
}
|
|
29922
|
+
let isSafe;
|
|
29923
|
+
let worstMs;
|
|
29924
|
+
if (prober) {
|
|
29925
|
+
const outcome = await prober.probe(rule);
|
|
29926
|
+
if (outcome.status === "unavailable") {
|
|
29927
|
+
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29928
|
+
continue;
|
|
29929
|
+
}
|
|
29930
|
+
isSafe = outcome.status === "ok" ? outcome.safe : false;
|
|
29931
|
+
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
29932
|
+
} else {
|
|
29933
|
+
try {
|
|
29934
|
+
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
29935
|
+
} catch {
|
|
29936
|
+
isSafe = false;
|
|
29937
|
+
worstMs = Number.POSITIVE_INFINITY;
|
|
29938
|
+
}
|
|
29939
|
+
}
|
|
29940
|
+
let persisted = false;
|
|
29941
|
+
try {
|
|
29942
|
+
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
29943
|
+
persisted = true;
|
|
29944
|
+
} catch {
|
|
29945
|
+
}
|
|
29946
|
+
if (isSafe) safe.push(rule);
|
|
29947
|
+
else warnQuarantined(rule, worstMs, persisted);
|
|
29948
|
+
}
|
|
29949
|
+
} finally {
|
|
29950
|
+
for (const [reason, count] of unmeasurable) warnUnmeasurable(reason, count);
|
|
29951
|
+
}
|
|
29952
|
+
return safe;
|
|
29953
|
+
}
|
|
29954
|
+
|
|
29955
|
+
// ../../packages/plugin-sdk/src/guarded-scan.ts
|
|
29956
|
+
var DEFAULT_DEGRADE_SCOPE = "the rest of this process";
|
|
29957
|
+
function warnDegraded(scope, dropped, detail) {
|
|
29958
|
+
process.stderr.write(
|
|
29959
|
+
`[aka] isolated scanning is off for ${scope}: ${detail}. ${String(dropped)} pulled/custom-pack rule(s) are excluded; the built-in packs still run.
|
|
29960
|
+
`
|
|
29961
|
+
);
|
|
29962
|
+
}
|
|
29963
|
+
function createGuardedScanner(partition, gateway, opts) {
|
|
29964
|
+
const degradeScope = opts?.degradeScope ?? DEFAULT_DEGRADE_SCOPE;
|
|
29965
|
+
const verified = partition.verified;
|
|
29966
|
+
let unverified = partition.unverified;
|
|
29967
|
+
let isolated = unverified.length > 0 ? createIsolatedScanner({ verified, unverified }, opts) : void 0;
|
|
29968
|
+
let retired = false;
|
|
29969
|
+
function inProcess(text, context) {
|
|
29970
|
+
return scan(text, verified, context);
|
|
29971
|
+
}
|
|
29972
|
+
async function retire() {
|
|
29973
|
+
const live = isolated;
|
|
29974
|
+
isolated = void 0;
|
|
29975
|
+
unverified = [];
|
|
29976
|
+
if (live) await live.close();
|
|
29977
|
+
}
|
|
29978
|
+
async function degrade() {
|
|
29979
|
+
retired = true;
|
|
29980
|
+
await retire();
|
|
29981
|
+
}
|
|
29982
|
+
async function attempt(active, text, context, attribute) {
|
|
29983
|
+
try {
|
|
29984
|
+
return await active.scan(text, context, { attribute });
|
|
29985
|
+
} catch (error51) {
|
|
29986
|
+
return {
|
|
29987
|
+
status: "unavailable",
|
|
29988
|
+
reason: error51 instanceof Error ? error51.message : "the scan worker failed unexpectedly"
|
|
29989
|
+
};
|
|
29990
|
+
}
|
|
29991
|
+
}
|
|
29992
|
+
async function guardedScan(text, context) {
|
|
29993
|
+
const active = isolated;
|
|
29994
|
+
if (!active) return inProcess(text, context);
|
|
29995
|
+
let outcome = await attempt(active, text, context, false);
|
|
29996
|
+
if (outcome.status === "ok") return outcome.findings;
|
|
29997
|
+
if (outcome.status === "timeout") outcome = await attempt(active, text, context, true);
|
|
29998
|
+
const dropped = unverified.length;
|
|
29999
|
+
if (outcome.status === "ok") {
|
|
30000
|
+
warnDegraded(
|
|
30001
|
+
degradeScope,
|
|
30002
|
+
dropped,
|
|
30003
|
+
"a scan overran its bound once and no rule could be held responsible"
|
|
30004
|
+
);
|
|
30005
|
+
const findings = outcome.findings;
|
|
30006
|
+
await degrade();
|
|
30007
|
+
return findings;
|
|
30008
|
+
}
|
|
30009
|
+
if (outcome.status === "timeout") {
|
|
30010
|
+
const culprit = outcome.culpritIndex === void 0 ? void 0 : unverified[outcome.culpritIndex];
|
|
30011
|
+
if (culprit) {
|
|
30012
|
+
await quarantineRule(
|
|
30013
|
+
gateway,
|
|
30014
|
+
culprit,
|
|
30015
|
+
outcome.elapsedMs,
|
|
30016
|
+
`it did not finish within the ${outcome.elapsedMs.toFixed(0)}ms isolated-scan bound and was terminated; excluded from every later scan.`
|
|
30017
|
+
);
|
|
30018
|
+
}
|
|
30019
|
+
warnDegraded(
|
|
30020
|
+
degradeScope,
|
|
30021
|
+
dropped,
|
|
30022
|
+
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`
|
|
30023
|
+
);
|
|
30024
|
+
} else {
|
|
30025
|
+
warnDegraded(degradeScope, dropped, outcome.reason);
|
|
30026
|
+
}
|
|
30027
|
+
await degrade();
|
|
30028
|
+
return inProcess(text, context);
|
|
30029
|
+
}
|
|
30030
|
+
return {
|
|
30031
|
+
scan: guardedScan,
|
|
30032
|
+
degraded: () => retired,
|
|
30033
|
+
async close() {
|
|
30034
|
+
await retire();
|
|
28457
30035
|
}
|
|
28458
30036
|
};
|
|
28459
30037
|
}
|
|
28460
30038
|
|
|
28461
30039
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
28462
|
-
import { arch, hostname as
|
|
30040
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
28463
30041
|
|
|
28464
30042
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
28465
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
28466
|
-
import { join as
|
|
30043
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
30044
|
+
import { join as join11 } from "path";
|
|
28467
30045
|
|
|
28468
30046
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
28469
|
-
import { readdirSync as
|
|
28470
|
-
import { basename as
|
|
30047
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
30048
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
28471
30049
|
|
|
28472
30050
|
// ../../packages/plugin-sdk/src/posture.ts
|
|
28473
30051
|
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
@@ -28481,8 +30059,34 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
28481
30059
|
|
|
28482
30060
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
28483
30061
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
28484
|
-
import { existsSync as
|
|
28485
|
-
import { basename as
|
|
30062
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
30063
|
+
import { basename as basename5, join as join12 } from "path";
|
|
30064
|
+
|
|
30065
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30066
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
30067
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30068
|
+
return v;
|
|
30069
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30070
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
30071
|
+
if (typeof v !== "string") return false;
|
|
30072
|
+
const normalized = v.trim().toLowerCase();
|
|
30073
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
30074
|
+
}, external_exports.boolean()).catch(false);
|
|
30075
|
+
var antigravityProviderEnvShape = {
|
|
30076
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
30077
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
30078
|
+
};
|
|
30079
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
30080
|
+
|
|
30081
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
30082
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
30083
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30084
|
+
return v;
|
|
30085
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30086
|
+
var codexProviderEnvShape = {
|
|
30087
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
30088
|
+
};
|
|
30089
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
28486
30090
|
|
|
28487
30091
|
// ../../packages/plugin-sdk/src/raw-egress.ts
|
|
28488
30092
|
var MIN_RAW_LEN = 4;
|
|
@@ -28494,61 +30098,8 @@ function safeMaskedMatch(rawMatch) {
|
|
|
28494
30098
|
return masked;
|
|
28495
30099
|
}
|
|
28496
30100
|
|
|
28497
|
-
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
28498
|
-
var PASS_BUDGET_MS = 2e3;
|
|
28499
|
-
function ruleProbeKey(rule) {
|
|
28500
|
-
if (rule.matcher.type !== "regex") return void 0;
|
|
28501
|
-
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
28502
|
-
}
|
|
28503
|
-
function warnQuarantined(rule, worstMs) {
|
|
28504
|
-
const timing = worstMs === void 0 ? "not verified in time" : `${worstMs.toFixed(1)}ms`;
|
|
28505
|
-
process.stderr.write(
|
|
28506
|
-
`[aka] quarantined rule "${rule.id}": regex matcher exceeded the ReDoS timing budget (${timing}); excluded from this scan.
|
|
28507
|
-
`
|
|
28508
|
-
);
|
|
28509
|
-
}
|
|
28510
|
-
async function filterUnsafeRules(rules, gateway, opts) {
|
|
28511
|
-
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
28512
|
-
const passStart = performance.now();
|
|
28513
|
-
const safe = [];
|
|
28514
|
-
for (const rule of rules) {
|
|
28515
|
-
const key = ruleProbeKey(rule);
|
|
28516
|
-
if (key === void 0) {
|
|
28517
|
-
safe.push(rule);
|
|
28518
|
-
continue;
|
|
28519
|
-
}
|
|
28520
|
-
let cached2;
|
|
28521
|
-
try {
|
|
28522
|
-
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
28523
|
-
} catch {
|
|
28524
|
-
cached2 = void 0;
|
|
28525
|
-
}
|
|
28526
|
-
if (cached2) {
|
|
28527
|
-
if (cached2.verdict === "safe") safe.push(rule);
|
|
28528
|
-
else warnQuarantined(rule, cached2.worstProbeMs);
|
|
28529
|
-
continue;
|
|
28530
|
-
}
|
|
28531
|
-
if (performance.now() - passStart >= passBudgetMs) {
|
|
28532
|
-
warnQuarantined(rule, void 0);
|
|
28533
|
-
continue;
|
|
28534
|
-
}
|
|
28535
|
-
let isSafe;
|
|
28536
|
-
let worstMs;
|
|
28537
|
-
try {
|
|
28538
|
-
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
28539
|
-
} catch {
|
|
28540
|
-
isSafe = false;
|
|
28541
|
-
worstMs = Number.POSITIVE_INFINITY;
|
|
28542
|
-
}
|
|
28543
|
-
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
28544
|
-
if (isSafe) safe.push(rule);
|
|
28545
|
-
else warnQuarantined(rule, worstMs);
|
|
28546
|
-
}
|
|
28547
|
-
return safe;
|
|
28548
|
-
}
|
|
28549
|
-
|
|
28550
30101
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28551
|
-
import { randomUUID as
|
|
30102
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28552
30103
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
28553
30104
|
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
28554
30105
|
function entryIsActive(entry, now) {
|
|
@@ -28573,6 +30124,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28573
30124
|
const dataDir2 = opts?.dataDir;
|
|
28574
30125
|
let policies = [];
|
|
28575
30126
|
let rules = [];
|
|
30127
|
+
let scanner;
|
|
28576
30128
|
let bundleExceptions = [];
|
|
28577
30129
|
let initialized = false;
|
|
28578
30130
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
@@ -28598,8 +30150,24 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28598
30150
|
return key !== void 0 && bundledProbeKeys.has(key);
|
|
28599
30151
|
});
|
|
28600
30152
|
const needsGate = incoming.filter((rule) => !ciVerified.includes(rule));
|
|
28601
|
-
|
|
28602
|
-
|
|
30153
|
+
let prober;
|
|
30154
|
+
const gated = await filterUnsafeRules(needsGate, gateway, {
|
|
30155
|
+
prober: {
|
|
30156
|
+
probe: (rule) => {
|
|
30157
|
+
prober ??= createIsolatedScanner({ verified: [], unverified: [] }, opts?.scanIsolation);
|
|
30158
|
+
return prober.probe(rule);
|
|
30159
|
+
}
|
|
30160
|
+
}
|
|
30161
|
+
});
|
|
30162
|
+
await prober?.close();
|
|
30163
|
+
const verified = bundle.rulesComplete ? [...ciVerified] : [...getLoadedRules(), ...ciVerified];
|
|
30164
|
+
const unverified = [];
|
|
30165
|
+
for (const rule of gated) {
|
|
30166
|
+
if (rule.matcher.type === "regex") unverified.push(rule);
|
|
30167
|
+
else verified.push(rule);
|
|
30168
|
+
}
|
|
30169
|
+
rules = [...verified, ...unverified];
|
|
30170
|
+
scanner = createGuardedScanner({ verified, unverified }, gateway, opts?.scanIsolation);
|
|
28603
30171
|
bundleExceptions = bundle.exceptions ?? [];
|
|
28604
30172
|
initialized = true;
|
|
28605
30173
|
}
|
|
@@ -28739,7 +30307,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28739
30307
|
const pair = `${finding.ruleId}:${fp}`;
|
|
28740
30308
|
if (seen.has(pair)) continue;
|
|
28741
30309
|
seen.add(pair);
|
|
28742
|
-
const reference =
|
|
30310
|
+
const reference = randomUUID14().replaceAll("-", "").slice(0, 6);
|
|
28743
30311
|
const maskedValue = maskMatch(finding.rawMatch);
|
|
28744
30312
|
try {
|
|
28745
30313
|
await gateway.recordBlockedDetection({
|
|
@@ -28763,8 +30331,10 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28763
30331
|
async function evaluate(text, context, ctx) {
|
|
28764
30332
|
try {
|
|
28765
30333
|
await ensureInitialized();
|
|
30334
|
+
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
28766
30335
|
const shielded = shieldPointers(text);
|
|
28767
|
-
const
|
|
30336
|
+
const matched = await scanner.scan(shielded.text, context);
|
|
30337
|
+
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
28768
30338
|
const fpCache = /* @__PURE__ */ new Map();
|
|
28769
30339
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
28770
30340
|
const decision = decide(findings, text, excepted);
|
|
@@ -28821,7 +30391,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28821
30391
|
valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
|
|
28822
30392
|
}) : void 0;
|
|
28823
30393
|
return {
|
|
28824
|
-
id:
|
|
30394
|
+
id: randomUUID14(),
|
|
28825
30395
|
eventId: event.id,
|
|
28826
30396
|
ruleId: match.ruleId,
|
|
28827
30397
|
category: match.category,
|
|
@@ -28847,21 +30417,28 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28847
30417
|
const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
28848
30418
|
return contentHashOf(JSON.stringify(sorted));
|
|
28849
30419
|
} catch {
|
|
28850
|
-
return `unresolved-${
|
|
30420
|
+
return `unresolved-${randomUUID14()}`;
|
|
28851
30421
|
}
|
|
28852
30422
|
}
|
|
30423
|
+
function scanIsolationDegraded() {
|
|
30424
|
+
return scanner?.degraded() ?? false;
|
|
30425
|
+
}
|
|
28853
30426
|
async function close() {
|
|
30427
|
+
try {
|
|
30428
|
+
await scanner?.close();
|
|
30429
|
+
} catch {
|
|
30430
|
+
}
|
|
28854
30431
|
await gateway.close();
|
|
28855
30432
|
}
|
|
28856
|
-
return { processText, capture, rulesetFingerprint, close };
|
|
30433
|
+
return { processText, capture, rulesetFingerprint, scanIsolationDegraded, close };
|
|
28857
30434
|
}
|
|
28858
30435
|
|
|
28859
30436
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28860
30437
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28861
30438
|
|
|
28862
30439
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28863
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
28864
|
-
import { join as
|
|
30440
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
30441
|
+
import { join as join13 } from "path";
|
|
28865
30442
|
|
|
28866
30443
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
28867
30444
|
function redactedPlaceholder(category) {
|
|
@@ -29103,7 +30680,6 @@ function createVaultGlue(options) {
|
|
|
29103
30680
|
const vault = new SecretVault({
|
|
29104
30681
|
repo: db.secretVault,
|
|
29105
30682
|
keys: createKeyProvider(settings.vaultKeyCustody, keysDir(base)),
|
|
29106
|
-
fingerprintKey: loadOrCreateFingerprintKey(dir),
|
|
29107
30683
|
// Read live so a revocation applies to the very next call, not the next
|
|
29108
30684
|
// process.
|
|
29109
30685
|
isConsented: () => isVaultConsentValid(readWorkspaceSettings(base).vaultConsent),
|
|
@@ -29124,8 +30700,10 @@ function createVaultGlue(options) {
|
|
|
29124
30700
|
return decision.allow;
|
|
29125
30701
|
}
|
|
29126
30702
|
});
|
|
30703
|
+
let fingerprintKey;
|
|
30704
|
+
const fingerprintKeyForWrite = () => fingerprintKey ??= loadOrCreateFingerprintKey(dir);
|
|
29127
30705
|
const vaultWithSightings = {
|
|
29128
|
-
tokenize: (raw, meta3) => vault.tokenize(raw, meta3),
|
|
30706
|
+
tokenize: (raw, meta3) => vault.tokenize(raw, meta3, fingerprintKeyForWrite),
|
|
29129
30707
|
detokenize: (token, opts) => vault.detokenize(token, opts),
|
|
29130
30708
|
describePointer: (token) => vault.describePointer(token),
|
|
29131
30709
|
resolvePointerIdentity: (token) => vault.resolvePointerIdentity(token),
|
|
@@ -29158,132 +30736,7 @@ var UNOPENABLE_VAULT = {
|
|
|
29158
30736
|
resolvePointerIdentity: () => Promise.resolve(null)
|
|
29159
30737
|
};
|
|
29160
30738
|
|
|
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
|
|
30739
|
+
// ../../packages/setup-wizard/src/remediation/chain.ts
|
|
29287
30740
|
var REMEDIATION_OPTIONS = [
|
|
29288
30741
|
{ id: "redact-rotation-checklist", label: "Redact + rotation checklist" },
|
|
29289
30742
|
{ id: "redact-only", label: "Redact only" },
|
|
@@ -29322,9 +30775,9 @@ function routeRemediationOption(option, handlers) {
|
|
|
29322
30775
|
}
|
|
29323
30776
|
}
|
|
29324
30777
|
|
|
29325
|
-
// src/remediation/rotation-checklist.ts
|
|
29326
|
-
import { writeFileSync as
|
|
29327
|
-
import { join as
|
|
30778
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
30779
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
30780
|
+
import { join as join14 } from "path";
|
|
29328
30781
|
var GENERIC_CONSOLE_PATH = "rotate via the provider's own console";
|
|
29329
30782
|
var CONSOLE_PATHS = {
|
|
29330
30783
|
anthropic: "console.anthropic.com \u2192 Settings \u2192 API keys",
|
|
@@ -29409,7 +30862,7 @@ function renderRotationChecklistResolvedLine(location) {
|
|
|
29409
30862
|
return `\u2713 I drafted a rotation checklist for you (${location}).`;
|
|
29410
30863
|
}
|
|
29411
30864
|
function writeRotationChecklist(entries, targetDirectory) {
|
|
29412
|
-
|
|
30865
|
+
writeFileSync7(
|
|
29413
30866
|
`${targetDirectory}/rotation-checklist.md`,
|
|
29414
30867
|
renderChecklistMarkdown(entries),
|
|
29415
30868
|
"utf8"
|
|
@@ -29424,7 +30877,7 @@ function generateRotationChecklist(input) {
|
|
|
29424
30877
|
try {
|
|
29425
30878
|
const target = resolveRotationChecklistTarget(input.cwd);
|
|
29426
30879
|
targetDirectory = target.directory;
|
|
29427
|
-
const filePath =
|
|
30880
|
+
const filePath = join14(target.directory, "rotation-checklist.md");
|
|
29428
30881
|
writeRotationChecklist(input.entries, target.directory);
|
|
29429
30882
|
return {
|
|
29430
30883
|
status: "written",
|
|
@@ -29440,25 +30893,7 @@ function generateRotationChecklist(input) {
|
|
|
29440
30893
|
}
|
|
29441
30894
|
}
|
|
29442
30895
|
|
|
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
|
-
}
|
|
30896
|
+
// ../../packages/setup-wizard/src/remediation/redaction-summary.ts
|
|
29462
30897
|
function renderRedactionConfirmation(redactedKeys) {
|
|
29463
30898
|
const noun = redactedKeys === 1 ? "key" : "keys";
|
|
29464
30899
|
return `\u2713 Redacted ${String(redactedKeys)} ${noun}`;
|
|
@@ -29480,8 +30915,8 @@ function renderRedactionOutcome(input) {
|
|
|
29480
30915
|
const totalKeys = input.findings.length;
|
|
29481
30916
|
const isComplete = input.redactedKeys === totalKeys && input.unredactedFindings.length === 0;
|
|
29482
30917
|
if (isComplete) {
|
|
29483
|
-
const
|
|
29484
|
-
return pointeredKeys === 0 ?
|
|
30918
|
+
const confirmation2 = `${renderRedactionConfirmation(input.redactedKeys)}.`;
|
|
30919
|
+
return pointeredKeys === 0 ? confirmation2 : `${confirmation2} ${renderPointeredNote(pointeredKeys)}`;
|
|
29485
30920
|
}
|
|
29486
30921
|
const partialLine = renderPartialRedactionLine(
|
|
29487
30922
|
input.redactedKeys,
|
|
@@ -29513,7 +30948,7 @@ function renderResolvedSummary(input) {
|
|
|
29513
30948
|
return ["Leaked secrets \u2014 resolved", redactionLine, checklistLine, "", preview].join("\n");
|
|
29514
30949
|
}
|
|
29515
30950
|
|
|
29516
|
-
// src/remediation/deliverable.ts
|
|
30951
|
+
// ../../packages/setup-wizard/src/remediation/deliverable.ts
|
|
29517
30952
|
function resolveRemediationDeliverable(input) {
|
|
29518
30953
|
const pointeredKeys = input.pointeredKeys ?? 0;
|
|
29519
30954
|
const unredactedFindings = input.unredactedFindings ?? [];
|
|
@@ -29537,6 +30972,192 @@ function resolveRemediationDeliverable(input) {
|
|
|
29537
30972
|
return { summary, writeResult, entries };
|
|
29538
30973
|
}
|
|
29539
30974
|
|
|
30975
|
+
// ../../packages/setup-wizard/src/remediation/posture.ts
|
|
30976
|
+
function writeStandingSecretPosture(level, policies) {
|
|
30977
|
+
try {
|
|
30978
|
+
applyCategoryPosture({ secret: level }, policies, "overwrite");
|
|
30979
|
+
return { persisted: true, level };
|
|
30980
|
+
} catch {
|
|
30981
|
+
return { persisted: false };
|
|
30982
|
+
}
|
|
30983
|
+
}
|
|
30984
|
+
|
|
30985
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
30986
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
30987
|
+
import { tmpdir } from "os";
|
|
30988
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
30989
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
30990
|
+
ruleId: external_exports.string(),
|
|
30991
|
+
category: DetectionCategory,
|
|
30992
|
+
valueFingerprint: external_exports.string(),
|
|
30993
|
+
keyVersion: external_exports.number(),
|
|
30994
|
+
maskedValue: external_exports.string(),
|
|
30995
|
+
justification: external_exports.string()
|
|
30996
|
+
});
|
|
30997
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
30998
|
+
category: DetectionCategory,
|
|
30999
|
+
action: BuiltinPolicyId,
|
|
31000
|
+
genuineCount: external_exports.number(),
|
|
31001
|
+
fpCount: external_exports.number(),
|
|
31002
|
+
reasoning: external_exports.string()
|
|
31003
|
+
});
|
|
31004
|
+
var JoinEntrySchema = external_exports.object({
|
|
31005
|
+
id: external_exports.string(),
|
|
31006
|
+
ruleId: external_exports.string(),
|
|
31007
|
+
category: DetectionCategory,
|
|
31008
|
+
valueFingerprint: external_exports.string().optional(),
|
|
31009
|
+
keyVersion: external_exports.number().optional(),
|
|
31010
|
+
maskedMatch: external_exports.string(),
|
|
31011
|
+
maskedContext: external_exports.string()
|
|
31012
|
+
});
|
|
31013
|
+
var PLAN_FILE_VERSION = 3;
|
|
31014
|
+
var PersistedPlanSchema = external_exports.object({
|
|
31015
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
31016
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
31017
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
31018
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
31019
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
31020
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
31021
|
+
join: external_exports.array(JoinEntrySchema),
|
|
31022
|
+
notes: external_exports.string(),
|
|
31023
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
31024
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
31025
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
31026
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
31027
|
+
});
|
|
31028
|
+
|
|
31029
|
+
// ../../packages/setup-wizard/src/triage/surfaced-secrets.ts
|
|
31030
|
+
function deriveProvider(ruleId) {
|
|
31031
|
+
const slug = (ruleId.split("/").pop() ?? ruleId).split(".").pop() ?? ruleId;
|
|
31032
|
+
const provider = slug.split("-")[0] ?? slug;
|
|
31033
|
+
return provider === "" ? "unknown" : provider;
|
|
31034
|
+
}
|
|
31035
|
+
|
|
31036
|
+
// src/command-registry.ts
|
|
31037
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
31038
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
31039
|
+
var COMMAND_NAMESPACE = "aka";
|
|
31040
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
31041
|
+
function readRegisteredCommands() {
|
|
31042
|
+
return readdirSync5(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
31043
|
+
}
|
|
31044
|
+
function selectRegisteredCommands(curated, registry2) {
|
|
31045
|
+
const registered = new Set(registry2);
|
|
31046
|
+
const missing = curated.filter((c) => !registered.has(c));
|
|
31047
|
+
if (missing.length > 0) {
|
|
31048
|
+
throw new Error(
|
|
31049
|
+
`Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
|
|
31050
|
+
);
|
|
31051
|
+
}
|
|
31052
|
+
return [...curated];
|
|
31053
|
+
}
|
|
31054
|
+
var SECRET_SCAN_CONTINUATION = ["/aka:secretscan", "/aka:scan"];
|
|
31055
|
+
function selectSecretScanContinuation(registry2 = readRegisteredCommands()) {
|
|
31056
|
+
const curated = SECRET_SCAN_CONTINUATION.find((c) => registry2.includes(c));
|
|
31057
|
+
if (curated === void 0) {
|
|
31058
|
+
throw new Error(
|
|
31059
|
+
`No secret-scan continuation command registered in the installed plugin (looked for ${SECRET_SCAN_CONTINUATION.join(", ")})`
|
|
31060
|
+
);
|
|
31061
|
+
}
|
|
31062
|
+
const [selected = curated] = selectRegisteredCommands([curated], registry2);
|
|
31063
|
+
return selected;
|
|
31064
|
+
}
|
|
31065
|
+
|
|
31066
|
+
// src/setup-frame-json.ts
|
|
31067
|
+
var FRAME_JSON_BEGIN = "<<<AKA_FRAME_JSON";
|
|
31068
|
+
var FRAME_JSON_END = "AKA_FRAME_JSON>>>";
|
|
31069
|
+
function frameJsonBlock(payload) {
|
|
31070
|
+
return `${FRAME_JSON_BEGIN}
|
|
31071
|
+
${JSON.stringify(payload)}
|
|
31072
|
+
${FRAME_JSON_END}
|
|
31073
|
+
`;
|
|
31074
|
+
}
|
|
31075
|
+
function readFrameJsonBlock(stdout) {
|
|
31076
|
+
const begin = stdout.indexOf(FRAME_JSON_BEGIN);
|
|
31077
|
+
if (begin === -1) return void 0;
|
|
31078
|
+
const from = begin + FRAME_JSON_BEGIN.length;
|
|
31079
|
+
const end = stdout.indexOf(FRAME_JSON_END, from);
|
|
31080
|
+
if (end === -1) return void 0;
|
|
31081
|
+
try {
|
|
31082
|
+
return JSON.parse(stdout.slice(from, end).trim());
|
|
31083
|
+
} catch {
|
|
31084
|
+
return void 0;
|
|
31085
|
+
}
|
|
31086
|
+
}
|
|
31087
|
+
|
|
31088
|
+
// src/setup-show.ts
|
|
31089
|
+
var SHOW_BEGIN = "<<<AKA_SHOW";
|
|
31090
|
+
var SHOW_END = "AKA_SHOW>>>";
|
|
31091
|
+
function showBlock(body) {
|
|
31092
|
+
return `${SHOW_BEGIN}
|
|
31093
|
+
${body}
|
|
31094
|
+
${SHOW_END}
|
|
31095
|
+
`;
|
|
31096
|
+
}
|
|
31097
|
+
|
|
31098
|
+
// src/present.ts
|
|
31099
|
+
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
31100
|
+
function visibleLength(text) {
|
|
31101
|
+
return text.replace(ANSI_RE, "").length;
|
|
31102
|
+
}
|
|
31103
|
+
var fg = (hex3) => (text) => {
|
|
31104
|
+
const r = Number.parseInt(hex3.slice(1, 3), 16);
|
|
31105
|
+
const g = Number.parseInt(hex3.slice(3, 5), 16);
|
|
31106
|
+
const b = Number.parseInt(hex3.slice(5, 7), 16);
|
|
31107
|
+
return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
|
|
31108
|
+
};
|
|
31109
|
+
var paint = {
|
|
31110
|
+
brand: fg("#33e6c6"),
|
|
31111
|
+
// --color-brand · ▸▸ AKA wordmark (accent text)
|
|
31112
|
+
dim: fg("#838995"),
|
|
31113
|
+
// --color-text-3 · separators · "/100" · the "unreviewed" label
|
|
31114
|
+
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
31115
|
+
// the health score number
|
|
31116
|
+
ok: fg("#0db15f"),
|
|
31117
|
+
// --color-ok · healthy ● dot
|
|
31118
|
+
critical: fg("#e63448"),
|
|
31119
|
+
// --color-sev-critical · ■ and the open-findings flag
|
|
31120
|
+
high: fg("#e97a0a"),
|
|
31121
|
+
// --color-sev-high · ■ and the mid-health dot
|
|
31122
|
+
medium: fg("#f7bd00"),
|
|
31123
|
+
// --color-sev-medium · ■
|
|
31124
|
+
low: fg("#0581d4")
|
|
31125
|
+
// --color-sev-low · ■ (azure blue, not purple)
|
|
31126
|
+
};
|
|
31127
|
+
function padEnd(text, width) {
|
|
31128
|
+
const pad = width - visibleLength(text);
|
|
31129
|
+
return pad > 0 ? text + " ".repeat(pad) : text;
|
|
31130
|
+
}
|
|
31131
|
+
function table(headers, rows, opts = {}) {
|
|
31132
|
+
const gap = opts.gap ?? 3;
|
|
31133
|
+
const widths = headers.map(
|
|
31134
|
+
(h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
|
|
31135
|
+
);
|
|
31136
|
+
const sep4 = " ".repeat(gap);
|
|
31137
|
+
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep4);
|
|
31138
|
+
const headerLine = fmt(headers.map((h) => h.toUpperCase()));
|
|
31139
|
+
if (opts.rowSep === true) {
|
|
31140
|
+
const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
31141
|
+
const rule = "\u2500".repeat(fullWidth);
|
|
31142
|
+
const body = [];
|
|
31143
|
+
rows.forEach((row, i) => {
|
|
31144
|
+
if (i > 0) body.push(rule);
|
|
31145
|
+
body.push(fmt(row));
|
|
31146
|
+
});
|
|
31147
|
+
return [headerLine, rule, ...body].join("\n");
|
|
31148
|
+
}
|
|
31149
|
+
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep4);
|
|
31150
|
+
return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
|
|
31151
|
+
}
|
|
31152
|
+
function fenced(body) {
|
|
31153
|
+
const longestRun = Math.max(0, ...[...body.matchAll(/`+/g)].map((m) => m[0].length));
|
|
31154
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
31155
|
+
return [fence, body, fence].join("\n");
|
|
31156
|
+
}
|
|
31157
|
+
function show(body) {
|
|
31158
|
+
return showBlock(body);
|
|
31159
|
+
}
|
|
31160
|
+
|
|
29540
31161
|
// src/remediation/findings.ts
|
|
29541
31162
|
function loadSecretLeakFindings(readCalibrationFrame) {
|
|
29542
31163
|
try {
|
|
@@ -29547,21 +31168,31 @@ function loadSecretLeakFindings(readCalibrationFrame) {
|
|
|
29547
31168
|
}
|
|
29548
31169
|
}
|
|
29549
31170
|
|
|
29550
|
-
// src/remediation/
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
|
|
29555
|
-
|
|
29556
|
-
|
|
29557
|
-
|
|
31171
|
+
// src/remediation/render.ts
|
|
31172
|
+
var RECOMMENDATION_LINE = "I'd redact them and get you rotating, most-exposed first";
|
|
31173
|
+
var STATE_LABEL = {
|
|
31174
|
+
"still-valid": "still valid",
|
|
31175
|
+
unknown: "unknown",
|
|
31176
|
+
invalid: "invalid"
|
|
31177
|
+
};
|
|
31178
|
+
function renderRemediationDecision(findings, moreCount, registry2) {
|
|
31179
|
+
const scanCommand = selectSecretScanContinuation(registry2);
|
|
31180
|
+
const rows = findings.map((f) => [
|
|
31181
|
+
f.provider,
|
|
31182
|
+
f.maskedToken,
|
|
31183
|
+
f.where.filePath,
|
|
31184
|
+
STATE_LABEL[f.state]
|
|
31185
|
+
]);
|
|
31186
|
+
const findingTable = table(["Provider", "Token", "Where", "State"], rows, { rowSep: true });
|
|
31187
|
+
const chainingLine = `${String(moreCount)} more worth a look \u2014 run ${scanCommand} when you're ready.`;
|
|
31188
|
+
return [findingTable, "", RECOMMENDATION_LINE, "", chainingLine].join("\n");
|
|
29558
31189
|
}
|
|
29559
31190
|
|
|
29560
31191
|
// src/remediation/surfaced-redact.ts
|
|
29561
|
-
import { readFileSync as
|
|
31192
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
29562
31193
|
|
|
29563
31194
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
29564
|
-
import { randomUUID as
|
|
31195
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
29565
31196
|
|
|
29566
31197
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
29567
31198
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -29723,7 +31354,7 @@ var StandaloneDataGateway = class {
|
|
|
29723
31354
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
29724
31355
|
const installed = this.installedScanRules();
|
|
29725
31356
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
29726
|
-
id:
|
|
31357
|
+
id: randomUUID15(),
|
|
29727
31358
|
scope: "global",
|
|
29728
31359
|
target: { ruleId },
|
|
29729
31360
|
action,
|
|
@@ -29876,28 +31507,21 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
29876
31507
|
}
|
|
29877
31508
|
|
|
29878
31509
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
29879
|
-
import { randomUUID as
|
|
31510
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
29880
31511
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
29881
31512
|
|
|
29882
31513
|
// src/history/transcripts.ts
|
|
29883
|
-
import { readdirSync as
|
|
31514
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync10 } from "fs";
|
|
29884
31515
|
import { homedir as homedir3 } from "os";
|
|
29885
|
-
import { join as
|
|
31516
|
+
import { join as join16 } from "path";
|
|
29886
31517
|
function transcriptsDir(home) {
|
|
29887
|
-
return
|
|
31518
|
+
return join16(home ?? homedir3(), ".claude", "projects");
|
|
29888
31519
|
}
|
|
29889
31520
|
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
29890
31521
|
|
|
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
31522
|
// src/remediation/redact.ts
|
|
29899
|
-
import { readFileSync as
|
|
29900
|
-
import { isAbsolute as isAbsolute2, relative
|
|
31523
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
31524
|
+
import { isAbsolute as isAbsolute2, relative, resolve } from "path";
|
|
29901
31525
|
var REDACTED_PLACEHOLDER = "[REDACTED:SECRET]";
|
|
29902
31526
|
var REPLACE_PATTERN_SEQUENCE = /\$[$&`'<0-9]/;
|
|
29903
31527
|
function replacementFor(rawValue, replacements) {
|
|
@@ -29920,7 +31544,7 @@ function realPathOrNull(path) {
|
|
|
29920
31544
|
function isWithinRoot(realTarget, root) {
|
|
29921
31545
|
const realRoot = realPathOrNull(root);
|
|
29922
31546
|
if (realRoot === null) return false;
|
|
29923
|
-
const rel =
|
|
31547
|
+
const rel = relative(realRoot, realTarget);
|
|
29924
31548
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
29925
31549
|
}
|
|
29926
31550
|
function resolveRedactableArtifact(filePath, scope) {
|
|
@@ -29944,7 +31568,7 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
|
|
|
29944
31568
|
for (const [filePath, fileTargets] of byFile) {
|
|
29945
31569
|
let content;
|
|
29946
31570
|
try {
|
|
29947
|
-
content =
|
|
31571
|
+
content = readFileSync11(filePath, "utf8");
|
|
29948
31572
|
} catch {
|
|
29949
31573
|
continue;
|
|
29950
31574
|
}
|
|
@@ -29974,11 +31598,11 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
|
|
|
29974
31598
|
if (struckHere.length === 0) continue;
|
|
29975
31599
|
const tmpPath = `${filePath}.aka-redact.tmp`;
|
|
29976
31600
|
try {
|
|
29977
|
-
|
|
31601
|
+
writeFileSync9(tmpPath, content);
|
|
29978
31602
|
renameSync5(tmpPath, filePath);
|
|
29979
31603
|
} catch {
|
|
29980
31604
|
try {
|
|
29981
|
-
|
|
31605
|
+
rmSync6(tmpPath, { force: true, recursive: true });
|
|
29982
31606
|
} catch {
|
|
29983
31607
|
}
|
|
29984
31608
|
continue;
|
|
@@ -30067,7 +31691,7 @@ async function redactSurfacedSecrets(findings, overrides = {}) {
|
|
|
30067
31691
|
for (const [filePath, fileFindings] of byFile) {
|
|
30068
31692
|
let content;
|
|
30069
31693
|
try {
|
|
30070
|
-
content =
|
|
31694
|
+
content = readFileSync12(filePath, "utf8");
|
|
30071
31695
|
} catch {
|
|
30072
31696
|
unrecovered.push(...fileFindings);
|
|
30073
31697
|
continue;
|
|
@@ -30228,11 +31852,11 @@ async function route(frameText, rawOption, rawPosture) {
|
|
|
30228
31852
|
break;
|
|
30229
31853
|
}
|
|
30230
31854
|
}
|
|
30231
|
-
if (process.argv[1] &&
|
|
31855
|
+
if (process.argv[1] && fileURLToPath3(import.meta.url) === process.argv[1]) {
|
|
30232
31856
|
try {
|
|
30233
31857
|
const argv = process.argv.slice(2);
|
|
30234
31858
|
const optionIndex = argv.indexOf("--option");
|
|
30235
|
-
const frameText =
|
|
31859
|
+
const frameText = readFileSync13(0, "utf8");
|
|
30236
31860
|
if (optionIndex === -1) {
|
|
30237
31861
|
present(frameText);
|
|
30238
31862
|
} else {
|