@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/filescan.js
CHANGED
|
@@ -492,13 +492,12 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
495
|
+
import { existsSync as existsSync5 } from "fs";
|
|
496
|
+
import { join as join8 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/database.ts
|
|
499
|
-
import { randomUUID as
|
|
500
|
-
import {
|
|
501
|
-
import { join, sep } from "path";
|
|
499
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
500
|
+
import { join as join2, sep } from "path";
|
|
502
501
|
import { DatabaseSync } from "node:sqlite";
|
|
503
502
|
|
|
504
503
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -578,6 +577,14 @@ var SQLITE_MIGRATIONS = [
|
|
|
578
577
|
{
|
|
579
578
|
tag: "0018_serious_tana_nile",
|
|
580
579
|
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0019_audit_started_at_index",
|
|
583
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
587
|
+
sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
|
|
581
588
|
}
|
|
582
589
|
];
|
|
583
590
|
|
|
@@ -15315,7 +15322,17 @@ var Finding = external_exports.object({
|
|
|
15315
15322
|
}).meta({ id: "Finding" });
|
|
15316
15323
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15317
15324
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15318
|
-
var FindingProvider = external_exports.enum([
|
|
15325
|
+
var FindingProvider = external_exports.enum([
|
|
15326
|
+
"claudecode",
|
|
15327
|
+
"claudedesktop",
|
|
15328
|
+
"cursor",
|
|
15329
|
+
"copilot",
|
|
15330
|
+
"chatgpt",
|
|
15331
|
+
"claudeai",
|
|
15332
|
+
"codex",
|
|
15333
|
+
"antigravity",
|
|
15334
|
+
"api"
|
|
15335
|
+
]).meta({ id: "FindingProvider" });
|
|
15319
15336
|
var FindingCategory = external_exports.enum([
|
|
15320
15337
|
"secret",
|
|
15321
15338
|
"pii",
|
|
@@ -15369,7 +15386,16 @@ var FindingInstance = external_exports.object({
|
|
|
15369
15386
|
confidence: external_exports.number().min(0).max(1),
|
|
15370
15387
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15371
15388
|
// that predate the resolution feature stay valid.
|
|
15372
|
-
status: FindingStatus.optional()
|
|
15389
|
+
status: FindingStatus.optional(),
|
|
15390
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15391
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15392
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15393
|
+
// MOST RECENT detection event, not the first.
|
|
15394
|
+
eventId: external_exports.string().optional(),
|
|
15395
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15396
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15397
|
+
// outside a session.
|
|
15398
|
+
sessionId: external_exports.string().optional()
|
|
15373
15399
|
}).meta({ id: "FindingInstance" });
|
|
15374
15400
|
var FindingGroup = external_exports.object({
|
|
15375
15401
|
id: external_exports.string(),
|
|
@@ -15413,7 +15439,11 @@ var FindingFacets = external_exports.object({
|
|
|
15413
15439
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15414
15440
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15415
15441
|
// counted under no value.
|
|
15416
|
-
status: external_exports.array(FindingFacetItem)
|
|
15442
|
+
status: external_exports.array(FindingFacetItem),
|
|
15443
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15444
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15445
|
+
// because a group spans tools.
|
|
15446
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15417
15447
|
}).meta({ id: "FindingFacets" });
|
|
15418
15448
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15419
15449
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15431,6 +15461,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15431
15461
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15432
15462
|
// session → findings drilldown). Findings without a session never match.
|
|
15433
15463
|
sessionId: external_exports.string().optional(),
|
|
15464
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15465
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15466
|
+
// means all time — this list has no default window.
|
|
15467
|
+
from: external_exports.iso.datetime().optional(),
|
|
15468
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15469
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15470
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15471
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15472
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15473
|
+
includeId: external_exports.string().optional(),
|
|
15434
15474
|
groupBy: external_exports.literal("type").optional(),
|
|
15435
15475
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15436
15476
|
cursor: external_exports.string().optional()
|
|
@@ -15475,15 +15515,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15475
15515
|
detection: FindingDetectionRef,
|
|
15476
15516
|
policy: FindingPolicyRef
|
|
15477
15517
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15518
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15519
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15520
|
+
severity: external_exports.array(Severity).optional(),
|
|
15521
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15522
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15523
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15524
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15525
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15526
|
+
// the grouped query's group-level fold.
|
|
15527
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15528
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15529
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15530
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15531
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15532
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15533
|
+
repo: external_exports.string().optional(),
|
|
15534
|
+
file: external_exports.string().optional(),
|
|
15535
|
+
q: external_exports.string().optional(),
|
|
15536
|
+
sessionId: external_exports.string().optional(),
|
|
15537
|
+
from: external_exports.iso.datetime().optional(),
|
|
15538
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15539
|
+
cursor: external_exports.string().optional()
|
|
15540
|
+
});
|
|
15541
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15542
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15543
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15544
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15545
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15546
|
+
// dimension still excludes its own filter.
|
|
15547
|
+
facets: FindingFacets,
|
|
15548
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15549
|
+
nextCursor: external_exports.string().nullable()
|
|
15550
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15551
|
+
var FindingLocationFile = external_exports.object({
|
|
15552
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15553
|
+
// with no file attribution).
|
|
15554
|
+
file: external_exports.string(),
|
|
15555
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15556
|
+
maxSeverity: Severity,
|
|
15557
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15558
|
+
// Folded from the instances' derived statuses with the same
|
|
15559
|
+
// open-dominates precedence a group uses.
|
|
15560
|
+
status: FindingStatus.optional(),
|
|
15561
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15562
|
+
// chips, and the count is what conveys scale.
|
|
15563
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15564
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15565
|
+
var FindingLocationRepo = external_exports.object({
|
|
15566
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15567
|
+
repo: external_exports.string(),
|
|
15568
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15569
|
+
maxSeverity: Severity,
|
|
15570
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15571
|
+
status: FindingStatus.optional(),
|
|
15572
|
+
files: external_exports.array(FindingLocationFile)
|
|
15573
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15574
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15575
|
+
severity: external_exports.array(Severity).optional(),
|
|
15576
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15577
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15578
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15579
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15580
|
+
// instances that match, and folds its status from those.
|
|
15581
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15582
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15583
|
+
q: external_exports.string().optional(),
|
|
15584
|
+
sessionId: external_exports.string().optional(),
|
|
15585
|
+
from: external_exports.iso.datetime().optional(),
|
|
15586
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15587
|
+
});
|
|
15588
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15589
|
+
totals: external_exports.object({
|
|
15590
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15591
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15592
|
+
files: external_exports.number().int().nonnegative()
|
|
15593
|
+
}),
|
|
15594
|
+
/** Sorted by max severity, then most recent. */
|
|
15595
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15596
|
+
/** Whether `limit` truncated the repo list. */
|
|
15597
|
+
hasMore: external_exports.boolean()
|
|
15598
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15478
15599
|
|
|
15479
15600
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15480
|
-
var Harness = external_exports.enum([
|
|
15601
|
+
var Harness = external_exports.enum([
|
|
15602
|
+
"claudecode",
|
|
15603
|
+
"cursor",
|
|
15604
|
+
"copilot",
|
|
15605
|
+
"codex",
|
|
15606
|
+
"antigravity",
|
|
15607
|
+
"windsurf",
|
|
15608
|
+
"claudedesktop",
|
|
15609
|
+
"chatgpt",
|
|
15610
|
+
"claudeai",
|
|
15611
|
+
"api"
|
|
15612
|
+
]).meta({ id: "Harness" });
|
|
15481
15613
|
var TOOL_TO_HARNESS = {
|
|
15482
15614
|
"claude-code": "claudecode",
|
|
15483
15615
|
"claude-desktop": "claudedesktop",
|
|
15484
15616
|
"github-copilot": "copilot",
|
|
15485
15617
|
cursor: "cursor",
|
|
15486
|
-
chatgpt: "chatgpt"
|
|
15618
|
+
chatgpt: "chatgpt",
|
|
15619
|
+
codex: "codex",
|
|
15620
|
+
antigravity: "antigravity",
|
|
15621
|
+
"claude-ai": "claudeai"
|
|
15487
15622
|
};
|
|
15488
15623
|
|
|
15489
15624
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15941,7 +16076,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15941
16076
|
// ../../packages/schema/src/zod/event.ts
|
|
15942
16077
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15943
16078
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15944
|
-
var SourceTool = external_exports.enum([
|
|
16079
|
+
var SourceTool = external_exports.enum([
|
|
16080
|
+
"claude-code",
|
|
16081
|
+
"claude-desktop",
|
|
16082
|
+
"cursor",
|
|
16083
|
+
"chatgpt",
|
|
16084
|
+
"claude-ai",
|
|
16085
|
+
"github-copilot",
|
|
16086
|
+
"codex",
|
|
16087
|
+
"antigravity",
|
|
16088
|
+
"cli",
|
|
16089
|
+
"unknown"
|
|
16090
|
+
]).meta({ id: "SourceTool" });
|
|
15945
16091
|
var EventMetadata = external_exports.object({
|
|
15946
16092
|
sessionId: external_exports.string().optional(),
|
|
15947
16093
|
repo: external_exports.string().optional(),
|
|
@@ -16012,7 +16158,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16012
16158
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16013
16159
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16014
16160
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16015
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16161
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16016
16162
|
var AccessCounts = external_exports.object({
|
|
16017
16163
|
open: external_exports.number().int().nonnegative(),
|
|
16018
16164
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16281,6 +16427,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16281
16427
|
useCount: true,
|
|
16282
16428
|
conditions: true
|
|
16283
16429
|
});
|
|
16430
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16284
16431
|
|
|
16285
16432
|
// ../../packages/schema/src/zod/rule.ts
|
|
16286
16433
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17091,6 +17238,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17091
17238
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17092
17239
|
}).meta({ id: "EgressWriteSummary" });
|
|
17093
17240
|
|
|
17241
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17242
|
+
var confirmation = external_exports.string().optional();
|
|
17243
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17244
|
+
reference: external_exports.string(),
|
|
17245
|
+
scope: external_exports.string(),
|
|
17246
|
+
reason: external_exports.string(),
|
|
17247
|
+
confirmation
|
|
17248
|
+
});
|
|
17249
|
+
var AddExceptionInput = external_exports.object({
|
|
17250
|
+
ruleId: external_exports.string(),
|
|
17251
|
+
value: external_exports.string(),
|
|
17252
|
+
scope: external_exports.string(),
|
|
17253
|
+
reason: external_exports.string(),
|
|
17254
|
+
confirmation
|
|
17255
|
+
});
|
|
17256
|
+
var GrantRevealInput = external_exports.object({
|
|
17257
|
+
pointer: external_exports.string(),
|
|
17258
|
+
scope: external_exports.string(),
|
|
17259
|
+
justification: external_exports.string(),
|
|
17260
|
+
confirmation
|
|
17261
|
+
});
|
|
17262
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17263
|
+
id: external_exports.string(),
|
|
17264
|
+
reason: external_exports.string()
|
|
17265
|
+
});
|
|
17266
|
+
var RotateKeyInput = external_exports.object({
|
|
17267
|
+
confirmation: external_exports.string()
|
|
17268
|
+
});
|
|
17269
|
+
|
|
17094
17270
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17095
17271
|
function toApiAction(dbVal) {
|
|
17096
17272
|
const map2 = {
|
|
@@ -17146,6 +17322,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17146
17322
|
repo: r.repo,
|
|
17147
17323
|
file: r.file,
|
|
17148
17324
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17325
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17326
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17149
17327
|
action: toApiAction(effectiveDbAction),
|
|
17150
17328
|
detectedAt: r.occurredAt,
|
|
17151
17329
|
confidence: r.confidence,
|
|
@@ -17277,14 +17455,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17277
17455
|
}
|
|
17278
17456
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17279
17457
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17458
|
+
function compareFindingGroupOrder(a, b) {
|
|
17459
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17460
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17461
|
+
const severityDiff = rankA - rankB;
|
|
17462
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17463
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17464
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17465
|
+
return a.id.localeCompare(b.id);
|
|
17466
|
+
}
|
|
17280
17467
|
function sortFindingGroups(groups) {
|
|
17281
|
-
return [...groups].sort(
|
|
17282
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17283
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17284
|
-
const severityDiff = rankA - rankB;
|
|
17285
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17286
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17287
|
-
});
|
|
17468
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17288
17469
|
}
|
|
17289
17470
|
function computeFindingFacets(allGroups, opts) {
|
|
17290
17471
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17340,15 +17521,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17340
17521
|
for (const g of forStatus) {
|
|
17341
17522
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17342
17523
|
}
|
|
17343
|
-
const
|
|
17524
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17525
|
+
return {
|
|
17526
|
+
severity: toItems2(severityMap),
|
|
17527
|
+
provider: toItems2(providerMap),
|
|
17528
|
+
action: toItems2(actionMap),
|
|
17529
|
+
subtype: toItems2(subtypeMap),
|
|
17530
|
+
status: toItems2(statusMap)
|
|
17531
|
+
};
|
|
17532
|
+
}
|
|
17533
|
+
|
|
17534
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17535
|
+
function rowHaystack(row) {
|
|
17536
|
+
return [
|
|
17537
|
+
row.ruleId,
|
|
17538
|
+
row.category,
|
|
17539
|
+
row.maskedMatch,
|
|
17540
|
+
row.repo,
|
|
17541
|
+
row.file,
|
|
17542
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17543
|
+
row.id
|
|
17544
|
+
].join(" ").toLowerCase();
|
|
17545
|
+
}
|
|
17546
|
+
function matchesDimension(row, opts, dimension) {
|
|
17547
|
+
switch (dimension) {
|
|
17548
|
+
case "severity":
|
|
17549
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17550
|
+
case "subtype":
|
|
17551
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17552
|
+
case "providers":
|
|
17553
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17554
|
+
case "actions":
|
|
17555
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17556
|
+
case "statuses":
|
|
17557
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17558
|
+
case "tools":
|
|
17559
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17560
|
+
case "repo":
|
|
17561
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17562
|
+
case "file":
|
|
17563
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17564
|
+
case "q":
|
|
17565
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17566
|
+
}
|
|
17567
|
+
}
|
|
17568
|
+
var DIMENSIONS = [
|
|
17569
|
+
"severity",
|
|
17570
|
+
"subtype",
|
|
17571
|
+
"providers",
|
|
17572
|
+
"actions",
|
|
17573
|
+
"statuses",
|
|
17574
|
+
"tools",
|
|
17575
|
+
"repo",
|
|
17576
|
+
"file",
|
|
17577
|
+
"q"
|
|
17578
|
+
];
|
|
17579
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17580
|
+
for (const dimension of DIMENSIONS) {
|
|
17581
|
+
if (dimension === except) continue;
|
|
17582
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17583
|
+
}
|
|
17584
|
+
return true;
|
|
17585
|
+
}
|
|
17586
|
+
function toItems(counts) {
|
|
17587
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17588
|
+
}
|
|
17589
|
+
function bump(counts, value) {
|
|
17590
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17591
|
+
}
|
|
17592
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17593
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17594
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17595
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17596
|
+
const action = /* @__PURE__ */ new Map();
|
|
17597
|
+
const status = /* @__PURE__ */ new Map();
|
|
17598
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17344
17599
|
return {
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17600
|
+
add(row) {
|
|
17601
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17603
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17604
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17605
|
+
}
|
|
17606
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17607
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17608
|
+
bump(status, row.status);
|
|
17609
|
+
}
|
|
17610
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17611
|
+
bump(tool, row.toolName);
|
|
17612
|
+
}
|
|
17613
|
+
},
|
|
17614
|
+
facets: () => ({
|
|
17615
|
+
severity: toItems(severity),
|
|
17616
|
+
subtype: toItems(subtype),
|
|
17617
|
+
provider: toItems(provider),
|
|
17618
|
+
action: toItems(action),
|
|
17619
|
+
status: toItems(status),
|
|
17620
|
+
tool: toItems(tool)
|
|
17621
|
+
})
|
|
17622
|
+
};
|
|
17623
|
+
}
|
|
17624
|
+
function toInstanceDetail(row) {
|
|
17625
|
+
const category = toApiCategory(row.category);
|
|
17626
|
+
return {
|
|
17627
|
+
id: row.id,
|
|
17628
|
+
provider: toApiProvider(row.sourceTool),
|
|
17629
|
+
repo: row.repo,
|
|
17630
|
+
file: row.file,
|
|
17631
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17632
|
+
eventId: row.eventId,
|
|
17633
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17634
|
+
action: toApiAction(row.actionTaken),
|
|
17635
|
+
detectedAt: row.occurredAt,
|
|
17636
|
+
confidence: row.confidence,
|
|
17637
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17638
|
+
groupId: row.ruleId,
|
|
17639
|
+
category,
|
|
17640
|
+
subtype: row.ruleId,
|
|
17641
|
+
severity: row.severity,
|
|
17642
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17643
|
+
detection: { id: row.ruleId, name: null },
|
|
17644
|
+
policy: { id: `category:${category}`, name: category }
|
|
17645
|
+
};
|
|
17646
|
+
}
|
|
17647
|
+
var SEVERITY_ORDER2 = {
|
|
17648
|
+
critical: 0,
|
|
17649
|
+
high: 1,
|
|
17650
|
+
medium: 2,
|
|
17651
|
+
low: 3
|
|
17652
|
+
};
|
|
17653
|
+
function newLocationAccumulator() {
|
|
17654
|
+
return {
|
|
17655
|
+
instanceCount: 0,
|
|
17656
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17657
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17658
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17659
|
+
maxSeverity: "low",
|
|
17660
|
+
latestDetectedAt: "",
|
|
17661
|
+
statuses: [],
|
|
17662
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17350
17663
|
};
|
|
17351
17664
|
}
|
|
17665
|
+
function addToLocation(acc, row) {
|
|
17666
|
+
acc.instanceCount += 1;
|
|
17667
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17668
|
+
if (rank < acc.maxSeverityRank) {
|
|
17669
|
+
acc.maxSeverityRank = rank;
|
|
17670
|
+
acc.maxSeverity = row.severity;
|
|
17671
|
+
}
|
|
17672
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17673
|
+
acc.statuses.push(row.status);
|
|
17674
|
+
acc.ruleIds.add(row.ruleId);
|
|
17675
|
+
}
|
|
17352
17676
|
|
|
17353
17677
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17354
17678
|
var InstalledPack = external_exports.object({
|
|
@@ -17485,6 +17809,50 @@ var VaultInventoryEntry = external_exports.object({
|
|
|
17485
17809
|
revealGrantId: external_exports.string().nullable(),
|
|
17486
17810
|
sightings: external_exports.array(VaultSighting)
|
|
17487
17811
|
});
|
|
17812
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17813
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17814
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17815
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17816
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17817
|
+
// Opaque; names the last row of the page just served.
|
|
17818
|
+
cursor: external_exports.string().optional()
|
|
17819
|
+
});
|
|
17820
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17821
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17822
|
+
// independent, so paging never changes what the count claims.
|
|
17823
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17824
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17825
|
+
// `null` once the last page is reached.
|
|
17826
|
+
nextCursor: external_exports.string().nullable()
|
|
17827
|
+
});
|
|
17828
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17829
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17830
|
+
cursor: external_exports.string().optional()
|
|
17831
|
+
});
|
|
17832
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17833
|
+
// Reused values across the whole store — the number the section's claim
|
|
17834
|
+
// ("values detected in more than one place") is about.
|
|
17835
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17836
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17837
|
+
nextCursor: external_exports.string().nullable()
|
|
17838
|
+
});
|
|
17839
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17840
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17841
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17842
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17843
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17844
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17845
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17846
|
+
cursor: external_exports.string().optional()
|
|
17847
|
+
});
|
|
17848
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17849
|
+
items: external_exports.array(VaultDeref),
|
|
17850
|
+
nextCursor: external_exports.string().nullable(),
|
|
17851
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17852
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17853
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17854
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17855
|
+
});
|
|
17488
17856
|
var VaultKeyCustody = external_exports.string();
|
|
17489
17857
|
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17490
17858
|
var VaultConsent = external_exports.object({
|
|
@@ -17857,7 +18225,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17857
18225
|
// Omit for both kinds.
|
|
17858
18226
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17859
18227
|
});
|
|
17860
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18228
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17861
18229
|
var ScanCoverageProvider = external_exports.object({
|
|
17862
18230
|
provider: Provider,
|
|
17863
18231
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18110,6 +18478,195 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18110
18478
|
);
|
|
18111
18479
|
}
|
|
18112
18480
|
|
|
18481
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18482
|
+
import { randomUUID } from "crypto";
|
|
18483
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18484
|
+
import { basename, dirname, join } from "path";
|
|
18485
|
+
|
|
18486
|
+
// ../../packages/persistence/src/paths.ts
|
|
18487
|
+
import {
|
|
18488
|
+
chmodSync,
|
|
18489
|
+
linkSync,
|
|
18490
|
+
lstatSync,
|
|
18491
|
+
mkdirSync,
|
|
18492
|
+
renameSync,
|
|
18493
|
+
rmSync,
|
|
18494
|
+
writeFileSync
|
|
18495
|
+
} from "fs";
|
|
18496
|
+
import { threadId } from "worker_threads";
|
|
18497
|
+
var DATA_DIR_MODE = 448;
|
|
18498
|
+
var DATA_FILE_MODE = 384;
|
|
18499
|
+
var DB_FILENAME = "aka.db";
|
|
18500
|
+
function isSymlink(path) {
|
|
18501
|
+
try {
|
|
18502
|
+
return lstatSync(path).isSymbolicLink();
|
|
18503
|
+
} catch {
|
|
18504
|
+
return false;
|
|
18505
|
+
}
|
|
18506
|
+
}
|
|
18507
|
+
function chmodBestEffort(path, mode) {
|
|
18508
|
+
if (isSymlink(path)) return;
|
|
18509
|
+
try {
|
|
18510
|
+
chmodSync(path, mode);
|
|
18511
|
+
} catch {
|
|
18512
|
+
}
|
|
18513
|
+
}
|
|
18514
|
+
function tightenDir(dir) {
|
|
18515
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18516
|
+
}
|
|
18517
|
+
function ensureDataDirSync(dir) {
|
|
18518
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18519
|
+
tightenDir(dir);
|
|
18520
|
+
}
|
|
18521
|
+
function dbSidecars(file2) {
|
|
18522
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18523
|
+
}
|
|
18524
|
+
function tightenFile(file2) {
|
|
18525
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18526
|
+
}
|
|
18527
|
+
function tightenPerms(file2) {
|
|
18528
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18529
|
+
}
|
|
18530
|
+
function classifyOccupant(file2) {
|
|
18531
|
+
try {
|
|
18532
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
18533
|
+
return { kind: "gone" };
|
|
18534
|
+
} catch (err) {
|
|
18535
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
18536
|
+
return { kind: "unknown", cause: err };
|
|
18537
|
+
}
|
|
18538
|
+
}
|
|
18539
|
+
var KeyUnclaimableError = class extends Error {
|
|
18540
|
+
code = "key-unclaimable";
|
|
18541
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
18542
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
18543
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
18544
|
+
// captured and then lost, which is worse than its plain absence.
|
|
18545
|
+
constructor(message, cause) {
|
|
18546
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
18547
|
+
this.name = "KeyUnclaimableError";
|
|
18548
|
+
}
|
|
18549
|
+
};
|
|
18550
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
18551
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
18552
|
+
try {
|
|
18553
|
+
rmSync(tmp, { force: true });
|
|
18554
|
+
} catch {
|
|
18555
|
+
}
|
|
18556
|
+
let created;
|
|
18557
|
+
try {
|
|
18558
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18559
|
+
created = publishByLink(tmp, file2, data);
|
|
18560
|
+
} finally {
|
|
18561
|
+
try {
|
|
18562
|
+
rmSync(tmp, { force: true });
|
|
18563
|
+
} catch {
|
|
18564
|
+
}
|
|
18565
|
+
}
|
|
18566
|
+
if (created) tightenFile(file2);
|
|
18567
|
+
return created;
|
|
18568
|
+
}
|
|
18569
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
18570
|
+
function publishByLink(tmp, file2, data) {
|
|
18571
|
+
try {
|
|
18572
|
+
linkSync(tmp, file2);
|
|
18573
|
+
return true;
|
|
18574
|
+
} catch (err) {
|
|
18575
|
+
const code = err.code;
|
|
18576
|
+
if (code === "EEXIST") return false;
|
|
18577
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18578
|
+
}
|
|
18579
|
+
try {
|
|
18580
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18581
|
+
return true;
|
|
18582
|
+
} catch (err) {
|
|
18583
|
+
if (err.code === "EEXIST") return false;
|
|
18584
|
+
throw err;
|
|
18585
|
+
}
|
|
18586
|
+
}
|
|
18587
|
+
|
|
18588
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18589
|
+
function backupPath(file2, tag) {
|
|
18590
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18591
|
+
}
|
|
18592
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18593
|
+
function reapStalePartials(file2) {
|
|
18594
|
+
const dir = dirname(file2);
|
|
18595
|
+
const prefix = `${basename(file2)}.`;
|
|
18596
|
+
let entries;
|
|
18597
|
+
try {
|
|
18598
|
+
entries = readdirSync(dir);
|
|
18599
|
+
} catch {
|
|
18600
|
+
return;
|
|
18601
|
+
}
|
|
18602
|
+
for (const name of entries) {
|
|
18603
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18604
|
+
const partial2 = join(dir, name);
|
|
18605
|
+
try {
|
|
18606
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18607
|
+
rmSync2(partial2, { force: true });
|
|
18608
|
+
}
|
|
18609
|
+
} catch {
|
|
18610
|
+
}
|
|
18611
|
+
}
|
|
18612
|
+
}
|
|
18613
|
+
function snapshotStore(db, backup) {
|
|
18614
|
+
const partial2 = `${backup}.partial`;
|
|
18615
|
+
try {
|
|
18616
|
+
rmSync2(partial2, { force: true });
|
|
18617
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18618
|
+
tightenFile(partial2);
|
|
18619
|
+
renameSync2(partial2, backup);
|
|
18620
|
+
} catch (error51) {
|
|
18621
|
+
try {
|
|
18622
|
+
rmSync2(partial2, { force: true });
|
|
18623
|
+
} catch {
|
|
18624
|
+
}
|
|
18625
|
+
throw error51;
|
|
18626
|
+
}
|
|
18627
|
+
}
|
|
18628
|
+
function moveStoreAside(file2, backup) {
|
|
18629
|
+
const undo = [];
|
|
18630
|
+
renameSync2(file2, backup);
|
|
18631
|
+
undo.push([backup, file2]);
|
|
18632
|
+
try {
|
|
18633
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18634
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18635
|
+
try {
|
|
18636
|
+
renameSync2(sidecar, moved);
|
|
18637
|
+
undo.push([moved, sidecar]);
|
|
18638
|
+
} catch {
|
|
18639
|
+
rmSync2(sidecar, { force: true });
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
} catch (error51) {
|
|
18643
|
+
for (const [from, to] of undo.reverse()) {
|
|
18644
|
+
try {
|
|
18645
|
+
renameSync2(from, to);
|
|
18646
|
+
} catch {
|
|
18647
|
+
}
|
|
18648
|
+
}
|
|
18649
|
+
throw error51;
|
|
18650
|
+
}
|
|
18651
|
+
tightenPerms(backup);
|
|
18652
|
+
}
|
|
18653
|
+
function discardStore(file2, backup) {
|
|
18654
|
+
try {
|
|
18655
|
+
rmSync2(file2, { force: true });
|
|
18656
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18657
|
+
rmSync2(sidecar, { force: true });
|
|
18658
|
+
}
|
|
18659
|
+
} catch (error51) {
|
|
18660
|
+
if (existsSync(file2)) {
|
|
18661
|
+
try {
|
|
18662
|
+
rmSync2(backup, { force: true });
|
|
18663
|
+
} catch {
|
|
18664
|
+
}
|
|
18665
|
+
}
|
|
18666
|
+
throw error51;
|
|
18667
|
+
}
|
|
18668
|
+
}
|
|
18669
|
+
|
|
18113
18670
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18114
18671
|
function escapeLikePattern(s) {
|
|
18115
18672
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18251,55 +18808,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18251
18808
|
return out;
|
|
18252
18809
|
}
|
|
18253
18810
|
|
|
18254
|
-
// ../../packages/persistence/src/paths.ts
|
|
18255
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18256
|
-
var DATA_DIR_MODE = 448;
|
|
18257
|
-
var DATA_FILE_MODE = 384;
|
|
18258
|
-
var DB_FILENAME = "aka.db";
|
|
18259
|
-
function chmodBestEffort(path, mode) {
|
|
18260
|
-
try {
|
|
18261
|
-
chmodSync(path, mode);
|
|
18262
|
-
} catch {
|
|
18263
|
-
}
|
|
18264
|
-
}
|
|
18265
|
-
function tightenDir(dir) {
|
|
18266
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18267
|
-
}
|
|
18268
|
-
function ensureDataDirSync(dir) {
|
|
18269
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18270
|
-
tightenDir(dir);
|
|
18271
|
-
}
|
|
18272
|
-
function dbSidecars(file2) {
|
|
18273
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18274
|
-
}
|
|
18275
|
-
function tightenFile(file2) {
|
|
18276
|
-
try {
|
|
18277
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18278
|
-
} catch {
|
|
18279
|
-
}
|
|
18280
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18281
|
-
}
|
|
18282
|
-
function tightenPerms(file2) {
|
|
18283
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18284
|
-
}
|
|
18285
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18286
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18287
|
-
try {
|
|
18288
|
-
rmSync(tmp, { force: true });
|
|
18289
|
-
} catch {
|
|
18290
|
-
}
|
|
18291
|
-
try {
|
|
18292
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18293
|
-
renameSync(tmp, file2);
|
|
18294
|
-
} finally {
|
|
18295
|
-
try {
|
|
18296
|
-
rmSync(tmp, { force: true });
|
|
18297
|
-
} catch {
|
|
18298
|
-
}
|
|
18299
|
-
}
|
|
18300
|
-
tightenFile(file2);
|
|
18301
|
-
}
|
|
18302
|
-
|
|
18303
18811
|
// ../../packages/persistence/src/migrations.ts
|
|
18304
18812
|
function describeObject(object2) {
|
|
18305
18813
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18415,9 +18923,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18415
18923
|
}
|
|
18416
18924
|
}
|
|
18417
18925
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18926
|
+
reapStalePartials(file2);
|
|
18927
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18928
|
+
snapshotStore(db, backup);
|
|
18421
18929
|
return backup;
|
|
18422
18930
|
}
|
|
18423
18931
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18761,6 +19269,25 @@ function parseJsonObject(s) {
|
|
|
18761
19269
|
return void 0;
|
|
18762
19270
|
}
|
|
18763
19271
|
|
|
19272
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19273
|
+
function encodeKeysetCursor(payload) {
|
|
19274
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19275
|
+
}
|
|
19276
|
+
function decodeKeysetCursor(cursor) {
|
|
19277
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19278
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19279
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19280
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19281
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19282
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19283
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19284
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19285
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19286
|
+
return parsed;
|
|
19287
|
+
}
|
|
19288
|
+
return null;
|
|
19289
|
+
}
|
|
19290
|
+
|
|
18764
19291
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18765
19292
|
var DAY_MS = 864e5;
|
|
18766
19293
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18806,16 +19333,6 @@ function utcWindow(nowMs) {
|
|
|
18806
19333
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18807
19334
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18808
19335
|
}
|
|
18809
|
-
function encodeCursor(payload) {
|
|
18810
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18811
|
-
}
|
|
18812
|
-
function decodeCursor(cursor) {
|
|
18813
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18814
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18815
|
-
return parsed;
|
|
18816
|
-
}
|
|
18817
|
-
return null;
|
|
18818
|
-
}
|
|
18819
19336
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18820
19337
|
session: "session",
|
|
18821
19338
|
prompt: "prompt",
|
|
@@ -18960,7 +19477,7 @@ var SqliteActivityRepository = class {
|
|
|
18960
19477
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18961
19478
|
}
|
|
18962
19479
|
listSessions(query) {
|
|
18963
|
-
const cursor = query.cursor ?
|
|
19480
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18964
19481
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18965
19482
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18966
19483
|
const conditions = [SESSION_ROOT];
|
|
@@ -19034,7 +19551,7 @@ var SqliteActivityRepository = class {
|
|
|
19034
19551
|
)
|
|
19035
19552
|
);
|
|
19036
19553
|
const last = page[page.length - 1];
|
|
19037
|
-
const nextCursor = hasMore && last ?
|
|
19554
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
19038
19555
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
19039
19556
|
}
|
|
19040
19557
|
getSession(sessionId) {
|
|
@@ -19907,7 +20424,7 @@ var SqliteEventsRepository = class {
|
|
|
19907
20424
|
};
|
|
19908
20425
|
|
|
19909
20426
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19910
|
-
import { randomUUID } from "crypto";
|
|
20427
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19911
20428
|
|
|
19912
20429
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19913
20430
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19943,8 +20460,9 @@ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
|
19943
20460
|
AND conditions IS NULL
|
|
19944
20461
|
AND ${ACTIVE_PREDICATE}`;
|
|
19945
20462
|
var SqliteExceptionsRepository = class {
|
|
19946
|
-
constructor(db) {
|
|
20463
|
+
constructor(db, now = () => Date.now()) {
|
|
19947
20464
|
this.db = db;
|
|
20465
|
+
this.now = now;
|
|
19948
20466
|
this.consumeStmt = db.prepare(
|
|
19949
20467
|
`UPDATE exceptions
|
|
19950
20468
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19962,6 +20480,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19962
20480
|
);
|
|
19963
20481
|
}
|
|
19964
20482
|
db;
|
|
20483
|
+
now;
|
|
19965
20484
|
consumeStmt;
|
|
19966
20485
|
insertBlockedStmt;
|
|
19967
20486
|
sweepBlockedStmt;
|
|
@@ -19988,8 +20507,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19988
20507
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19989
20508
|
);
|
|
19990
20509
|
}
|
|
19991
|
-
const id =
|
|
19992
|
-
const now =
|
|
20510
|
+
const id = randomUUID2();
|
|
20511
|
+
const now = this.now();
|
|
19993
20512
|
try {
|
|
19994
20513
|
this.insertExceptionRow(id, input, now);
|
|
19995
20514
|
} catch (err) {
|
|
@@ -20067,7 +20586,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20067
20586
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
20068
20587
|
const rows = allRows(
|
|
20069
20588
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
20070
|
-
opts?.includeTerminal ? {} : { now:
|
|
20589
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
20071
20590
|
);
|
|
20072
20591
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
20073
20592
|
return Promise.resolve(exceptions);
|
|
@@ -20102,7 +20621,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20102
20621
|
* already revoked.
|
|
20103
20622
|
*/
|
|
20104
20623
|
revoke(id, revokedBy, reason) {
|
|
20105
|
-
const now =
|
|
20624
|
+
const now = this.now();
|
|
20106
20625
|
const result = this.db.prepare(
|
|
20107
20626
|
`UPDATE exceptions
|
|
20108
20627
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20116,7 +20635,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20116
20635
|
* callers must treat identically — means it does not and the detection is
|
|
20117
20636
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20118
20637
|
*/
|
|
20119
|
-
consume(id, now =
|
|
20638
|
+
consume(id, now = this.now()) {
|
|
20120
20639
|
const result = this.consumeStmt.run({ id, now });
|
|
20121
20640
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20122
20641
|
}
|
|
@@ -20125,7 +20644,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20125
20644
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20126
20645
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20127
20646
|
*/
|
|
20128
|
-
activeBundleEntries(keyVersion, now =
|
|
20647
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20129
20648
|
const rows = allRows(
|
|
20130
20649
|
this.db.prepare(
|
|
20131
20650
|
`SELECT * FROM exceptions
|
|
@@ -20157,7 +20676,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20157
20676
|
* than the retention window on every write, so the ledger self-limits.
|
|
20158
20677
|
*/
|
|
20159
20678
|
recordBlocked(entry) {
|
|
20160
|
-
const now =
|
|
20679
|
+
const now = this.now();
|
|
20161
20680
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20162
20681
|
this.insertBlockedStmt.run({
|
|
20163
20682
|
reference: entry.reference,
|
|
@@ -20180,7 +20699,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20180
20699
|
WHERE blocked_at > :cutoff
|
|
20181
20700
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20182
20701
|
),
|
|
20183
|
-
{ cutoff:
|
|
20702
|
+
{ cutoff: this.now() - windowMs }
|
|
20184
20703
|
);
|
|
20185
20704
|
return Promise.resolve(
|
|
20186
20705
|
rows.map((row) => ({
|
|
@@ -20208,8 +20727,9 @@ var SqliteExceptionsRepository = class {
|
|
|
20208
20727
|
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20209
20728
|
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20210
20729
|
*/
|
|
20211
|
-
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now
|
|
20730
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20212
20731
|
try {
|
|
20732
|
+
const at = now ?? this.now();
|
|
20213
20733
|
const row = getRow(
|
|
20214
20734
|
this.db.prepare(
|
|
20215
20735
|
`SELECT id FROM exceptions
|
|
@@ -20218,7 +20738,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20218
20738
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20219
20739
|
LIMIT 1`
|
|
20220
20740
|
),
|
|
20221
|
-
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20741
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20222
20742
|
);
|
|
20223
20743
|
return Promise.resolve(row ?? null);
|
|
20224
20744
|
} catch (err) {
|
|
@@ -20232,7 +20752,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20232
20752
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20233
20753
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20234
20754
|
*/
|
|
20235
|
-
sweepTerminal(retentionMs, now =
|
|
20755
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20236
20756
|
const result = this.db.prepare(
|
|
20237
20757
|
`DELETE FROM exceptions
|
|
20238
20758
|
WHERE updated_at < :cutoff
|
|
@@ -20295,6 +20815,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20295
20815
|
|
|
20296
20816
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20297
20817
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20818
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20819
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20820
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20821
|
+
function compareLocationOrder(a, b) {
|
|
20822
|
+
return compareFindingGroupOrder(
|
|
20823
|
+
{
|
|
20824
|
+
severity: a.maxSeverity,
|
|
20825
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20826
|
+
id: ""
|
|
20827
|
+
},
|
|
20828
|
+
{
|
|
20829
|
+
severity: b.maxSeverity,
|
|
20830
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20831
|
+
id: ""
|
|
20832
|
+
}
|
|
20833
|
+
);
|
|
20834
|
+
}
|
|
20298
20835
|
var CONCAT_SEP = ",";
|
|
20299
20836
|
var TUPLE_SEP = "|";
|
|
20300
20837
|
function splitConcat(value) {
|
|
@@ -20307,6 +20844,33 @@ function deriveInstanceStatus(row) {
|
|
|
20307
20844
|
latestResolutionStatus: row.latest_status
|
|
20308
20845
|
});
|
|
20309
20846
|
}
|
|
20847
|
+
function encodeGroupCursor(group) {
|
|
20848
|
+
const payload = {
|
|
20849
|
+
sev: group.severity,
|
|
20850
|
+
t: group.latestDetectedAt,
|
|
20851
|
+
id: group.id
|
|
20852
|
+
};
|
|
20853
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20854
|
+
}
|
|
20855
|
+
function decodeGroupCursor(cursor) {
|
|
20856
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20857
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20858
|
+
return {
|
|
20859
|
+
severity: parsed.sev,
|
|
20860
|
+
latestDetectedAt: parsed.t,
|
|
20861
|
+
id: parsed.id
|
|
20862
|
+
};
|
|
20863
|
+
}
|
|
20864
|
+
return null;
|
|
20865
|
+
}
|
|
20866
|
+
function firstAfter(sorted, cursor) {
|
|
20867
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20868
|
+
return index === -1 ? sorted.length : index;
|
|
20869
|
+
}
|
|
20870
|
+
function findDeepLinked(sorted, page, id) {
|
|
20871
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20872
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20873
|
+
}
|
|
20310
20874
|
var DAY_MS3 = 864e5;
|
|
20311
20875
|
var SqliteFindingsRepository = class {
|
|
20312
20876
|
constructor(db) {
|
|
@@ -20416,8 +20980,13 @@ var SqliteFindingsRepository = class {
|
|
|
20416
20980
|
*/
|
|
20417
20981
|
listGroupedFindings(query) {
|
|
20418
20982
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20419
|
-
const
|
|
20420
|
-
const
|
|
20983
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20984
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20985
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20986
|
+
const sessionParams = {
|
|
20987
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20988
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20989
|
+
};
|
|
20421
20990
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20422
20991
|
predicate,
|
|
20423
20992
|
params: sessionParams
|
|
@@ -20425,7 +20994,8 @@ var SqliteFindingsRepository = class {
|
|
|
20425
20994
|
const rows = allRows(
|
|
20426
20995
|
this.db.prepare(
|
|
20427
20996
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20428
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20997
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20998
|
+
kind, finding_key, latest_status
|
|
20429
20999
|
FROM (
|
|
20430
21000
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20431
21001
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20435,6 +21005,7 @@ var SqliteFindingsRepository = class {
|
|
|
20435
21005
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20436
21006
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20437
21007
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21008
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20438
21009
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20439
21010
|
latest.status AS latest_status,
|
|
20440
21011
|
ROW_NUMBER() OVER (
|
|
@@ -20466,6 +21037,8 @@ var SqliteFindingsRepository = class {
|
|
|
20466
21037
|
repo: r.repo ?? "",
|
|
20467
21038
|
file: r.file ?? "",
|
|
20468
21039
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21040
|
+
eventId: r.event_id,
|
|
21041
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20469
21042
|
status: deriveInstanceStatus(r)
|
|
20470
21043
|
}));
|
|
20471
21044
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20489,18 +21062,23 @@ var SqliteFindingsRepository = class {
|
|
|
20489
21062
|
groups: sorted.length
|
|
20490
21063
|
};
|
|
20491
21064
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21065
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21066
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21067
|
+
const page = sorted.slice(start, start + limit);
|
|
21068
|
+
const lastOnPage = page.at(-1);
|
|
21069
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21070
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20492
21071
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20493
|
-
const
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
);
|
|
21072
|
+
const narrow = (g) => statusSet ? {
|
|
21073
|
+
...g,
|
|
21074
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21075
|
+
} : g;
|
|
21076
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20499
21077
|
return Promise.resolve({
|
|
20500
21078
|
totals,
|
|
20501
21079
|
facets,
|
|
20502
21080
|
items,
|
|
20503
|
-
nextCursor
|
|
21081
|
+
nextCursor,
|
|
20504
21082
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20505
21083
|
});
|
|
20506
21084
|
}
|
|
@@ -20532,6 +21110,266 @@ var SqliteFindingsRepository = class {
|
|
|
20532
21110
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20533
21111
|
* path repeating across tuples.)
|
|
20534
21112
|
*/
|
|
21113
|
+
/**
|
|
21114
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21115
|
+
* paged by keyset.
|
|
21116
|
+
*
|
|
21117
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21118
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21119
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21120
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21121
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21122
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21123
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21124
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21125
|
+
* none of the mappers names", which no IN-list can say.
|
|
21126
|
+
*
|
|
21127
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21128
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21129
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21130
|
+
* while the counting runs, and only the page itself is retained.
|
|
21131
|
+
*/
|
|
21132
|
+
listFindingInstances(query) {
|
|
21133
|
+
const opts = {
|
|
21134
|
+
severity: query.severity,
|
|
21135
|
+
subtype: query.subtype,
|
|
21136
|
+
providers: query.provider,
|
|
21137
|
+
actions: query.action,
|
|
21138
|
+
statuses: query.status,
|
|
21139
|
+
tools: query.tool,
|
|
21140
|
+
repo: query.repo,
|
|
21141
|
+
file: query.file,
|
|
21142
|
+
q: query.q
|
|
21143
|
+
};
|
|
21144
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21145
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21146
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21147
|
+
const items = [];
|
|
21148
|
+
let total = 0;
|
|
21149
|
+
let last;
|
|
21150
|
+
let hasMore = false;
|
|
21151
|
+
for (const row of this.scanFindingRows({
|
|
21152
|
+
sessionId: query.sessionId,
|
|
21153
|
+
from: query.from
|
|
21154
|
+
})) {
|
|
21155
|
+
accumulator.add(row);
|
|
21156
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21157
|
+
total += 1;
|
|
21158
|
+
if (items.length < limit) {
|
|
21159
|
+
items.push(toInstanceDetail(row));
|
|
21160
|
+
last = row;
|
|
21161
|
+
} else {
|
|
21162
|
+
hasMore = true;
|
|
21163
|
+
}
|
|
21164
|
+
}
|
|
21165
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21166
|
+
if (cursor !== null) {
|
|
21167
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21168
|
+
return Promise.resolve({
|
|
21169
|
+
totals: { findings: total },
|
|
21170
|
+
facets: accumulator.facets(),
|
|
21171
|
+
items: resumed.items,
|
|
21172
|
+
nextCursor: resumed.nextCursor
|
|
21173
|
+
});
|
|
21174
|
+
}
|
|
21175
|
+
return Promise.resolve({
|
|
21176
|
+
totals: { findings: total },
|
|
21177
|
+
facets: accumulator.facets(),
|
|
21178
|
+
items,
|
|
21179
|
+
nextCursor
|
|
21180
|
+
});
|
|
21181
|
+
}
|
|
21182
|
+
/**
|
|
21183
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21184
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21185
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21186
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21187
|
+
*/
|
|
21188
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21189
|
+
const items = [];
|
|
21190
|
+
let last;
|
|
21191
|
+
let hasMore = false;
|
|
21192
|
+
for (const row of this.scanFindingRows({
|
|
21193
|
+
sessionId: query.sessionId,
|
|
21194
|
+
from: query.from,
|
|
21195
|
+
after: cursor
|
|
21196
|
+
})) {
|
|
21197
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21198
|
+
if (items.length < limit) {
|
|
21199
|
+
items.push(toInstanceDetail(row));
|
|
21200
|
+
last = row;
|
|
21201
|
+
} else {
|
|
21202
|
+
hasMore = true;
|
|
21203
|
+
break;
|
|
21204
|
+
}
|
|
21205
|
+
}
|
|
21206
|
+
return {
|
|
21207
|
+
items,
|
|
21208
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21209
|
+
};
|
|
21210
|
+
}
|
|
21211
|
+
/**
|
|
21212
|
+
* The same findings folded by location: repository, then file within it.
|
|
21213
|
+
*
|
|
21214
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21215
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21216
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21217
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21218
|
+
* filter can name it.
|
|
21219
|
+
*/
|
|
21220
|
+
listFindingLocations(query) {
|
|
21221
|
+
const opts = {
|
|
21222
|
+
severity: query.severity,
|
|
21223
|
+
subtype: query.subtype,
|
|
21224
|
+
providers: query.provider,
|
|
21225
|
+
actions: query.action,
|
|
21226
|
+
statuses: query.status,
|
|
21227
|
+
tools: query.tool,
|
|
21228
|
+
q: query.q
|
|
21229
|
+
};
|
|
21230
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21231
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21232
|
+
let total = 0;
|
|
21233
|
+
for (const row of this.scanFindingRows({
|
|
21234
|
+
sessionId: query.sessionId,
|
|
21235
|
+
from: query.from
|
|
21236
|
+
})) {
|
|
21237
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21238
|
+
total += 1;
|
|
21239
|
+
let files = byRepo.get(row.repo);
|
|
21240
|
+
if (files === void 0) {
|
|
21241
|
+
files = /* @__PURE__ */ new Map();
|
|
21242
|
+
byRepo.set(row.repo, files);
|
|
21243
|
+
}
|
|
21244
|
+
let acc = files.get(row.file);
|
|
21245
|
+
if (acc === void 0) {
|
|
21246
|
+
acc = newLocationAccumulator();
|
|
21247
|
+
files.set(row.file, acc);
|
|
21248
|
+
}
|
|
21249
|
+
addToLocation(acc, row);
|
|
21250
|
+
}
|
|
21251
|
+
let fileCount = 0;
|
|
21252
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21253
|
+
fileCount += files.size;
|
|
21254
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21255
|
+
file: file2,
|
|
21256
|
+
instanceCount: acc.instanceCount,
|
|
21257
|
+
maxSeverity: acc.maxSeverity,
|
|
21258
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21259
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21260
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21261
|
+
})).sort(compareLocationOrder);
|
|
21262
|
+
const rollup = fileRows.reduce(
|
|
21263
|
+
(a, f) => ({
|
|
21264
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21265
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21266
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21267
|
+
}),
|
|
21268
|
+
{
|
|
21269
|
+
instanceCount: 0,
|
|
21270
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21271
|
+
latestDetectedAt: ""
|
|
21272
|
+
}
|
|
21273
|
+
);
|
|
21274
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21275
|
+
const folded = foldGroupStatus(statuses);
|
|
21276
|
+
return {
|
|
21277
|
+
repo,
|
|
21278
|
+
instanceCount: rollup.instanceCount,
|
|
21279
|
+
maxSeverity: rollup.maxSeverity,
|
|
21280
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21281
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21282
|
+
files: fileRows
|
|
21283
|
+
};
|
|
21284
|
+
});
|
|
21285
|
+
repos.sort(compareLocationOrder);
|
|
21286
|
+
return Promise.resolve({
|
|
21287
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21288
|
+
items: repos.slice(0, limit),
|
|
21289
|
+
hasMore: repos.length > limit
|
|
21290
|
+
});
|
|
21291
|
+
}
|
|
21292
|
+
/**
|
|
21293
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21294
|
+
*
|
|
21295
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21296
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21297
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21298
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21299
|
+
* rather than one unbounded result set.
|
|
21300
|
+
*
|
|
21301
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21302
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21303
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21304
|
+
* a window over the whole resolution table once per batch.
|
|
21305
|
+
*
|
|
21306
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21307
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21308
|
+
* dimension — see listFindingInstances.
|
|
21309
|
+
*/
|
|
21310
|
+
*scanFindingRows(scope) {
|
|
21311
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21312
|
+
const params = [];
|
|
21313
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21314
|
+
conditions.push("e.root_session_id = ?");
|
|
21315
|
+
params.push(scope.sessionId);
|
|
21316
|
+
}
|
|
21317
|
+
if (scope.from !== void 0) {
|
|
21318
|
+
conditions.push("e.started_at >= ?");
|
|
21319
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21320
|
+
}
|
|
21321
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21322
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21323
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21324
|
+
e.started_at AS occurred_at,
|
|
21325
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21326
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21327
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21328
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21329
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21330
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21331
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21332
|
+
FROM inspection_findings f
|
|
21333
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21334
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21335
|
+
WHERE ${conditions.join(" AND ")}
|
|
21336
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21337
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21338
|
+
LIMIT ?`;
|
|
21339
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21340
|
+
for (; ; ) {
|
|
21341
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21342
|
+
...params,
|
|
21343
|
+
after.startedAtMs,
|
|
21344
|
+
after.startedAtMs,
|
|
21345
|
+
after.id,
|
|
21346
|
+
SCAN_BATCH_ROWS
|
|
21347
|
+
]);
|
|
21348
|
+
for (const r of rows) {
|
|
21349
|
+
yield {
|
|
21350
|
+
id: r.id,
|
|
21351
|
+
ruleId: r.rule_id,
|
|
21352
|
+
category: r.category,
|
|
21353
|
+
severity: r.severity,
|
|
21354
|
+
maskedMatch: r.masked_match,
|
|
21355
|
+
actionTaken: r.action_taken,
|
|
21356
|
+
confidence: r.confidence,
|
|
21357
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21358
|
+
sourceTool: r.source_tool,
|
|
21359
|
+
repo: r.repo ?? "",
|
|
21360
|
+
file: r.file ?? "",
|
|
21361
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21362
|
+
eventId: r.event_id,
|
|
21363
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21364
|
+
status: deriveInstanceStatus(r)
|
|
21365
|
+
};
|
|
21366
|
+
}
|
|
21367
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21368
|
+
const lastRow = rows[rows.length - 1];
|
|
21369
|
+
if (lastRow === void 0) return;
|
|
21370
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21371
|
+
}
|
|
21372
|
+
}
|
|
20535
21373
|
groupAggregates(withSearchText, scope) {
|
|
20536
21374
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20537
21375
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20792,7 +21630,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20792
21630
|
};
|
|
20793
21631
|
|
|
20794
21632
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20795
|
-
import { createHash as createHash2, randomUUID as
|
|
21633
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20796
21634
|
|
|
20797
21635
|
// ../../packages/persistence/src/semver.ts
|
|
20798
21636
|
function parse3(version2) {
|
|
@@ -20943,7 +21781,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20943
21781
|
let behind = false;
|
|
20944
21782
|
for (const row of rows) {
|
|
20945
21783
|
const params = {
|
|
20946
|
-
id:
|
|
21784
|
+
id: randomUUID3(),
|
|
20947
21785
|
namespace: row.namespace,
|
|
20948
21786
|
packId: row.packId,
|
|
20949
21787
|
version: row.version,
|
|
@@ -20955,7 +21793,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20955
21793
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20956
21794
|
this.upsertAvailableStmt.run({
|
|
20957
21795
|
...params,
|
|
20958
|
-
id:
|
|
21796
|
+
id: randomUUID3(),
|
|
20959
21797
|
recordedBy: meta3?.recordedBy ?? null
|
|
20960
21798
|
});
|
|
20961
21799
|
} else {
|
|
@@ -21278,14 +22116,15 @@ var SqliteInventoryRepository = class {
|
|
|
21278
22116
|
};
|
|
21279
22117
|
|
|
21280
22118
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21281
|
-
import { randomUUID as
|
|
22119
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21282
22120
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21283
22121
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21284
22122
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21285
22123
|
var HARNESS_LABELS = {
|
|
21286
22124
|
claudecode: "Claude Code",
|
|
21287
22125
|
cursor: "Cursor",
|
|
21288
|
-
codex: "Codex"
|
|
22126
|
+
codex: "Codex",
|
|
22127
|
+
antigravity: "Antigravity"
|
|
21289
22128
|
};
|
|
21290
22129
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21291
22130
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21300,6 +22139,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21300
22139
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21301
22140
|
if (t.includes("cursor")) return "cursor";
|
|
21302
22141
|
if (t.includes("codex")) return "codex";
|
|
22142
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21303
22143
|
return null;
|
|
21304
22144
|
}
|
|
21305
22145
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21758,7 +22598,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21758
22598
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21759
22599
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21760
22600
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21761
|
-
).run({ id:
|
|
22601
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21762
22602
|
}
|
|
21763
22603
|
return true;
|
|
21764
22604
|
}
|
|
@@ -21779,7 +22619,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21779
22619
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21780
22620
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21781
22621
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21782
|
-
).run({ id:
|
|
22622
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21783
22623
|
}
|
|
21784
22624
|
this.configRowsCache = void 0;
|
|
21785
22625
|
return "ok";
|
|
@@ -22076,7 +22916,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22076
22916
|
};
|
|
22077
22917
|
|
|
22078
22918
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
22079
|
-
import { randomUUID as
|
|
22919
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
22080
22920
|
var SqlitePoliciesRepository = class {
|
|
22081
22921
|
constructor(db) {
|
|
22082
22922
|
this.db = db;
|
|
@@ -22111,7 +22951,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22111
22951
|
failOpenTransaction(this.db, () => {
|
|
22112
22952
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
22113
22953
|
stmt.run({
|
|
22114
|
-
id:
|
|
22954
|
+
id: randomUUID5(),
|
|
22115
22955
|
target: JSON.stringify({ category }),
|
|
22116
22956
|
action,
|
|
22117
22957
|
now: Date.now()
|
|
@@ -22131,7 +22971,7 @@ var SqlitePoliciesRepository = class {
|
|
|
22131
22971
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
22132
22972
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
22133
22973
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
22134
|
-
).run({ id:
|
|
22974
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
22135
22975
|
}
|
|
22136
22976
|
// Caps every global per-category policy currently set to block/redact down
|
|
22137
22977
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22199,7 +23039,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22199
23039
|
};
|
|
22200
23040
|
|
|
22201
23041
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22202
|
-
import { randomUUID as
|
|
23042
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22203
23043
|
var SqliteProjectFilesRepository = class {
|
|
22204
23044
|
constructor(db) {
|
|
22205
23045
|
this.db = db;
|
|
@@ -22231,7 +23071,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22231
23071
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22232
23072
|
for (const file2 of scan2.files) {
|
|
22233
23073
|
this.upsertStmt.run({
|
|
22234
|
-
id:
|
|
23074
|
+
id: randomUUID6(),
|
|
22235
23075
|
projectId,
|
|
22236
23076
|
path: file2.path,
|
|
22237
23077
|
name: file2.name,
|
|
@@ -22245,7 +23085,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22245
23085
|
};
|
|
22246
23086
|
|
|
22247
23087
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22248
|
-
import { randomUUID as
|
|
23088
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22249
23089
|
var SqliteResolutionsRepository = class {
|
|
22250
23090
|
constructor(db, now = () => Date.now()) {
|
|
22251
23091
|
this.db = db;
|
|
@@ -22299,7 +23139,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22299
23139
|
*/
|
|
22300
23140
|
insertResolution(r) {
|
|
22301
23141
|
this.insertStmt.run({
|
|
22302
|
-
id:
|
|
23142
|
+
id: randomUUID7(),
|
|
22303
23143
|
findingKey: r.findingKey,
|
|
22304
23144
|
status: FindingStatus.parse(r.status),
|
|
22305
23145
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22358,13 +23198,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22358
23198
|
this.readStmt = db.prepare(
|
|
22359
23199
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22360
23200
|
);
|
|
23201
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23202
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23203
|
+
);
|
|
23204
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23205
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23206
|
+
);
|
|
22361
23207
|
}
|
|
22362
23208
|
db;
|
|
22363
23209
|
upsertStmt;
|
|
22364
23210
|
readStmt;
|
|
23211
|
+
countQuarantinedStmt;
|
|
23212
|
+
clearQuarantinedStmt;
|
|
22365
23213
|
getVerdict(ruleKey) {
|
|
22366
23214
|
return getRow(this.readStmt, { ruleKey });
|
|
22367
23215
|
}
|
|
23216
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23217
|
+
countQuarantined() {
|
|
23218
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23219
|
+
}
|
|
23220
|
+
/**
|
|
23221
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23222
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23223
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23224
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23225
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23226
|
+
*
|
|
23227
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23228
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23229
|
+
*
|
|
23230
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23231
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23232
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23233
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23234
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23235
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23236
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23237
|
+
* to restore are silently still disabled.
|
|
23238
|
+
*/
|
|
23239
|
+
clearQuarantined() {
|
|
23240
|
+
const before = this.countQuarantined();
|
|
23241
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23242
|
+
this.clearQuarantinedStmt.run();
|
|
23243
|
+
});
|
|
23244
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23245
|
+
}
|
|
22368
23246
|
setVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
22369
23247
|
failOpenTransaction(this.db, () => {
|
|
22370
23248
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs: worstProbeMs2, checkedAt: Date.now() });
|
|
@@ -22419,7 +23297,39 @@ var SqliteScanLedgerRepository = class {
|
|
|
22419
23297
|
};
|
|
22420
23298
|
|
|
22421
23299
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22422
|
-
import { randomUUID as
|
|
23300
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23301
|
+
function pageLimit(requested, fallback) {
|
|
23302
|
+
if (requested === void 0) return fallback;
|
|
23303
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23304
|
+
}
|
|
23305
|
+
function encodeReuseCursor(payload) {
|
|
23306
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23307
|
+
}
|
|
23308
|
+
function decodeReuseCursor(cursor) {
|
|
23309
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23310
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23311
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23312
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23313
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23314
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23315
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23316
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23317
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23318
|
+
}
|
|
23319
|
+
return null;
|
|
23320
|
+
}
|
|
23321
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23322
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23323
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23324
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23325
|
+
function toSighting(row) {
|
|
23326
|
+
return {
|
|
23327
|
+
location: row.location,
|
|
23328
|
+
kind: row.kind,
|
|
23329
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23330
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23331
|
+
};
|
|
23332
|
+
}
|
|
22423
23333
|
var SELECT_COLUMNS = `
|
|
22424
23334
|
pointer_id AS pointerId,
|
|
22425
23335
|
value_fingerprint AS valueFingerprint,
|
|
@@ -22603,39 +23513,67 @@ var SqliteSecretVaultRepository = class {
|
|
|
22603
23513
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22604
23514
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22605
23515
|
).run({
|
|
22606
|
-
id:
|
|
23516
|
+
id: randomUUID8(),
|
|
22607
23517
|
pointerId: entry.pointerId,
|
|
22608
23518
|
location: entry.location,
|
|
22609
23519
|
kind: entry.kind,
|
|
22610
23520
|
now
|
|
22611
23521
|
});
|
|
22612
23522
|
}
|
|
22613
|
-
|
|
23523
|
+
/**
|
|
23524
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23525
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23526
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23527
|
+
*
|
|
23528
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23529
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23530
|
+
*/
|
|
23531
|
+
sightingsFor(pointerIds) {
|
|
23532
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23533
|
+
if (pointerIds.length === 0) return byPointer;
|
|
22614
23534
|
const rows = allRows(
|
|
22615
23535
|
this.db.prepare(
|
|
22616
|
-
`SELECT location, kind, first_seen, last_seen
|
|
22617
|
-
|
|
23536
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23537
|
+
FROM secret_vault_sighting
|
|
23538
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23539
|
+
ORDER BY last_seen DESC`
|
|
22618
23540
|
),
|
|
22619
|
-
|
|
23541
|
+
pointerIds
|
|
22620
23542
|
);
|
|
23543
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23544
|
+
return byPointer;
|
|
23545
|
+
}
|
|
23546
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23547
|
+
toInventoryEntries(rows) {
|
|
23548
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
22621
23549
|
return rows.map((r) => ({
|
|
22622
|
-
|
|
22623
|
-
|
|
23550
|
+
pointerId: r.pointer_id,
|
|
23551
|
+
category: r.category,
|
|
23552
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23553
|
+
maskedMatch: r.masked_match,
|
|
23554
|
+
occurrences: r.occurrence_count,
|
|
22624
23555
|
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22625
|
-
lastSeen: new Date(r.last_seen).toISOString()
|
|
23556
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23557
|
+
revealGrantId: r.grant_id,
|
|
23558
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
22626
23559
|
}));
|
|
22627
23560
|
}
|
|
22628
23561
|
/**
|
|
22629
|
-
* The dashboard inventory
|
|
22630
|
-
* its sightings and the active
|
|
22631
|
-
* Raw-free by construction — neither
|
|
22632
|
-
* columns are selected.
|
|
23562
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23563
|
+
* value's descriptor data joined with its sightings and the active
|
|
23564
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23565
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23566
|
+
*
|
|
23567
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23568
|
+
* sees never depends on how far they have paged.
|
|
22633
23569
|
*/
|
|
22634
|
-
listInventory(now = Date.now()) {
|
|
23570
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23571
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23572
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23573
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
22635
23574
|
const rows = allRows(
|
|
22636
23575
|
this.db.prepare(
|
|
22637
|
-
`SELECT
|
|
22638
|
-
v.occurrence_count, v.first_seen, v.last_seen,
|
|
23576
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
22639
23577
|
(SELECT e.id FROM exceptions e
|
|
22640
23578
|
WHERE e.rule_id = v.rule_id
|
|
22641
23579
|
AND e.value_fingerprint = v.value_fingerprint
|
|
@@ -22643,45 +23581,109 @@ var SqliteSecretVaultRepository = class {
|
|
|
22643
23581
|
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22644
23582
|
LIMIT 1) AS grant_id
|
|
22645
23583
|
FROM secret_vault v
|
|
22646
|
-
|
|
23584
|
+
${where}
|
|
23585
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23586
|
+
LIMIT :limit`
|
|
22647
23587
|
),
|
|
22648
|
-
{
|
|
23588
|
+
bindParams({
|
|
23589
|
+
now,
|
|
23590
|
+
limit: limit + 1,
|
|
23591
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23592
|
+
})
|
|
22649
23593
|
);
|
|
22650
|
-
|
|
22651
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
23594
|
+
const hasMore = rows.length > limit;
|
|
23595
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23596
|
+
const last = page[page.length - 1];
|
|
23597
|
+
return {
|
|
23598
|
+
totals: { values: this.countEntries() },
|
|
23599
|
+
items: this.toInventoryEntries(page),
|
|
23600
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23601
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23602
|
+
};
|
|
23603
|
+
}
|
|
23604
|
+
/**
|
|
23605
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23606
|
+
* than one location — most-reused first, one page at a time.
|
|
23607
|
+
*
|
|
23608
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23609
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23610
|
+
* under-report exactly the values a reader most needs to see.
|
|
23611
|
+
*/
|
|
23612
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23613
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23614
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23615
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23616
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23617
|
+
const rows = allRows(
|
|
23618
|
+
this.db.prepare(
|
|
23619
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23620
|
+
(SELECT e.id FROM exceptions e
|
|
23621
|
+
WHERE e.rule_id = v.rule_id
|
|
23622
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23623
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23624
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23625
|
+
LIMIT 1) AS grant_id
|
|
23626
|
+
FROM secret_vault v
|
|
23627
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23628
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23629
|
+
LIMIT :limit`
|
|
23630
|
+
),
|
|
23631
|
+
bindParams({
|
|
23632
|
+
now,
|
|
23633
|
+
limit: limit + 1,
|
|
23634
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23635
|
+
})
|
|
23636
|
+
);
|
|
23637
|
+
const hasMore = rows.length > limit;
|
|
23638
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23639
|
+
const last = page[page.length - 1];
|
|
23640
|
+
return {
|
|
23641
|
+
totals: { reused: this.countReused() },
|
|
23642
|
+
items: this.toInventoryEntries(page),
|
|
23643
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23644
|
+
};
|
|
22661
23645
|
}
|
|
22662
23646
|
/**
|
|
22663
|
-
* The de-reference trail, newest first. By default the
|
|
22664
|
-
* reasons (display, view-render) are hidden and counted
|
|
22665
|
-
* that matter as a signal are the model crossings, and
|
|
22666
|
-
* render noise would defeat the audit's purpose.
|
|
23647
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23648
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23649
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23650
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23651
|
+
*
|
|
23652
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23653
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23654
|
+
* the reader pages.
|
|
22667
23655
|
*/
|
|
22668
|
-
listDerefs(
|
|
22669
|
-
const limit =
|
|
22670
|
-
const
|
|
23656
|
+
listDerefs(query = {}) {
|
|
23657
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23658
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23659
|
+
const conditions = [];
|
|
23660
|
+
if (query.includeBatched !== true) {
|
|
23661
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23662
|
+
}
|
|
23663
|
+
if (cursor !== null) {
|
|
23664
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23665
|
+
}
|
|
23666
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
22671
23667
|
const rows = allRows(
|
|
22672
23668
|
this.db.prepare(
|
|
22673
23669
|
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22674
23670
|
FROM secret_vault_deref ${where}
|
|
22675
|
-
ORDER BY at DESC,
|
|
23671
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
22676
23672
|
),
|
|
22677
|
-
{
|
|
23673
|
+
bindParams({
|
|
23674
|
+
limit: limit + 1,
|
|
23675
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23676
|
+
})
|
|
22678
23677
|
);
|
|
22679
|
-
const
|
|
23678
|
+
const hasMore = rows.length > limit;
|
|
23679
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23680
|
+
const last = page[page.length - 1];
|
|
23681
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
22680
23682
|
this.db,
|
|
22681
23683
|
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22682
23684
|
);
|
|
22683
23685
|
return {
|
|
22684
|
-
|
|
23686
|
+
items: page.map((r) => ({
|
|
22685
23687
|
id: r.id,
|
|
22686
23688
|
pointerId: r.pointer_id,
|
|
22687
23689
|
at: new Date(r.at).toISOString(),
|
|
@@ -22691,12 +23693,20 @@ var SqliteSecretVaultRepository = class {
|
|
|
22691
23693
|
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22692
23694
|
pointerCount: r.pointer_count
|
|
22693
23695
|
})),
|
|
23696
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
22694
23697
|
hiddenBatched
|
|
22695
23698
|
};
|
|
22696
23699
|
}
|
|
22697
23700
|
countEntries() {
|
|
22698
23701
|
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22699
23702
|
}
|
|
23703
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23704
|
+
countReused() {
|
|
23705
|
+
return countScalar(
|
|
23706
|
+
this.db,
|
|
23707
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23708
|
+
);
|
|
23709
|
+
}
|
|
22700
23710
|
};
|
|
22701
23711
|
|
|
22702
23712
|
// ../../packages/persistence/src/repositories/security.ts
|
|
@@ -22711,7 +23721,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22711
23721
|
var SCAN_COVERAGE = [
|
|
22712
23722
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22713
23723
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22714
|
-
{ provider: "codex", coverage:
|
|
23724
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23725
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23726
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22715
23727
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22716
23728
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22717
23729
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -23044,7 +24056,7 @@ var SqliteSecurityRepository = class {
|
|
|
23044
24056
|
};
|
|
23045
24057
|
|
|
23046
24058
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
23047
|
-
import { randomUUID as
|
|
24059
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
23048
24060
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
23049
24061
|
var IN_CHUNK = 500;
|
|
23050
24062
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -23300,7 +24312,7 @@ var SqliteSharesRepository = class {
|
|
|
23300
24312
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
23301
24313
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
23302
24314
|
).run({
|
|
23303
|
-
id:
|
|
24315
|
+
id: randomUUID9(),
|
|
23304
24316
|
destinationId,
|
|
23305
24317
|
host: dest.host,
|
|
23306
24318
|
decision,
|
|
@@ -23449,7 +24461,7 @@ var SqliteSharesRepository = class {
|
|
|
23449
24461
|
let destinationId = destIds.get(hit.host);
|
|
23450
24462
|
if (destinationId === void 0) {
|
|
23451
24463
|
destStmt.run({
|
|
23452
|
-
id:
|
|
24464
|
+
id: randomUUID9(),
|
|
23453
24465
|
kind: hit.kind,
|
|
23454
24466
|
name: hit.name,
|
|
23455
24467
|
host: hit.host,
|
|
@@ -23465,7 +24477,7 @@ var SqliteSharesRepository = class {
|
|
|
23465
24477
|
let endpointId = endpointIds.get(endpointKey);
|
|
23466
24478
|
if (endpointId === void 0) {
|
|
23467
24479
|
endpointStmt.run({
|
|
23468
|
-
id:
|
|
24480
|
+
id: randomUUID9(),
|
|
23469
24481
|
destinationId,
|
|
23470
24482
|
method: hit.method,
|
|
23471
24483
|
transport: hit.transport,
|
|
@@ -23478,7 +24490,7 @@ var SqliteSharesRepository = class {
|
|
|
23478
24490
|
endpointIds.set(endpointKey, endpointId);
|
|
23479
24491
|
}
|
|
23480
24492
|
siteStmt.run({
|
|
23481
|
-
id:
|
|
24493
|
+
id: randomUUID9(),
|
|
23482
24494
|
endpointId,
|
|
23483
24495
|
project: input.project,
|
|
23484
24496
|
projectKey: input.projectKey,
|
|
@@ -23843,6 +24855,9 @@ function purgeSampleData(db) {
|
|
|
23843
24855
|
}
|
|
23844
24856
|
|
|
23845
24857
|
// ../../packages/persistence/src/database.ts
|
|
24858
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24859
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24860
|
+
);
|
|
23846
24861
|
function linkHost(input, hostId) {
|
|
23847
24862
|
return hostId ? { ...input, hostId } : input;
|
|
23848
24863
|
}
|
|
@@ -23864,21 +24879,34 @@ function openWithPragmas(file2) {
|
|
|
23864
24879
|
}
|
|
23865
24880
|
return db;
|
|
23866
24881
|
}
|
|
23867
|
-
function backupLegacyStore(file2) {
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
|
|
24882
|
+
function backupLegacyStore(db, file2) {
|
|
24883
|
+
reapStalePartials(file2);
|
|
24884
|
+
const backup = backupPath(file2, "legacy");
|
|
24885
|
+
let snapshotted = false;
|
|
24886
|
+
let snapshotError;
|
|
24887
|
+
try {
|
|
24888
|
+
snapshotStore(db, backup);
|
|
24889
|
+
snapshotted = true;
|
|
24890
|
+
} catch (error51) {
|
|
24891
|
+
snapshotError = error51;
|
|
24892
|
+
} finally {
|
|
24893
|
+
db.close();
|
|
23873
24894
|
}
|
|
24895
|
+
if (!snapshotted) {
|
|
24896
|
+
akaWarn(
|
|
24897
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24898
|
+
);
|
|
24899
|
+
moveStoreAside(file2, backup);
|
|
24900
|
+
return backup;
|
|
24901
|
+
}
|
|
24902
|
+
discardStore(file2, backup);
|
|
23874
24903
|
return backup;
|
|
23875
24904
|
}
|
|
23876
24905
|
function openAndInitialize(file2) {
|
|
23877
24906
|
let db = openWithPragmas(file2);
|
|
23878
24907
|
try {
|
|
23879
24908
|
if (isForeignSqliteLineage(db)) {
|
|
23880
|
-
db
|
|
23881
|
-
const backup = backupLegacyStore(file2);
|
|
24909
|
+
const backup = backupLegacyStore(db, file2);
|
|
23882
24910
|
db = openWithPragmas(file2);
|
|
23883
24911
|
akaWarn(
|
|
23884
24912
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23922,7 +24950,7 @@ function openAndInitialize(file2) {
|
|
|
23922
24950
|
}
|
|
23923
24951
|
function openLocalDatabase(dir) {
|
|
23924
24952
|
ensureDataDirSync(dir);
|
|
23925
|
-
const file2 =
|
|
24953
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23926
24954
|
const {
|
|
23927
24955
|
db,
|
|
23928
24956
|
events,
|
|
@@ -24039,7 +25067,7 @@ function openLocalDatabase(dir) {
|
|
|
24039
25067
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
24040
25068
|
if (!definitionId) continue;
|
|
24041
25069
|
inspectionFindings.insertFinding({
|
|
24042
|
-
id:
|
|
25070
|
+
id: randomUUID10(),
|
|
24043
25071
|
auditEventId: record2.scanEvent.id,
|
|
24044
25072
|
inspectionDefinitionId: definitionId,
|
|
24045
25073
|
span: finding.span,
|
|
@@ -24145,10 +25173,26 @@ function openLocalDatabase(dir) {
|
|
|
24145
25173
|
transaction,
|
|
24146
25174
|
close: () => {
|
|
24147
25175
|
db.close();
|
|
24148
|
-
}
|
|
25176
|
+
},
|
|
25177
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25178
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
24149
25179
|
};
|
|
24150
25180
|
}
|
|
24151
25181
|
|
|
25182
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25183
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25184
|
+
import {
|
|
25185
|
+
closeSync,
|
|
25186
|
+
existsSync as existsSync2,
|
|
25187
|
+
openSync,
|
|
25188
|
+
readFileSync,
|
|
25189
|
+
rmSync as rmSync3,
|
|
25190
|
+
statSync as statSync2,
|
|
25191
|
+
writeFileSync as writeFileSync2
|
|
25192
|
+
} from "fs";
|
|
25193
|
+
import { hostname as hostname3 } from "os";
|
|
25194
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25195
|
+
|
|
24152
25196
|
// ../../packages/persistence/src/finding-key.ts
|
|
24153
25197
|
import { createHash as createHash3 } from "crypto";
|
|
24154
25198
|
function normalizeFilePath(filePath) {
|
|
@@ -24161,13 +25205,13 @@ function computeFindingKey(input) {
|
|
|
24161
25205
|
|
|
24162
25206
|
// ../../packages/persistence/src/fingerprint.ts
|
|
24163
25207
|
import { createHmac, randomBytes } from "crypto";
|
|
24164
|
-
import { existsSync as
|
|
24165
|
-
import { join as
|
|
25208
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25209
|
+
import { join as join3 } from "path";
|
|
24166
25210
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
24167
|
-
var
|
|
25211
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
24168
25212
|
var KEY_MATERIAL_BYTES = 32;
|
|
24169
25213
|
function keyFilePath(dataDir2) {
|
|
24170
|
-
return
|
|
25214
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
24171
25215
|
}
|
|
24172
25216
|
function parseKeyFile(raw) {
|
|
24173
25217
|
const parsed = JSON.parse(raw);
|
|
@@ -24205,8 +25249,8 @@ var FloorUnreadableError = class extends Error {
|
|
|
24205
25249
|
}
|
|
24206
25250
|
};
|
|
24207
25251
|
function storedKeyVersionFloor(dataDir2) {
|
|
24208
|
-
const file2 =
|
|
24209
|
-
if (!
|
|
25252
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25253
|
+
if (!existsSync3(file2)) return 0;
|
|
24210
25254
|
let db;
|
|
24211
25255
|
try {
|
|
24212
25256
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
@@ -24231,18 +25275,36 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
24231
25275
|
db?.close();
|
|
24232
25276
|
}
|
|
24233
25277
|
}
|
|
24234
|
-
function
|
|
25278
|
+
function serializeKey(key) {
|
|
25279
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
25280
|
+
}
|
|
25281
|
+
function createKeyFile(dataDir2, key) {
|
|
24235
25282
|
ensureDataDirSync(dataDir2);
|
|
24236
25283
|
const file2 = keyFilePath(dataDir2);
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
24240
|
-
|
|
25284
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
25285
|
+
`)) return key;
|
|
25286
|
+
const winner = readFingerprintKey(dataDir2);
|
|
25287
|
+
if (winner) {
|
|
25288
|
+
tightenFile(file2);
|
|
25289
|
+
return winner;
|
|
25290
|
+
}
|
|
25291
|
+
const occupant = classifyOccupant(file2);
|
|
25292
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
25293
|
+
}
|
|
25294
|
+
function occupantMessage(file2, kind) {
|
|
25295
|
+
switch (kind) {
|
|
25296
|
+
case "symlink":
|
|
25297
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
25298
|
+
case "gone":
|
|
25299
|
+
return "exception key file was removed while it was being created";
|
|
25300
|
+
case "unknown":
|
|
25301
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
25302
|
+
}
|
|
24241
25303
|
}
|
|
24242
25304
|
function readFingerprintKey(dataDir2) {
|
|
24243
25305
|
let raw;
|
|
24244
25306
|
try {
|
|
24245
|
-
raw =
|
|
25307
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
24246
25308
|
} catch (err) {
|
|
24247
25309
|
if (err.code === "ENOENT") return null;
|
|
24248
25310
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -24255,7 +25317,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
24255
25317
|
tightenFile(keyFilePath(dataDir2));
|
|
24256
25318
|
return existing;
|
|
24257
25319
|
}
|
|
24258
|
-
return
|
|
25320
|
+
return createKeyFile(dataDir2, {
|
|
24259
25321
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
24260
25322
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
24261
25323
|
});
|
|
@@ -24268,18 +25330,18 @@ function fingerprintValue(key, raw) {
|
|
|
24268
25330
|
import { renameSync as renameSync3 } from "fs";
|
|
24269
25331
|
import { mkdir } from "fs/promises";
|
|
24270
25332
|
import { homedir } from "os";
|
|
24271
|
-
import { join as
|
|
25333
|
+
import { join as join4 } from "path";
|
|
24272
25334
|
function defaultDataDir() {
|
|
24273
|
-
return
|
|
25335
|
+
return join4(homedir(), ".aka");
|
|
24274
25336
|
}
|
|
24275
25337
|
function settingsDir(base = defaultDataDir()) {
|
|
24276
|
-
return
|
|
25338
|
+
return join4(base, "settings");
|
|
24277
25339
|
}
|
|
24278
25340
|
function dataDir(base = defaultDataDir()) {
|
|
24279
|
-
return
|
|
25341
|
+
return join4(base, "data");
|
|
24280
25342
|
}
|
|
24281
25343
|
function dbPath(base = defaultDataDir()) {
|
|
24282
|
-
return
|
|
25344
|
+
return join4(dataDir(base), "aka.db");
|
|
24283
25345
|
}
|
|
24284
25346
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
24285
25347
|
ensureDataDirSync(dir);
|
|
@@ -24292,8 +25354,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24292
25354
|
for (const { name, dest } of moves) {
|
|
24293
25355
|
try {
|
|
24294
25356
|
ensureDataDirSync(dest);
|
|
24295
|
-
const moved =
|
|
24296
|
-
renameSync3(
|
|
25357
|
+
const moved = join4(dest, name);
|
|
25358
|
+
renameSync3(join4(base, name), moved);
|
|
24297
25359
|
tightenFile(moved);
|
|
24298
25360
|
} catch {
|
|
24299
25361
|
}
|
|
@@ -24301,10 +25363,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
24301
25363
|
}
|
|
24302
25364
|
|
|
24303
25365
|
// ../../packages/persistence/src/settings.ts
|
|
24304
|
-
import { readFileSync as
|
|
24305
|
-
import { join as
|
|
25366
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25367
|
+
import { join as join5 } from "path";
|
|
25368
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
24306
25369
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
24307
|
-
const record2 = readJson(
|
|
25370
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
24308
25371
|
if (!record2) return defaultWorkspaceSettings();
|
|
24309
25372
|
try {
|
|
24310
25373
|
return WorkspaceSettings.parse(record2);
|
|
@@ -24315,7 +25378,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
24315
25378
|
function readJson(file2) {
|
|
24316
25379
|
let text;
|
|
24317
25380
|
try {
|
|
24318
|
-
text =
|
|
25381
|
+
text = readFileSync3(file2, "utf8");
|
|
24319
25382
|
} catch {
|
|
24320
25383
|
return null;
|
|
24321
25384
|
}
|
|
@@ -24337,27 +25400,27 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
24337
25400
|
import {
|
|
24338
25401
|
chmodSync as chmodSync2,
|
|
24339
25402
|
mkdirSync as mkdirSync2,
|
|
24340
|
-
readFileSync as
|
|
25403
|
+
readFileSync as readFileSync4,
|
|
24341
25404
|
renameSync as renameSync4,
|
|
24342
|
-
rmSync as
|
|
24343
|
-
statSync,
|
|
24344
|
-
writeFileSync as
|
|
25405
|
+
rmSync as rmSync4,
|
|
25406
|
+
statSync as statSync3,
|
|
25407
|
+
writeFileSync as writeFileSync3
|
|
24345
25408
|
} from "fs";
|
|
24346
|
-
import { join as
|
|
25409
|
+
import { join as join6 } from "path";
|
|
24347
25410
|
|
|
24348
25411
|
// ../../packages/persistence/src/vault/vault.ts
|
|
24349
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25412
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
24350
25413
|
|
|
24351
25414
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
24352
|
-
import { existsSync as
|
|
24353
|
-
import { join as
|
|
25415
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25416
|
+
import { join as join7 } from "path";
|
|
24354
25417
|
var MARKER = "warn-era-capped";
|
|
24355
25418
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
24356
25419
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
24357
|
-
const marker =
|
|
24358
|
-
if (
|
|
25420
|
+
const marker = join7(dataDir2, MARKER);
|
|
25421
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
24359
25422
|
const capped = db.policies.capCategoryActions();
|
|
24360
|
-
|
|
25423
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
24361
25424
|
`, { mode: DATA_FILE_MODE });
|
|
24362
25425
|
return { capped };
|
|
24363
25426
|
}
|
|
@@ -24411,11 +25474,11 @@ function resolveProvider() {
|
|
|
24411
25474
|
}
|
|
24412
25475
|
|
|
24413
25476
|
// ../../packages/plugin-sdk/src/config.ts
|
|
24414
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25477
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
24415
25478
|
try {
|
|
24416
25479
|
ensureLayoutDirSync(base);
|
|
24417
|
-
const settingsFile =
|
|
24418
|
-
if (
|
|
25480
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25481
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
24419
25482
|
} catch {
|
|
24420
25483
|
}
|
|
24421
25484
|
migrateLegacyLayout(base);
|
|
@@ -24426,21 +25489,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
24426
25489
|
dbPath: dbPath(base),
|
|
24427
25490
|
settingsDir: settingsDir(base),
|
|
24428
25491
|
onboarded: settings.onboardedAt != null,
|
|
24429
|
-
provider: resolveProviderSafe()
|
|
25492
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
24430
25493
|
};
|
|
24431
25494
|
}
|
|
24432
|
-
function resolveProviderSafe() {
|
|
25495
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
24433
25496
|
try {
|
|
24434
|
-
return
|
|
25497
|
+
return resolveProviderFn();
|
|
24435
25498
|
} catch {
|
|
24436
25499
|
return { provider: "anthropic" };
|
|
24437
25500
|
}
|
|
24438
25501
|
}
|
|
24439
25502
|
|
|
24440
25503
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
24441
|
-
import { readdirSync, readFileSync as
|
|
25504
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
24442
25505
|
import { homedir as homedir2 } from "os";
|
|
24443
|
-
import { basename as
|
|
25506
|
+
import { basename as basename3, join as join10 } from "path";
|
|
24444
25507
|
|
|
24445
25508
|
// ../../packages/detections/src/egress/registry.ts
|
|
24446
25509
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -25430,11 +26493,11 @@ var EXACT_KIND_BY_BASENAME = {
|
|
|
25430
26493
|
"composer.json": "composer.json",
|
|
25431
26494
|
"packages.config": "packages.config"
|
|
25432
26495
|
};
|
|
25433
|
-
function manifestKindOf(
|
|
25434
|
-
if (LOCKFILE_BASENAMES.has(
|
|
25435
|
-
const exact = Object.hasOwn(EXACT_KIND_BY_BASENAME,
|
|
26496
|
+
function manifestKindOf(basename7) {
|
|
26497
|
+
if (LOCKFILE_BASENAMES.has(basename7)) return null;
|
|
26498
|
+
const exact = Object.hasOwn(EXACT_KIND_BY_BASENAME, basename7) ? EXACT_KIND_BY_BASENAME[basename7] : void 0;
|
|
25436
26499
|
if (exact !== void 0) return exact;
|
|
25437
|
-
if (
|
|
26500
|
+
if (basename7.endsWith(".csproj")) return "csproj";
|
|
25438
26501
|
return null;
|
|
25439
26502
|
}
|
|
25440
26503
|
function extractManifestSdks(text, kind) {
|
|
@@ -25849,6 +26912,40 @@ function escapeRegExp2(value) {
|
|
|
25849
26912
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25850
26913
|
}
|
|
25851
26914
|
|
|
26915
|
+
// ../../packages/detections/src/regex-cache.ts
|
|
26916
|
+
var singles = /* @__PURE__ */ new WeakMap();
|
|
26917
|
+
var keywordLists = /* @__PURE__ */ new WeakMap();
|
|
26918
|
+
var labelLists = /* @__PURE__ */ new WeakMap();
|
|
26919
|
+
function listCache(kind) {
|
|
26920
|
+
return kind === "keyword" ? keywordLists : labelLists;
|
|
26921
|
+
}
|
|
26922
|
+
function memoizedRegExp(owner, build) {
|
|
26923
|
+
const cached2 = singles.get(owner);
|
|
26924
|
+
if (cached2 !== void 0) {
|
|
26925
|
+
cached2.lastIndex = 0;
|
|
26926
|
+
return cached2;
|
|
26927
|
+
}
|
|
26928
|
+
const compiled = build();
|
|
26929
|
+
singles.set(owner, compiled);
|
|
26930
|
+
return compiled;
|
|
26931
|
+
}
|
|
26932
|
+
function memoizedRegExpList(kind, owner, build) {
|
|
26933
|
+
const cache = listCache(kind);
|
|
26934
|
+
const cached2 = cache.get(owner);
|
|
26935
|
+
if (cached2 !== void 0) {
|
|
26936
|
+
if (cached2.stateful) {
|
|
26937
|
+
for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
|
|
26938
|
+
}
|
|
26939
|
+
return cached2.entries;
|
|
26940
|
+
}
|
|
26941
|
+
const entries = build();
|
|
26942
|
+
cache.set(owner, {
|
|
26943
|
+
entries,
|
|
26944
|
+
stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
|
|
26945
|
+
});
|
|
26946
|
+
return entries;
|
|
26947
|
+
}
|
|
26948
|
+
|
|
25852
26949
|
// ../../packages/detections/src/matchers/limits.ts
|
|
25853
26950
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
25854
26951
|
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
@@ -25859,10 +26956,17 @@ var KeywordMatcher2 = class {
|
|
|
25859
26956
|
if (rule.matcher.type !== "keyword") return [];
|
|
25860
26957
|
const { keywords, caseSensitive } = rule.matcher;
|
|
25861
26958
|
const spans = [];
|
|
25862
|
-
|
|
25863
|
-
|
|
26959
|
+
const compiled = memoizedRegExpList(
|
|
26960
|
+
"keyword",
|
|
26961
|
+
rule.matcher,
|
|
26962
|
+
() => keywords.map((kw) => {
|
|
26963
|
+
if (kw.length === 0) return void 0;
|
|
26964
|
+
return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
26965
|
+
})
|
|
26966
|
+
);
|
|
26967
|
+
for (const re of compiled) {
|
|
26968
|
+
if (re === void 0) continue;
|
|
25864
26969
|
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
25865
|
-
const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
25866
26970
|
let m;
|
|
25867
26971
|
while ((m = re.exec(text)) !== null) {
|
|
25868
26972
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -25878,7 +26982,10 @@ var RegexMatcher2 = class {
|
|
|
25878
26982
|
match(text, rule) {
|
|
25879
26983
|
if (rule.matcher.type !== "regex") return [];
|
|
25880
26984
|
const { pattern, flags, captureGroup } = rule.matcher;
|
|
25881
|
-
const re =
|
|
26985
|
+
const re = memoizedRegExp(
|
|
26986
|
+
rule.matcher,
|
|
26987
|
+
() => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
|
|
26988
|
+
);
|
|
25882
26989
|
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
25883
26990
|
const spans = [];
|
|
25884
26991
|
let m;
|
|
@@ -25986,11 +27093,15 @@ function isCorroborated(candidate, candidates, text) {
|
|
|
25986
27093
|
const labels = req.labels;
|
|
25987
27094
|
if (labels && labels.length > 0) {
|
|
25988
27095
|
const haystack = text.slice(Math.max(0, winStart), winEnd);
|
|
25989
|
-
for (const
|
|
25990
|
-
|
|
25991
|
-
|
|
25992
|
-
|
|
25993
|
-
|
|
27096
|
+
for (const re of memoizedRegExpList(
|
|
27097
|
+
"label",
|
|
27098
|
+
req,
|
|
27099
|
+
() => labels.map((label) => {
|
|
27100
|
+
const trimmed = label.trim();
|
|
27101
|
+
return trimmed.length === 0 ? void 0 : new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
|
|
27102
|
+
})
|
|
27103
|
+
)) {
|
|
27104
|
+
if (re?.test(haystack)) return true;
|
|
25994
27105
|
}
|
|
25995
27106
|
}
|
|
25996
27107
|
return false;
|
|
@@ -26258,13 +27369,14 @@ function probesFor(rule) {
|
|
|
26258
27369
|
const derived = rule.matcher.type === "regex" ? derivedProbes(rule.matcher.pattern) : [];
|
|
26259
27370
|
return [...derived, ...EXPONENTIAL_PROBES, ...POLYNOMIAL_PROBES];
|
|
26260
27371
|
}
|
|
26261
|
-
|
|
27372
|
+
var wallClock = () => performance.now();
|
|
27373
|
+
function worstProbeMs(rule, now = wallClock) {
|
|
26262
27374
|
let ms = 0;
|
|
26263
27375
|
let probe = "";
|
|
26264
27376
|
for (const text of probesFor(rule)) {
|
|
26265
|
-
const start =
|
|
27377
|
+
const start = now();
|
|
26266
27378
|
scan(text, [rule]);
|
|
26267
|
-
const elapsed =
|
|
27379
|
+
const elapsed = now() - start;
|
|
26268
27380
|
if (elapsed > ms) {
|
|
26269
27381
|
ms = elapsed;
|
|
26270
27382
|
probe = text;
|
|
@@ -27932,7 +29044,7 @@ var gcp_service_account_default = {
|
|
|
27932
29044
|
severity: "critical",
|
|
27933
29045
|
matcher: {
|
|
27934
29046
|
type: "regex",
|
|
27935
|
-
pattern: "
|
|
29047
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27936
29048
|
flags: "g"
|
|
27937
29049
|
},
|
|
27938
29050
|
examples: [
|
|
@@ -28311,8 +29423,8 @@ function bundledDetections() {
|
|
|
28311
29423
|
}
|
|
28312
29424
|
|
|
28313
29425
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
28314
|
-
import { existsSync as
|
|
28315
|
-
import { basename, dirname, isAbsolute, join as
|
|
29426
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29427
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
28316
29428
|
function resolveRepoIdentity(cwd) {
|
|
28317
29429
|
try {
|
|
28318
29430
|
const root = findGitRoot(cwd);
|
|
@@ -28325,7 +29437,7 @@ function resolveRepoIdentity(cwd) {
|
|
|
28325
29437
|
// win32) so the persistence layer's `/`-separated checkout-path patterns
|
|
28326
29438
|
// (the ghost sweep + the read-side worktree filter) match it as written.
|
|
28327
29439
|
url: url2 ?? headRoot.split(sep2).join("/"),
|
|
28328
|
-
name: (url2 ? slugFromUrl(url2) : void 0) ??
|
|
29440
|
+
name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
|
|
28329
29441
|
};
|
|
28330
29442
|
} catch {
|
|
28331
29443
|
return void 0;
|
|
@@ -28341,36 +29453,36 @@ function resolveWorktreeRoot(cwd) {
|
|
|
28341
29453
|
function findGitRoot(start) {
|
|
28342
29454
|
let dir = start;
|
|
28343
29455
|
for (; ; ) {
|
|
28344
|
-
if (
|
|
28345
|
-
const parent =
|
|
29456
|
+
if (existsSync6(join9(dir, ".git"))) return dir;
|
|
29457
|
+
const parent = dirname2(dir);
|
|
28346
29458
|
if (parent === dir) return void 0;
|
|
28347
29459
|
dir = parent;
|
|
28348
29460
|
}
|
|
28349
29461
|
}
|
|
28350
29462
|
function resolveGitContext(root) {
|
|
28351
|
-
const dotGit =
|
|
29463
|
+
const dotGit = join9(root, ".git");
|
|
28352
29464
|
try {
|
|
28353
|
-
if (
|
|
28354
|
-
return { configPath:
|
|
29465
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
29466
|
+
return { configPath: join9(dotGit, "config"), headRoot: root };
|
|
28355
29467
|
}
|
|
28356
29468
|
} catch {
|
|
28357
29469
|
return void 0;
|
|
28358
29470
|
}
|
|
28359
29471
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
28360
29472
|
if (!target) return void 0;
|
|
28361
|
-
const gitdir = isAbsolute(target) ? target :
|
|
28362
|
-
if (
|
|
28363
|
-
return { configPath:
|
|
29473
|
+
const gitdir = isAbsolute(target) ? target : join9(root, target);
|
|
29474
|
+
if (existsSync6(join9(gitdir, "config"))) {
|
|
29475
|
+
return { configPath: join9(gitdir, "config"), headRoot: root };
|
|
28364
29476
|
}
|
|
28365
|
-
const commonRaw = safeRead(
|
|
29477
|
+
const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
|
|
28366
29478
|
if (!commonRaw) return void 0;
|
|
28367
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
28368
|
-
const headRoot =
|
|
28369
|
-
return { configPath:
|
|
29479
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
|
|
29480
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
|
|
29481
|
+
return { configPath: join9(commonGitDir, "config"), headRoot };
|
|
28370
29482
|
}
|
|
28371
29483
|
function safeRead(path) {
|
|
28372
29484
|
try {
|
|
28373
|
-
return
|
|
29485
|
+
return readFileSync5(path, "utf8");
|
|
28374
29486
|
} catch {
|
|
28375
29487
|
return void 0;
|
|
28376
29488
|
}
|
|
@@ -28408,13 +29520,13 @@ function slugFromUrl(url2) {
|
|
|
28408
29520
|
}
|
|
28409
29521
|
|
|
28410
29522
|
// ../../packages/plugin-sdk/src/events.ts
|
|
28411
|
-
import { createHash as createHash4, randomUUID as
|
|
29523
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28412
29524
|
function contentHashOf(text) {
|
|
28413
29525
|
return createHash4("sha256").update(text).digest("hex");
|
|
28414
29526
|
}
|
|
28415
29527
|
function buildIngestEvent(input) {
|
|
28416
29528
|
return {
|
|
28417
|
-
id:
|
|
29529
|
+
id: randomUUID13(),
|
|
28418
29530
|
sourceTool: input.sourceTool,
|
|
28419
29531
|
kind: input.kind,
|
|
28420
29532
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -28425,21 +29537,473 @@ function buildIngestEvent(input) {
|
|
|
28425
29537
|
// SDK boot in the fail-open hook path). Preserve any id the caller already set.
|
|
28426
29538
|
metadata: {
|
|
28427
29539
|
...input.metadata,
|
|
28428
|
-
correlationId: input.metadata?.correlationId ??
|
|
29540
|
+
correlationId: input.metadata?.correlationId ?? randomUUID13()
|
|
29541
|
+
}
|
|
29542
|
+
};
|
|
29543
|
+
}
|
|
29544
|
+
|
|
29545
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29546
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29547
|
+
import { fileURLToPath } from "url";
|
|
29548
|
+
import { Worker } from "worker_threads";
|
|
29549
|
+
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
29550
|
+
var ISOLATED_PROBE_BUDGET_MS = 1e3;
|
|
29551
|
+
var ISOLATED_START_BUDGET_MS = 5e3;
|
|
29552
|
+
var ATTRIBUTION_MIN_RULE_MS = 500;
|
|
29553
|
+
var ATTRIBUTION_MIN_SHARE = 0.5;
|
|
29554
|
+
var resolvedWorkerUrl;
|
|
29555
|
+
function resolveWorkerUrl() {
|
|
29556
|
+
if (resolvedWorkerUrl !== void 0) return resolvedWorkerUrl ?? void 0;
|
|
29557
|
+
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
29558
|
+
const candidate = new URL(name, import.meta.url);
|
|
29559
|
+
try {
|
|
29560
|
+
if (existsSync7(fileURLToPath(candidate))) {
|
|
29561
|
+
resolvedWorkerUrl = candidate;
|
|
29562
|
+
return candidate;
|
|
29563
|
+
}
|
|
29564
|
+
} catch {
|
|
29565
|
+
}
|
|
29566
|
+
}
|
|
29567
|
+
resolvedWorkerUrl = null;
|
|
29568
|
+
return void 0;
|
|
29569
|
+
}
|
|
29570
|
+
function messageOf(error51) {
|
|
29571
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
29572
|
+
}
|
|
29573
|
+
function createIsolatedScanner(data, opts = {}) {
|
|
29574
|
+
const budgetMs = opts.budgetMs ?? ISOLATED_SCAN_BUDGET_MS;
|
|
29575
|
+
const probeBudgetMs = opts.probeBudgetMs ?? ISOLATED_PROBE_BUDGET_MS;
|
|
29576
|
+
const startBudgetMs = opts.startBudgetMs ?? ISOLATED_START_BUDGET_MS;
|
|
29577
|
+
const minAttributionMs = opts.minAttributionMs ?? ATTRIBUTION_MIN_RULE_MS;
|
|
29578
|
+
let worker;
|
|
29579
|
+
let readyWorker;
|
|
29580
|
+
let broken;
|
|
29581
|
+
let closed = false;
|
|
29582
|
+
let nextJobId = 1;
|
|
29583
|
+
let pending;
|
|
29584
|
+
const terminating = /* @__PURE__ */ new Set();
|
|
29585
|
+
let chain = Promise.resolve();
|
|
29586
|
+
function clearTimers(job) {
|
|
29587
|
+
if (job.startupTimer !== void 0) clearTimeout(job.startupTimer);
|
|
29588
|
+
if (job.timer !== void 0) clearTimeout(job.timer);
|
|
29589
|
+
}
|
|
29590
|
+
function take() {
|
|
29591
|
+
const job = pending;
|
|
29592
|
+
if (!job) return void 0;
|
|
29593
|
+
pending = void 0;
|
|
29594
|
+
clearTimers(job);
|
|
29595
|
+
worker?.unref();
|
|
29596
|
+
return job;
|
|
29597
|
+
}
|
|
29598
|
+
function failPending(outcome) {
|
|
29599
|
+
take()?.fail(outcome);
|
|
29600
|
+
}
|
|
29601
|
+
function kill(dead) {
|
|
29602
|
+
if (worker === dead) worker = void 0;
|
|
29603
|
+
if (readyWorker === dead) readyWorker = void 0;
|
|
29604
|
+
const done = dead.terminate().catch(() => void 0);
|
|
29605
|
+
terminating.add(done);
|
|
29606
|
+
void done.finally(() => terminating.delete(done));
|
|
29607
|
+
}
|
|
29608
|
+
function onDeadline(job) {
|
|
29609
|
+
if (pending !== job) return;
|
|
29610
|
+
const now = performance.now();
|
|
29611
|
+
const runningMs = now - job.progressAt;
|
|
29612
|
+
const elapsedMs = now - job.startedAt;
|
|
29613
|
+
const blamed = job.progressIndex >= 0 && runningMs >= minAttributionMs && runningMs >= elapsedMs * ATTRIBUTION_MIN_SHARE;
|
|
29614
|
+
const culpritIndex = blamed ? job.progressIndex : void 0;
|
|
29615
|
+
kill(job.worker);
|
|
29616
|
+
failPending({ status: "timeout", culpritIndex, elapsedMs });
|
|
29617
|
+
}
|
|
29618
|
+
function ensureWorker() {
|
|
29619
|
+
if (worker) return worker;
|
|
29620
|
+
const url2 = opts.workerUrl ?? resolveWorkerUrl();
|
|
29621
|
+
if (!url2) {
|
|
29622
|
+
return {
|
|
29623
|
+
error: "the scan worker script was not found next to this bundle"
|
|
29624
|
+
};
|
|
29625
|
+
}
|
|
29626
|
+
let started;
|
|
29627
|
+
try {
|
|
29628
|
+
started = new Worker(url2, { workerData: data });
|
|
29629
|
+
} catch (error51) {
|
|
29630
|
+
return { error: `could not start the scan worker: ${messageOf(error51)}` };
|
|
29631
|
+
}
|
|
29632
|
+
opts.onWorkerStart?.(started.threadId);
|
|
29633
|
+
started.on("message", (message) => {
|
|
29634
|
+
if (worker !== started) return;
|
|
29635
|
+
if (message.kind === "ready") {
|
|
29636
|
+
readyWorker = started;
|
|
29637
|
+
if (pending?.worker === started) beginDeadline(pending);
|
|
29638
|
+
return;
|
|
29639
|
+
}
|
|
29640
|
+
if (message.kind === "progress") {
|
|
29641
|
+
if (pending?.worker === started) {
|
|
29642
|
+
pending.progressIndex = message.index;
|
|
29643
|
+
pending.progressAt = performance.now();
|
|
29644
|
+
}
|
|
29645
|
+
return;
|
|
29646
|
+
}
|
|
29647
|
+
if (pending?.id !== message.id) return;
|
|
29648
|
+
if (message.kind === "failed") {
|
|
29649
|
+
failPending({
|
|
29650
|
+
status: "unavailable",
|
|
29651
|
+
reason: `the scan worker failed: ${message.message}`
|
|
29652
|
+
});
|
|
29653
|
+
return;
|
|
29654
|
+
}
|
|
29655
|
+
const job = take();
|
|
29656
|
+
if (job && !job.reply(message)) {
|
|
29657
|
+
job.fail({ status: "unavailable", reason: "the scan worker answered the wrong job" });
|
|
29658
|
+
}
|
|
29659
|
+
});
|
|
29660
|
+
started.on("error", (error51) => {
|
|
29661
|
+
if (worker !== started) return;
|
|
29662
|
+
broken = messageOf(error51);
|
|
29663
|
+
worker = void 0;
|
|
29664
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29665
|
+
failPending({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29666
|
+
});
|
|
29667
|
+
started.on("exit", () => {
|
|
29668
|
+
if (worker !== started) return;
|
|
29669
|
+
broken ??= "the scan worker exited before answering";
|
|
29670
|
+
worker = void 0;
|
|
29671
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29672
|
+
failPending({ status: "unavailable", reason: "the scan worker exited before answering" });
|
|
29673
|
+
});
|
|
29674
|
+
started.unref();
|
|
29675
|
+
worker = started;
|
|
29676
|
+
return started;
|
|
29677
|
+
}
|
|
29678
|
+
function beginDeadline(job) {
|
|
29679
|
+
if (job.startupTimer !== void 0) {
|
|
29680
|
+
clearTimeout(job.startupTimer);
|
|
29681
|
+
job.startupTimer = void 0;
|
|
29682
|
+
}
|
|
29683
|
+
if (job.timer !== void 0) return;
|
|
29684
|
+
job.startedAt = performance.now();
|
|
29685
|
+
job.progressAt = job.startedAt;
|
|
29686
|
+
job.timer = setTimeout(() => {
|
|
29687
|
+
onDeadline(job);
|
|
29688
|
+
}, job.budgetMs);
|
|
29689
|
+
}
|
|
29690
|
+
function runOne(spec, fail) {
|
|
29691
|
+
if (closed) {
|
|
29692
|
+
fail({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29693
|
+
return;
|
|
29694
|
+
}
|
|
29695
|
+
if (broken !== void 0) {
|
|
29696
|
+
fail({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29697
|
+
return;
|
|
29698
|
+
}
|
|
29699
|
+
const started = ensureWorker();
|
|
29700
|
+
if (!(started instanceof Worker)) {
|
|
29701
|
+
broken = started.error;
|
|
29702
|
+
fail({ status: "unavailable", reason: started.error });
|
|
29703
|
+
return;
|
|
29704
|
+
}
|
|
29705
|
+
const id = nextJobId++;
|
|
29706
|
+
const now = performance.now();
|
|
29707
|
+
const job = {
|
|
29708
|
+
id,
|
|
29709
|
+
worker: started,
|
|
29710
|
+
budgetMs: spec.budgetMs,
|
|
29711
|
+
startedAt: now,
|
|
29712
|
+
progressIndex: -1,
|
|
29713
|
+
progressAt: now,
|
|
29714
|
+
startupTimer: void 0,
|
|
29715
|
+
timer: void 0,
|
|
29716
|
+
reply: spec.reply,
|
|
29717
|
+
fail
|
|
29718
|
+
};
|
|
29719
|
+
pending = job;
|
|
29720
|
+
started.ref();
|
|
29721
|
+
if (readyWorker === started) {
|
|
29722
|
+
beginDeadline(job);
|
|
29723
|
+
} else {
|
|
29724
|
+
job.startupTimer = setTimeout(() => {
|
|
29725
|
+
if (pending !== job) return;
|
|
29726
|
+
kill(job.worker);
|
|
29727
|
+
failPending({
|
|
29728
|
+
status: "unavailable",
|
|
29729
|
+
reason: `the scan worker did not start within ${String(startBudgetMs)}ms`
|
|
29730
|
+
});
|
|
29731
|
+
}, startBudgetMs);
|
|
29732
|
+
}
|
|
29733
|
+
try {
|
|
29734
|
+
started.postMessage(spec.build(id));
|
|
29735
|
+
} catch (error51) {
|
|
29736
|
+
failPending({
|
|
29737
|
+
// The thread went away between the ref and the post.
|
|
29738
|
+
status: "unavailable",
|
|
29739
|
+
reason: `could not reach the scan worker: ${messageOf(error51)}`
|
|
29740
|
+
});
|
|
29741
|
+
}
|
|
29742
|
+
}
|
|
29743
|
+
function enqueue(spec) {
|
|
29744
|
+
const next = chain.then(
|
|
29745
|
+
() => new Promise((resolve) => {
|
|
29746
|
+
spec(resolve);
|
|
29747
|
+
})
|
|
29748
|
+
);
|
|
29749
|
+
chain = next.then(
|
|
29750
|
+
() => void 0,
|
|
29751
|
+
() => void 0
|
|
29752
|
+
);
|
|
29753
|
+
return next;
|
|
29754
|
+
}
|
|
29755
|
+
return {
|
|
29756
|
+
scan(text, context, scanOpts) {
|
|
29757
|
+
return enqueue((resolve) => {
|
|
29758
|
+
runOne(
|
|
29759
|
+
{
|
|
29760
|
+
budgetMs,
|
|
29761
|
+
build: (id) => ({
|
|
29762
|
+
kind: "scan",
|
|
29763
|
+
id,
|
|
29764
|
+
text,
|
|
29765
|
+
filePath: context?.filePath,
|
|
29766
|
+
attribute: scanOpts?.attribute === true
|
|
29767
|
+
}),
|
|
29768
|
+
reply: (message) => {
|
|
29769
|
+
if (message.kind !== "result") return false;
|
|
29770
|
+
resolve({ status: "ok", findings: message.findings });
|
|
29771
|
+
return true;
|
|
29772
|
+
}
|
|
29773
|
+
},
|
|
29774
|
+
resolve
|
|
29775
|
+
);
|
|
29776
|
+
});
|
|
29777
|
+
},
|
|
29778
|
+
probe(rule) {
|
|
29779
|
+
return enqueue((resolve) => {
|
|
29780
|
+
runOne(
|
|
29781
|
+
{
|
|
29782
|
+
budgetMs: probeBudgetMs,
|
|
29783
|
+
build: (id) => ({ kind: "probe", id, rule }),
|
|
29784
|
+
reply: (message) => {
|
|
29785
|
+
if (message.kind !== "probed") return false;
|
|
29786
|
+
resolve({ status: "ok", safe: message.safe, worstMs: message.worstMs });
|
|
29787
|
+
return true;
|
|
29788
|
+
}
|
|
29789
|
+
},
|
|
29790
|
+
resolve
|
|
29791
|
+
);
|
|
29792
|
+
});
|
|
29793
|
+
},
|
|
29794
|
+
async close() {
|
|
29795
|
+
closed = true;
|
|
29796
|
+
const live = worker;
|
|
29797
|
+
worker = void 0;
|
|
29798
|
+
readyWorker = void 0;
|
|
29799
|
+
failPending({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29800
|
+
if (live) kill(live);
|
|
29801
|
+
await Promise.all([...terminating]);
|
|
29802
|
+
}
|
|
29803
|
+
};
|
|
29804
|
+
}
|
|
29805
|
+
|
|
29806
|
+
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
29807
|
+
var PASS_BUDGET_MS = 2e3;
|
|
29808
|
+
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
29809
|
+
function ruleProbeKey(rule) {
|
|
29810
|
+
if (rule.matcher.type !== "regex") return void 0;
|
|
29811
|
+
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
29812
|
+
}
|
|
29813
|
+
function warn(rule, verb, detail, recoverable) {
|
|
29814
|
+
const hint = recoverable ? ` (${UNQUARANTINE_HINT})` : "";
|
|
29815
|
+
process.stderr.write(`[aka] ${verb} rule "${rule.id}": ${detail}${hint}
|
|
29816
|
+
`);
|
|
29817
|
+
}
|
|
29818
|
+
function warnQuarantined(rule, worstMs, cached2) {
|
|
29819
|
+
warn(
|
|
29820
|
+
rule,
|
|
29821
|
+
"quarantined",
|
|
29822
|
+
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.",
|
|
29823
|
+
cached2
|
|
29824
|
+
);
|
|
29825
|
+
}
|
|
29826
|
+
function warnUnmeasured(rule) {
|
|
29827
|
+
warn(
|
|
29828
|
+
rule,
|
|
29829
|
+
"skipped",
|
|
29830
|
+
"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.",
|
|
29831
|
+
false
|
|
29832
|
+
);
|
|
29833
|
+
}
|
|
29834
|
+
function warnUnmeasurable(reason, count) {
|
|
29835
|
+
process.stderr.write(
|
|
29836
|
+
`[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.
|
|
29837
|
+
`
|
|
29838
|
+
);
|
|
29839
|
+
}
|
|
29840
|
+
async function quarantineRule(gateway, rule, worstMs, detail) {
|
|
29841
|
+
const key = ruleProbeKey(rule);
|
|
29842
|
+
let cached2 = false;
|
|
29843
|
+
if (key !== void 0) {
|
|
29844
|
+
try {
|
|
29845
|
+
await gateway.setRuleProbeVerdict(key, "quarantined", worstMs);
|
|
29846
|
+
cached2 = true;
|
|
29847
|
+
} catch {
|
|
29848
|
+
}
|
|
29849
|
+
}
|
|
29850
|
+
warn(rule, "quarantined", detail, cached2);
|
|
29851
|
+
}
|
|
29852
|
+
async function filterUnsafeRules(rules, gateway, opts) {
|
|
29853
|
+
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
29854
|
+
const prober = opts?.prober;
|
|
29855
|
+
const passStart = performance.now();
|
|
29856
|
+
const safe = [];
|
|
29857
|
+
const unmeasurable = /* @__PURE__ */ new Map();
|
|
29858
|
+
try {
|
|
29859
|
+
for (const rule of rules) {
|
|
29860
|
+
const key = ruleProbeKey(rule);
|
|
29861
|
+
if (key === void 0) {
|
|
29862
|
+
safe.push(rule);
|
|
29863
|
+
continue;
|
|
29864
|
+
}
|
|
29865
|
+
let cached2;
|
|
29866
|
+
try {
|
|
29867
|
+
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
29868
|
+
} catch {
|
|
29869
|
+
cached2 = void 0;
|
|
29870
|
+
}
|
|
29871
|
+
if (cached2) {
|
|
29872
|
+
if (cached2.verdict === "safe") safe.push(rule);
|
|
29873
|
+
else warnQuarantined(rule, cached2.worstProbeMs, true);
|
|
29874
|
+
continue;
|
|
29875
|
+
}
|
|
29876
|
+
if (performance.now() - passStart >= passBudgetMs) {
|
|
29877
|
+
warnUnmeasured(rule);
|
|
29878
|
+
continue;
|
|
29879
|
+
}
|
|
29880
|
+
let isSafe;
|
|
29881
|
+
let worstMs;
|
|
29882
|
+
if (prober) {
|
|
29883
|
+
const outcome = await prober.probe(rule);
|
|
29884
|
+
if (outcome.status === "unavailable") {
|
|
29885
|
+
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29886
|
+
continue;
|
|
29887
|
+
}
|
|
29888
|
+
isSafe = outcome.status === "ok" ? outcome.safe : false;
|
|
29889
|
+
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
29890
|
+
} else {
|
|
29891
|
+
try {
|
|
29892
|
+
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
29893
|
+
} catch {
|
|
29894
|
+
isSafe = false;
|
|
29895
|
+
worstMs = Number.POSITIVE_INFINITY;
|
|
29896
|
+
}
|
|
29897
|
+
}
|
|
29898
|
+
let persisted = false;
|
|
29899
|
+
try {
|
|
29900
|
+
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
29901
|
+
persisted = true;
|
|
29902
|
+
} catch {
|
|
29903
|
+
}
|
|
29904
|
+
if (isSafe) safe.push(rule);
|
|
29905
|
+
else warnQuarantined(rule, worstMs, persisted);
|
|
29906
|
+
}
|
|
29907
|
+
} finally {
|
|
29908
|
+
for (const [reason, count] of unmeasurable) warnUnmeasurable(reason, count);
|
|
29909
|
+
}
|
|
29910
|
+
return safe;
|
|
29911
|
+
}
|
|
29912
|
+
|
|
29913
|
+
// ../../packages/plugin-sdk/src/guarded-scan.ts
|
|
29914
|
+
var DEFAULT_DEGRADE_SCOPE = "the rest of this process";
|
|
29915
|
+
function warnDegraded(scope, dropped, detail) {
|
|
29916
|
+
process.stderr.write(
|
|
29917
|
+
`[aka] isolated scanning is off for ${scope}: ${detail}. ${String(dropped)} pulled/custom-pack rule(s) are excluded; the built-in packs still run.
|
|
29918
|
+
`
|
|
29919
|
+
);
|
|
29920
|
+
}
|
|
29921
|
+
function createGuardedScanner(partition, gateway, opts) {
|
|
29922
|
+
const degradeScope = opts?.degradeScope ?? DEFAULT_DEGRADE_SCOPE;
|
|
29923
|
+
const verified = partition.verified;
|
|
29924
|
+
let unverified = partition.unverified;
|
|
29925
|
+
let isolated = unverified.length > 0 ? createIsolatedScanner({ verified, unverified }, opts) : void 0;
|
|
29926
|
+
let retired = false;
|
|
29927
|
+
function inProcess(text, context) {
|
|
29928
|
+
return scan(text, verified, context);
|
|
29929
|
+
}
|
|
29930
|
+
async function retire() {
|
|
29931
|
+
const live = isolated;
|
|
29932
|
+
isolated = void 0;
|
|
29933
|
+
unverified = [];
|
|
29934
|
+
if (live) await live.close();
|
|
29935
|
+
}
|
|
29936
|
+
async function degrade() {
|
|
29937
|
+
retired = true;
|
|
29938
|
+
await retire();
|
|
29939
|
+
}
|
|
29940
|
+
async function attempt(active, text, context, attribute) {
|
|
29941
|
+
try {
|
|
29942
|
+
return await active.scan(text, context, { attribute });
|
|
29943
|
+
} catch (error51) {
|
|
29944
|
+
return {
|
|
29945
|
+
status: "unavailable",
|
|
29946
|
+
reason: error51 instanceof Error ? error51.message : "the scan worker failed unexpectedly"
|
|
29947
|
+
};
|
|
29948
|
+
}
|
|
29949
|
+
}
|
|
29950
|
+
async function guardedScan(text, context) {
|
|
29951
|
+
const active = isolated;
|
|
29952
|
+
if (!active) return inProcess(text, context);
|
|
29953
|
+
let outcome = await attempt(active, text, context, false);
|
|
29954
|
+
if (outcome.status === "ok") return outcome.findings;
|
|
29955
|
+
if (outcome.status === "timeout") outcome = await attempt(active, text, context, true);
|
|
29956
|
+
const dropped = unverified.length;
|
|
29957
|
+
if (outcome.status === "ok") {
|
|
29958
|
+
warnDegraded(
|
|
29959
|
+
degradeScope,
|
|
29960
|
+
dropped,
|
|
29961
|
+
"a scan overran its bound once and no rule could be held responsible"
|
|
29962
|
+
);
|
|
29963
|
+
const findings = outcome.findings;
|
|
29964
|
+
await degrade();
|
|
29965
|
+
return findings;
|
|
29966
|
+
}
|
|
29967
|
+
if (outcome.status === "timeout") {
|
|
29968
|
+
const culprit = outcome.culpritIndex === void 0 ? void 0 : unverified[outcome.culpritIndex];
|
|
29969
|
+
if (culprit) {
|
|
29970
|
+
await quarantineRule(
|
|
29971
|
+
gateway,
|
|
29972
|
+
culprit,
|
|
29973
|
+
outcome.elapsedMs,
|
|
29974
|
+
`it did not finish within the ${outcome.elapsedMs.toFixed(0)}ms isolated-scan bound and was terminated; excluded from every later scan.`
|
|
29975
|
+
);
|
|
29976
|
+
}
|
|
29977
|
+
warnDegraded(
|
|
29978
|
+
degradeScope,
|
|
29979
|
+
dropped,
|
|
29980
|
+
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`
|
|
29981
|
+
);
|
|
29982
|
+
} else {
|
|
29983
|
+
warnDegraded(degradeScope, dropped, outcome.reason);
|
|
29984
|
+
}
|
|
29985
|
+
await degrade();
|
|
29986
|
+
return inProcess(text, context);
|
|
29987
|
+
}
|
|
29988
|
+
return {
|
|
29989
|
+
scan: guardedScan,
|
|
29990
|
+
degraded: () => retired,
|
|
29991
|
+
async close() {
|
|
29992
|
+
await retire();
|
|
28429
29993
|
}
|
|
28430
29994
|
};
|
|
28431
29995
|
}
|
|
28432
29996
|
|
|
28433
29997
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
28434
|
-
import { arch, hostname as
|
|
29998
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
28435
29999
|
|
|
28436
30000
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
28437
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
28438
|
-
import { join as
|
|
30001
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
30002
|
+
import { join as join11 } from "path";
|
|
28439
30003
|
|
|
28440
30004
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
28441
|
-
import { readdirSync as
|
|
28442
|
-
import { basename as
|
|
30005
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
30006
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
28443
30007
|
function toPosix(path) {
|
|
28444
30008
|
return path.split(sep3).join("/");
|
|
28445
30009
|
}
|
|
@@ -28449,7 +30013,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
28449
30013
|
let root = null;
|
|
28450
30014
|
for (let level = 0; level < MAX_PROJECT_ROOT_LEVELS; level += 1) {
|
|
28451
30015
|
if (directoryHasMarker(dir, recognizeMarker)) root = dir;
|
|
28452
|
-
const parent =
|
|
30016
|
+
const parent = dirname3(dir);
|
|
28453
30017
|
if (parent === dir) break;
|
|
28454
30018
|
dir = parent;
|
|
28455
30019
|
}
|
|
@@ -28457,7 +30021,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
28457
30021
|
}
|
|
28458
30022
|
function directoryHasMarker(dir, recognizeMarker) {
|
|
28459
30023
|
try {
|
|
28460
|
-
for (const entry of
|
|
30024
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
28461
30025
|
if (entry.isFile() && recognizeMarker(entry.name) != null) return true;
|
|
28462
30026
|
}
|
|
28463
30027
|
} catch {
|
|
@@ -28468,69 +30032,42 @@ function directoryHasMarker(dir, recognizeMarker) {
|
|
|
28468
30032
|
function resolveNonGitProject(startDir, recognizeMarker) {
|
|
28469
30033
|
const projectRoot = findProjectRoot(startDir, recognizeMarker);
|
|
28470
30034
|
const realRoot = realpathSync2(projectRoot);
|
|
28471
|
-
return { root: projectRoot, projectKey: `path:${realRoot}`, project:
|
|
30035
|
+
return { root: projectRoot, projectKey: `path:${realRoot}`, project: basename4(realRoot) };
|
|
28472
30036
|
}
|
|
28473
30037
|
|
|
28474
30038
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
28475
30039
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
28476
|
-
import { existsSync as
|
|
28477
|
-
import { basename as
|
|
30040
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
30041
|
+
import { basename as basename5, join as join12 } from "path";
|
|
28478
30042
|
|
|
28479
|
-
// ../../packages/plugin-sdk/src/
|
|
28480
|
-
var
|
|
28481
|
-
|
|
28482
|
-
|
|
28483
|
-
|
|
28484
|
-
|
|
28485
|
-
|
|
28486
|
-
const
|
|
28487
|
-
|
|
28488
|
-
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
28492
|
-
|
|
28493
|
-
|
|
28494
|
-
|
|
28495
|
-
|
|
28496
|
-
|
|
28497
|
-
|
|
28498
|
-
|
|
28499
|
-
|
|
28500
|
-
|
|
28501
|
-
|
|
28502
|
-
|
|
28503
|
-
|
|
28504
|
-
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
28505
|
-
} catch {
|
|
28506
|
-
cached2 = void 0;
|
|
28507
|
-
}
|
|
28508
|
-
if (cached2) {
|
|
28509
|
-
if (cached2.verdict === "safe") safe.push(rule);
|
|
28510
|
-
else warnQuarantined(rule, cached2.worstProbeMs);
|
|
28511
|
-
continue;
|
|
28512
|
-
}
|
|
28513
|
-
if (performance.now() - passStart >= passBudgetMs) {
|
|
28514
|
-
warnQuarantined(rule, void 0);
|
|
28515
|
-
continue;
|
|
28516
|
-
}
|
|
28517
|
-
let isSafe;
|
|
28518
|
-
let worstMs;
|
|
28519
|
-
try {
|
|
28520
|
-
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
28521
|
-
} catch {
|
|
28522
|
-
isSafe = false;
|
|
28523
|
-
worstMs = Number.POSITIVE_INFINITY;
|
|
28524
|
-
}
|
|
28525
|
-
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
28526
|
-
if (isSafe) safe.push(rule);
|
|
28527
|
-
else warnQuarantined(rule, worstMs);
|
|
28528
|
-
}
|
|
28529
|
-
return safe;
|
|
28530
|
-
}
|
|
30043
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30044
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
30045
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30046
|
+
return v;
|
|
30047
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30048
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
30049
|
+
if (typeof v !== "string") return false;
|
|
30050
|
+
const normalized = v.trim().toLowerCase();
|
|
30051
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
30052
|
+
}, external_exports.boolean()).catch(false);
|
|
30053
|
+
var antigravityProviderEnvShape = {
|
|
30054
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
30055
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
30056
|
+
};
|
|
30057
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
30058
|
+
|
|
30059
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
30060
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
30061
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30062
|
+
return v;
|
|
30063
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30064
|
+
var codexProviderEnvShape = {
|
|
30065
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
30066
|
+
};
|
|
30067
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
28531
30068
|
|
|
28532
30069
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28533
|
-
import { randomUUID as
|
|
30070
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28534
30071
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
28535
30072
|
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
28536
30073
|
function entryIsActive(entry, now) {
|
|
@@ -28555,6 +30092,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28555
30092
|
const dataDir2 = opts?.dataDir;
|
|
28556
30093
|
let policies = [];
|
|
28557
30094
|
let rules = [];
|
|
30095
|
+
let scanner;
|
|
28558
30096
|
let bundleExceptions = [];
|
|
28559
30097
|
let initialized = false;
|
|
28560
30098
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
@@ -28580,8 +30118,24 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28580
30118
|
return key !== void 0 && bundledProbeKeys.has(key);
|
|
28581
30119
|
});
|
|
28582
30120
|
const needsGate = incoming.filter((rule) => !ciVerified.includes(rule));
|
|
28583
|
-
|
|
28584
|
-
|
|
30121
|
+
let prober;
|
|
30122
|
+
const gated = await filterUnsafeRules(needsGate, gateway, {
|
|
30123
|
+
prober: {
|
|
30124
|
+
probe: (rule) => {
|
|
30125
|
+
prober ??= createIsolatedScanner({ verified: [], unverified: [] }, opts?.scanIsolation);
|
|
30126
|
+
return prober.probe(rule);
|
|
30127
|
+
}
|
|
30128
|
+
}
|
|
30129
|
+
});
|
|
30130
|
+
await prober?.close();
|
|
30131
|
+
const verified = bundle.rulesComplete ? [...ciVerified] : [...getLoadedRules(), ...ciVerified];
|
|
30132
|
+
const unverified = [];
|
|
30133
|
+
for (const rule of gated) {
|
|
30134
|
+
if (rule.matcher.type === "regex") unverified.push(rule);
|
|
30135
|
+
else verified.push(rule);
|
|
30136
|
+
}
|
|
30137
|
+
rules = [...verified, ...unverified];
|
|
30138
|
+
scanner = createGuardedScanner({ verified, unverified }, gateway, opts?.scanIsolation);
|
|
28585
30139
|
bundleExceptions = bundle.exceptions ?? [];
|
|
28586
30140
|
initialized = true;
|
|
28587
30141
|
}
|
|
@@ -28721,7 +30275,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28721
30275
|
const pair = `${finding.ruleId}:${fp}`;
|
|
28722
30276
|
if (seen.has(pair)) continue;
|
|
28723
30277
|
seen.add(pair);
|
|
28724
|
-
const reference =
|
|
30278
|
+
const reference = randomUUID14().replaceAll("-", "").slice(0, 6);
|
|
28725
30279
|
const maskedValue = maskMatch(finding.rawMatch);
|
|
28726
30280
|
try {
|
|
28727
30281
|
await gateway.recordBlockedDetection({
|
|
@@ -28745,8 +30299,10 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28745
30299
|
async function evaluate2(text, context, ctx) {
|
|
28746
30300
|
try {
|
|
28747
30301
|
await ensureInitialized();
|
|
30302
|
+
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
28748
30303
|
const shielded = shieldPointers(text);
|
|
28749
|
-
const
|
|
30304
|
+
const matched = await scanner.scan(shielded.text, context);
|
|
30305
|
+
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
28750
30306
|
const fpCache = /* @__PURE__ */ new Map();
|
|
28751
30307
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
28752
30308
|
const decision = decide(findings, text, excepted);
|
|
@@ -28803,7 +30359,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28803
30359
|
valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
|
|
28804
30360
|
}) : void 0;
|
|
28805
30361
|
return {
|
|
28806
|
-
id:
|
|
30362
|
+
id: randomUUID14(),
|
|
28807
30363
|
eventId: event.id,
|
|
28808
30364
|
ruleId: match.ruleId,
|
|
28809
30365
|
category: match.category,
|
|
@@ -28829,25 +30385,32 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28829
30385
|
const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
28830
30386
|
return contentHashOf(JSON.stringify(sorted));
|
|
28831
30387
|
} catch {
|
|
28832
|
-
return `unresolved-${
|
|
30388
|
+
return `unresolved-${randomUUID14()}`;
|
|
28833
30389
|
}
|
|
28834
30390
|
}
|
|
30391
|
+
function scanIsolationDegraded() {
|
|
30392
|
+
return scanner?.degraded() ?? false;
|
|
30393
|
+
}
|
|
28835
30394
|
async function close() {
|
|
30395
|
+
try {
|
|
30396
|
+
await scanner?.close();
|
|
30397
|
+
} catch {
|
|
30398
|
+
}
|
|
28836
30399
|
await gateway.close();
|
|
28837
30400
|
}
|
|
28838
|
-
return { processText, capture, rulesetFingerprint, close };
|
|
30401
|
+
return { processText, capture, rulesetFingerprint, scanIsolationDegraded, close };
|
|
28839
30402
|
}
|
|
28840
30403
|
|
|
28841
30404
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28842
30405
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28843
30406
|
|
|
28844
30407
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28845
|
-
import { mkdirSync as mkdirSync4, statSync as
|
|
28846
|
-
import { join as
|
|
30408
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
30409
|
+
import { join as join13 } from "path";
|
|
28847
30410
|
|
|
28848
30411
|
// ../../packages/scanner/src/discover.ts
|
|
28849
|
-
import { readdirSync as
|
|
28850
|
-
import { join as
|
|
30412
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
30413
|
+
import { join as join14 } from "path";
|
|
28851
30414
|
|
|
28852
30415
|
// ../../packages/scanner/src/constants.ts
|
|
28853
30416
|
var COMMON_SKIP_DIRS = ["node_modules", "__pycache__", ".venv", "venv", ".cache"];
|
|
@@ -28876,7 +30439,7 @@ function discoverGitRepos(opts) {
|
|
|
28876
30439
|
if (depth > maxDepth || excludePaths.has(dir)) return;
|
|
28877
30440
|
let entries;
|
|
28878
30441
|
try {
|
|
28879
|
-
entries =
|
|
30442
|
+
entries = readdirSync5(dir, { withFileTypes: true, encoding: "utf8" });
|
|
28880
30443
|
} catch {
|
|
28881
30444
|
return;
|
|
28882
30445
|
}
|
|
@@ -28892,7 +30455,7 @@ function discoverGitRepos(opts) {
|
|
|
28892
30455
|
if (!entry.isDirectory()) continue;
|
|
28893
30456
|
if (DISCOVER_SKIP.has(entry.name)) continue;
|
|
28894
30457
|
if (entry.name.startsWith(".")) continue;
|
|
28895
|
-
visit(
|
|
30458
|
+
visit(join14(dir, entry.name), depth + 1);
|
|
28896
30459
|
}
|
|
28897
30460
|
}
|
|
28898
30461
|
for (const root of searchRoots) {
|
|
@@ -28902,8 +30465,8 @@ function discoverGitRepos(opts) {
|
|
|
28902
30465
|
}
|
|
28903
30466
|
|
|
28904
30467
|
// ../../packages/scanner/src/render.ts
|
|
28905
|
-
import { basename as
|
|
28906
|
-
var
|
|
30468
|
+
import { basename as basename6, relative } from "path";
|
|
30469
|
+
var SEVERITY_ORDER3 = ["critical", "high", "medium", "low"];
|
|
28907
30470
|
var SEVERITY_GLYPH = {
|
|
28908
30471
|
critical: "\u2588",
|
|
28909
30472
|
high: "\u2593",
|
|
@@ -28920,9 +30483,9 @@ function indent(text, spaces = 2) {
|
|
|
28920
30483
|
}
|
|
28921
30484
|
function table(headers, rows, gap = 3) {
|
|
28922
30485
|
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
28923
|
-
const
|
|
28924
|
-
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(
|
|
28925
|
-
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(
|
|
30486
|
+
const sep5 = " ".repeat(gap);
|
|
30487
|
+
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
|
|
30488
|
+
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep5);
|
|
28926
30489
|
return [fmt(headers.map((h) => h.toUpperCase())), ruleLine, ...rows.map(fmt)].join("\n");
|
|
28927
30490
|
}
|
|
28928
30491
|
function metaBlock(rows) {
|
|
@@ -28934,7 +30497,7 @@ function findingsLabel(total, gitignored) {
|
|
|
28934
30497
|
return `${String(total)} (${String(gitignored)} in .gitignore'd files \u2014 informational)`;
|
|
28935
30498
|
}
|
|
28936
30499
|
function severitySection(bySeverity) {
|
|
28937
|
-
const rows =
|
|
30500
|
+
const rows = SEVERITY_ORDER3.filter((s) => (bySeverity[s] ?? 0) > 0).map((s) => [
|
|
28938
30501
|
`${SEVERITY_GLYPH[s] ?? ""} ${s}`,
|
|
28939
30502
|
String(bySeverity[s])
|
|
28940
30503
|
]);
|
|
@@ -28944,7 +30507,7 @@ function followUpSection(opts) {
|
|
|
28944
30507
|
return opts.followUp !== void 0 ? ["", ` ${opts.followUp}`] : [];
|
|
28945
30508
|
}
|
|
28946
30509
|
function renderWorktreeSummary(summary, opts = {}) {
|
|
28947
|
-
const rootLabel =
|
|
30510
|
+
const rootLabel = relative(process.cwd(), summary.rootDir) || ".";
|
|
28948
30511
|
const meta3 = metaBlock([
|
|
28949
30512
|
["Root", rootLabel],
|
|
28950
30513
|
["Scanned", `${String(summary.scanned)} files`],
|
|
@@ -28982,7 +30545,7 @@ function renderMultiRepoSummary(summary, opts = {}) {
|
|
|
28982
30545
|
"\n"
|
|
28983
30546
|
);
|
|
28984
30547
|
}
|
|
28985
|
-
const repoRows = summary.repos.filter((r) => r.summary.scanned > 0 || r.summary.findings > 0).map((r) => [
|
|
30548
|
+
const repoRows = summary.repos.filter((r) => r.summary.scanned > 0 || r.summary.findings > 0).map((r) => [basename6(r.rootDir), String(r.summary.scanned), String(r.summary.findings)]);
|
|
28986
30549
|
const repoSection = repoRows.length > 0 ? ["", indent(table(["REPO", "SCANNED", "FINDINGS"], repoRows))].join("\n") : "";
|
|
28987
30550
|
return [
|
|
28988
30551
|
"\u2713 Multi-repo scan complete",
|
|
@@ -28995,11 +30558,11 @@ function renderMultiRepoSummary(summary, opts = {}) {
|
|
|
28995
30558
|
}
|
|
28996
30559
|
|
|
28997
30560
|
// ../../packages/scanner/src/scan.ts
|
|
28998
|
-
import { existsSync as
|
|
28999
|
-
import { extname as extname2, isAbsolute as isAbsolute2, relative as
|
|
30561
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
30562
|
+
import { extname as extname2, isAbsolute as isAbsolute2, relative as relative3 } from "path";
|
|
29000
30563
|
|
|
29001
30564
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
29002
|
-
import { randomUUID as
|
|
30565
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
29003
30566
|
|
|
29004
30567
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
29005
30568
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -29161,7 +30724,7 @@ var StandaloneDataGateway = class {
|
|
|
29161
30724
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
29162
30725
|
const installed = this.installedScanRules();
|
|
29163
30726
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
29164
|
-
id:
|
|
30727
|
+
id: randomUUID15(),
|
|
29165
30728
|
scope: "global",
|
|
29166
30729
|
target: { ruleId },
|
|
29167
30730
|
action,
|
|
@@ -29314,16 +30877,16 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
29314
30877
|
}
|
|
29315
30878
|
|
|
29316
30879
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
29317
|
-
import { randomUUID as
|
|
30880
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
29318
30881
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
29319
30882
|
|
|
29320
30883
|
// ../../packages/scanner/src/manifests.ts
|
|
29321
|
-
import { statSync as
|
|
30884
|
+
import { statSync as statSync8 } from "fs";
|
|
29322
30885
|
|
|
29323
30886
|
// ../../packages/scanner/src/walk.ts
|
|
29324
30887
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
29325
|
-
import { readdirSync as
|
|
29326
|
-
import { extname, join as
|
|
30888
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
|
|
30889
|
+
import { extname, join as join15, relative as relative2, sep as sep4 } from "path";
|
|
29327
30890
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
29328
30891
|
".ts",
|
|
29329
30892
|
".tsx",
|
|
@@ -29355,7 +30918,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
29355
30918
|
var DEFAULT_MAX_BYTES = 512 * 1024;
|
|
29356
30919
|
function readIgnoreLayer(dir, filename) {
|
|
29357
30920
|
try {
|
|
29358
|
-
const content =
|
|
30921
|
+
const content = readFileSync9(join15(dir, filename), "utf8");
|
|
29359
30922
|
return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
|
|
29360
30923
|
} catch {
|
|
29361
30924
|
return void 0;
|
|
@@ -29364,7 +30927,7 @@ function readIgnoreLayer(dir, filename) {
|
|
|
29364
30927
|
function evaluate(layers, absPath, isDir) {
|
|
29365
30928
|
let state = "unmatched";
|
|
29366
30929
|
for (const layer of layers) {
|
|
29367
|
-
const rel =
|
|
30930
|
+
const rel = relative2(layer.base, absPath).split(sep4).join("/") + (isDir ? "/" : "");
|
|
29368
30931
|
const verdict = layer.matcher.test(rel);
|
|
29369
30932
|
if (verdict.ignored) state = "ignored";
|
|
29370
30933
|
else if (verdict.unignored) state = "unignored";
|
|
@@ -29377,7 +30940,7 @@ function* walkTree(rootDir, opts = {}) {
|
|
|
29377
30940
|
function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
29378
30941
|
let dirents;
|
|
29379
30942
|
try {
|
|
29380
|
-
dirents =
|
|
30943
|
+
dirents = readdirSync6(dir, { withFileTypes: true, encoding: "utf8" });
|
|
29381
30944
|
} catch {
|
|
29382
30945
|
return;
|
|
29383
30946
|
}
|
|
@@ -29387,7 +30950,7 @@ function* walkTree(rootDir, opts = {}) {
|
|
|
29387
30950
|
const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
|
|
29388
30951
|
for (const entry of dirents) {
|
|
29389
30952
|
const name = entry.name;
|
|
29390
|
-
const fullPath =
|
|
30953
|
+
const fullPath = join15(dir, name);
|
|
29391
30954
|
if (entry.isDirectory()) {
|
|
29392
30955
|
const skipState = evaluate(dirSkipLayers, fullPath, true);
|
|
29393
30956
|
if (skipState !== "unignored" && (SKIP_DIRS.has(name) || skipState === "ignored")) {
|
|
@@ -29421,7 +30984,7 @@ function* walkSourceFiles(opts = {}) {
|
|
|
29421
30984
|
let size;
|
|
29422
30985
|
let mtime;
|
|
29423
30986
|
try {
|
|
29424
|
-
const st =
|
|
30987
|
+
const st = statSync7(file2.path);
|
|
29425
30988
|
size = st.size;
|
|
29426
30989
|
mtime = st.mtime;
|
|
29427
30990
|
} catch {
|
|
@@ -29433,7 +30996,7 @@ function* walkSourceFiles(opts = {}) {
|
|
|
29433
30996
|
// Posix-separated like every stored relative path (and the ignore
|
|
29434
30997
|
// matching inside walkTree) — native separators must not leak into the
|
|
29435
30998
|
// contract.
|
|
29436
|
-
relativePath:
|
|
30999
|
+
relativePath: relative2(rootDir, file2.path).split(sep4).join("/"),
|
|
29437
31000
|
mtime: mtime.toISOString(),
|
|
29438
31001
|
size,
|
|
29439
31002
|
gitignored: file2.gitignored
|
|
@@ -29441,7 +31004,7 @@ function* walkSourceFiles(opts = {}) {
|
|
|
29441
31004
|
if (opts.shouldRead && !opts.shouldRead(meta3)) continue;
|
|
29442
31005
|
let content;
|
|
29443
31006
|
try {
|
|
29444
|
-
content =
|
|
31007
|
+
content = readFileSync9(file2.path, "utf8");
|
|
29445
31008
|
} catch {
|
|
29446
31009
|
continue;
|
|
29447
31010
|
}
|
|
@@ -29463,7 +31026,7 @@ function collectManifests(rootDir, maxFileSizeBytes = MAX_MANIFEST_BYTES) {
|
|
|
29463
31026
|
const kind = manifestKindOf(file2.name);
|
|
29464
31027
|
if (kind === null) continue;
|
|
29465
31028
|
try {
|
|
29466
|
-
const st =
|
|
31029
|
+
const st = statSync8(file2.path);
|
|
29467
31030
|
if (st.size > maxFileSizeBytes) continue;
|
|
29468
31031
|
found.push({ path: file2.path, kind, mtime: st.mtime.toISOString(), size: st.size });
|
|
29469
31032
|
} catch {
|
|
@@ -29501,7 +31064,7 @@ function resolveEgressProject(rootDir) {
|
|
|
29501
31064
|
}
|
|
29502
31065
|
}
|
|
29503
31066
|
function egressKey(root, absPath) {
|
|
29504
|
-
const rel = toPosix(
|
|
31067
|
+
const rel = toPosix(relative3(root, absPath));
|
|
29505
31068
|
return rel === "" || rel.startsWith("../") ? null : rel;
|
|
29506
31069
|
}
|
|
29507
31070
|
function startEgress(rootDir) {
|
|
@@ -29555,13 +31118,13 @@ async function reopenRedetectedFindings(gateway, path, currentKeys, evidence, re
|
|
|
29555
31118
|
}
|
|
29556
31119
|
}
|
|
29557
31120
|
function isUnderRoot(path, rootDir) {
|
|
29558
|
-
const rel =
|
|
31121
|
+
const rel = relative3(rootDir, path);
|
|
29559
31122
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
29560
31123
|
}
|
|
29561
31124
|
async function sweepDeletedFiles(gateway, rootDir, previous) {
|
|
29562
31125
|
const deleted = [];
|
|
29563
31126
|
for (const path of previous.keys()) {
|
|
29564
|
-
if (!isUnderRoot(path, rootDir) ||
|
|
31127
|
+
if (!isUnderRoot(path, rootDir) || existsSync9(path)) continue;
|
|
29565
31128
|
deleted.push(path);
|
|
29566
31129
|
await resolveRemovedFindings(gateway, path, [], { deleted: true });
|
|
29567
31130
|
}
|
|
@@ -29656,6 +31219,9 @@ async function scanDir(runtime, gateway, config2, seen, ledger, rootDir, opts) {
|
|
|
29656
31219
|
if (committed === null) {
|
|
29657
31220
|
return { rootDir, scanned, skipped, findings, gitignoredFindings, byRule, bySeverity };
|
|
29658
31221
|
}
|
|
31222
|
+
if (runtime.scanIsolationDegraded()) {
|
|
31223
|
+
return { rootDir, scanned, skipped, findings, gitignoredFindings, byRule, bySeverity };
|
|
31224
|
+
}
|
|
29659
31225
|
await gateway.recordScanned(ledgerable(updates, egress, committed));
|
|
29660
31226
|
return { rootDir, scanned, skipped, findings, gitignoredFindings, byRule, bySeverity };
|
|
29661
31227
|
}
|
|
@@ -29665,7 +31231,7 @@ function scanManifests(egress, ledger, updates, rootDir) {
|
|
|
29665
31231
|
if (prev?.mtime === manifest.mtime) continue;
|
|
29666
31232
|
let content;
|
|
29667
31233
|
try {
|
|
29668
|
-
content =
|
|
31234
|
+
content = readFileSync10(manifest.path, "utf8");
|
|
29669
31235
|
} catch {
|
|
29670
31236
|
continue;
|
|
29671
31237
|
}
|