@akasecurity/ai-tc-claude-code 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/commands/setup.md +123 -35
- package/hooks/hooks.json +11 -0
- package/package.json +7 -6
- package/scripts/apply-suppressions.js +2178 -593
- package/scripts/backfill.js +3815 -429
- package/scripts/filescan.js +2379 -336
- package/scripts/firstrun.js +1748 -181
- package/scripts/intro.js +520 -48
- package/scripts/message-display.js +30054 -0
- package/scripts/onboard.js +2007 -208
- package/scripts/post-tool-use.js +3721 -388
- package/scripts/pre-tool-use.js +3900 -414
- package/scripts/query.js +1752 -181
- package/scripts/reconcile.js +3321 -372
- package/scripts/remediate.js +3925 -582
- package/scripts/scan-worker.js +18006 -0
- package/scripts/session-start.js +1879 -273
- package/scripts/start-light.js +540 -68
- package/scripts/statusline.js +1747 -180
- package/scripts/stop.js +504 -75
- package/scripts/user-prompt-submit.js +3695 -408
|
@@ -492,15 +492,14 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/apply-suppressions.ts
|
|
495
|
-
import { existsSync as
|
|
495
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
|
|
496
496
|
import { userInfo } from "os";
|
|
497
|
-
import { dirname as
|
|
498
|
-
import { fileURLToPath as
|
|
497
|
+
import { dirname as dirname6, join as join17 } from "path";
|
|
498
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
499
499
|
|
|
500
500
|
// ../../packages/persistence/src/database.ts
|
|
501
|
-
import { randomUUID as
|
|
502
|
-
import {
|
|
503
|
-
import { join, sep } from "path";
|
|
501
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
502
|
+
import { join as join2, sep } from "path";
|
|
504
503
|
import { DatabaseSync } from "node:sqlite";
|
|
505
504
|
|
|
506
505
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -564,6 +563,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
564
563
|
{
|
|
565
564
|
tag: "0014_drop_legacy_events_findings",
|
|
566
565
|
sql: "-- Custom migration: drops the frozen legacy `events`/`findings` tables\n-- (superseded by audit_events/inspection_definitions/inspection_findings) and\n-- replaces them with read-only views of the same name.\n--\n-- persistence's migrations.ts applies this migration ONLY once the batched\n-- history backfill (see runLegacyHistoryBackfill) has fully drained both\n-- tables, and only after copying the live file aside \u2014 see\n-- backupBeforeLegacyDrop in packages/persistence/src/migrations.ts. A store\n-- still mid-copy keeps its real tables and this migration stays pending.\n--\n-- The views exist for skew: the `aka` CLI and the Claude Code plugin update\n-- independently against one shared store, so an older, already-installed\n-- binary can open a store a newer binary already dropped these tables on.\n-- Every already-shipped repository constructor prepares its SQL eagerly at\n-- open time, so a bare \"table not found\" would fail the WHOLE open, not just\n-- a findings-specific read \u2014 these views keep `prepare()` succeeding so an\n-- old binary's unrelated features keep working; its own reads stay truthful,\n-- and its rare (already fail-open) writes fail at run time instead.\n--\n-- The 0009/0010 expression indexes existed only over the legacy `events`\n-- table; DROP TABLE below would remove them implicitly, but they are\n-- dropped explicitly first so the intent reads clearly. IF EXISTS so a store\n-- that reached here with an index missing out of band (an adopted-tag store\n-- whose physical index was never built) does not throw a deterministic \"no\n-- such index\" out of the fail-open drop path.\nDROP INDEX IF EXISTS `idx_events_code_change_path`;\n--> statement-breakpoint\nDROP INDEX IF EXISTS `idx_events_session_id`;\n--> statement-breakpoint\n-- `findings.event_id` references `events.id` \u2014 drop the child first.\nDROP TABLE `findings`;\n--> statement-breakpoint\nDROP TABLE `events`;\n--> statement-breakpoint\n-- Legacy `events` shape, projected from audit_events: `kind`/`occurred_at`/\n-- `source_tool` become real columns again (source_tool round-trips through\n-- the attributes bag, the only place a capture-typed audit row keeps it);\n-- `metadata` is reconstructed as the old camelCase JSON object from the new\n-- snake_case attributes bag, with `root_session_id` folded back in as\n-- `sessionId` (the one legacy metadata key that became a column, never an\n-- attribute, on the new table). Constrained to the four capture kinds so\n-- structural rows (session/run/tool_call/llm_call/source_lookup/config_scan)\n-- never leak into a legacy reader's result set \u2014 the old `events` table\n-- never held them either.\nCREATE VIEW `events` AS\nSELECT\n id,\n json_extract(attributes, '$.source_tool') AS source_tool,\n event_type AS kind,\n started_at AS occurred_at,\n content_hash,\n content,\n -- Plugin-local bookkeeping column that `ensureSyncedAtColumn` adds to the\n -- real `events` table at open time. A pre-cutover binary runs that probe on\n -- EVERY open; without this projection its `columnNames('events')` check\n -- misses `synced_at` and issues `ALTER TABLE events ADD COLUMN` against this\n -- view, which SQLite rejects (\"Cannot add a column to a view\") \u2014 a hard,\n -- non-fail-open crash of the whole open, the exact skew failure these views\n -- exist to prevent. Projecting it (always NULL; no reader consumes it)\n -- short-circuits that ALTER.\n NULL AS synced_at,\n json_object(\n 'sessionId', root_session_id,\n 'repo', json_extract(attributes, '$.repo'),\n 'filePath', json_extract(attributes, '$.file_path'),\n 'toolName', json_extract(attributes, '$.tool_name'),\n 'gitignored', json_extract(attributes, '$.gitignored'),\n 'wholeFile', json_extract(attributes, '$.whole_file'),\n 'model', json_extract(attributes, '$.model'),\n 'turnIndex', json_extract(attributes, '$.turn_index'),\n 'correlationId', json_extract(attributes, '$.correlation_id'),\n 'traceId', json_extract(attributes, '$.trace_id'),\n 'exceptionIds', json_extract(attributes, '$.exception_ids')\n ) AS metadata\nFROM audit_events\nWHERE event_type IN ('prompt', 'response', 'code_change', 'tool_use');\n--> statement-breakpoint\n-- A plain single-row INSERT (the old writer's shape \u2014 no ON CONFLICT) is\n-- accepted by prepare() against a view once an INSTEAD OF INSERT trigger\n-- exists, so it fails at run time instead of at open \u2014 landing in the same\n-- fail-open path the caller already wraps its write in.\nCREATE TRIGGER `trg_events_ro`\nINSTEAD OF INSERT ON `events`\nBEGIN SELECT RAISE(ABORT, 'events is read-only; write to audit_events instead'); END;\n--> statement-breakpoint\n-- Legacy `findings` shape: rule_id/category/severity come from the joined\n-- inspection_definitions row (the legacy table inlined them per-row; the\n-- generalized schema normalizes them out into the shared definition). A view\n-- has no rowid of its own, so the underlying table's rowid is re-exposed\n-- explicitly under that name \u2014 a legacy reader ordering by `f.rowid` would\n-- otherwise fail to resolve the column at all. Joined to audit_events for the\n-- same four-capture-kind constraint as the events view above (a finding\n-- attached to a tool_call/config_scan row never existed in the legacy table\n-- either \u2014 see the persistence findings repository's identical predicate).\nCREATE VIEW `findings` AS\nSELECT\n f.id AS id,\n f.rowid AS rowid,\n f.audit_event_id AS event_id,\n d.rule_id AS rule_id,\n d.category AS category,\n d.severity AS severity,\n f.span_start AS span_start,\n f.span_end AS span_end,\n f.masked_match AS masked_match,\n f.action_taken AS action_taken,\n f.confidence AS confidence,\n f.finding_key AS finding_key,\n f.first_detected_at AS first_detected_at\nFROM inspection_findings f\nJOIN audit_events e ON e.id = f.audit_event_id\nJOIN inspection_definitions d ON d.id = f.inspection_definition_id\nWHERE e.event_type IN ('prompt', 'response', 'code_change', 'tool_use');\n--> statement-breakpoint\n-- Defense in depth only: SQLite refuses to plan ANY upsert against a view\n-- (\"cannot UPSERT a view\") no matter what trigger exists, so this can never\n-- rescue the real legacy writer, which always used\n-- `ON CONFLICT (finding_key) DO UPDATE` \u2014 that statement still fails at\n-- prepare() on an old binary, same as it would with no trigger at all. This\n-- only covers a plain-INSERT shape, should one ever exist.\nCREATE TRIGGER `trg_findings_ro`\nINSTEAD OF INSERT ON `findings`\nBEGIN SELECT RAISE(ABORT, 'findings is read-only; write to inspection_findings instead'); END;\n"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
tag: "0015_busy_vengeance",
|
|
569
|
+
sql: "CREATE TABLE `secret_vault` (\n `pointer_id` text PRIMARY KEY NOT NULL,\n `value_fingerprint` text NOT NULL,\n `fingerprint_key_version` integer NOT NULL,\n `key_version` integer NOT NULL,\n `category` text NOT NULL,\n `rule_id` text NOT NULL,\n `masked_match` text NOT NULL,\n `provider` text,\n `ciphertext` text NOT NULL,\n `nonce` text NOT NULL,\n `auth_tag` text NOT NULL,\n `occurrence_count` integer DEFAULT 1 NOT NULL,\n `first_seen` integer NOT NULL,\n `last_seen` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_secret_vault_value` ON `secret_vault` (`value_fingerprint`);--> statement-breakpoint\nCREATE TABLE `secret_vault_deref` (\n `id` text PRIMARY KEY NOT NULL,\n `pointer_id` text NOT NULL,\n `at` integer NOT NULL,\n `target` text NOT NULL,\n `reason` text NOT NULL,\n `outcome` text NOT NULL,\n `grant_id` text,\n `pointer_count` integer DEFAULT 1 NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_pointer` ON `secret_vault_deref` (`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_reason_at` ON `secret_vault_deref` (`reason`,`at`);"
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
tag: "0016_breezy_zodiak",
|
|
573
|
+
sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
tag: "0017_rainy_kat_farrell",
|
|
577
|
+
sql: "CREATE TABLE `secret_vault_sighting` (\n `id` text PRIMARY KEY NOT NULL,\n `pointer_id` text NOT NULL,\n `location` text NOT NULL,\n `kind` text NOT NULL,\n `first_seen` integer NOT NULL,\n `last_seen` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_secret_vault_sighting` ON `secret_vault_sighting` (`pointer_id`,`location`);"
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
tag: "0018_serious_tana_nile",
|
|
581
|
+
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
tag: "0019_audit_started_at_index",
|
|
585
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
589
|
+
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');"
|
|
567
590
|
}
|
|
568
591
|
];
|
|
569
592
|
|
|
@@ -15301,7 +15324,17 @@ var Finding = external_exports.object({
|
|
|
15301
15324
|
}).meta({ id: "Finding" });
|
|
15302
15325
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15303
15326
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15304
|
-
var FindingProvider = external_exports.enum([
|
|
15327
|
+
var FindingProvider = external_exports.enum([
|
|
15328
|
+
"claudecode",
|
|
15329
|
+
"claudedesktop",
|
|
15330
|
+
"cursor",
|
|
15331
|
+
"copilot",
|
|
15332
|
+
"chatgpt",
|
|
15333
|
+
"claudeai",
|
|
15334
|
+
"codex",
|
|
15335
|
+
"antigravity",
|
|
15336
|
+
"api"
|
|
15337
|
+
]).meta({ id: "FindingProvider" });
|
|
15305
15338
|
var FindingCategory = external_exports.enum([
|
|
15306
15339
|
"secret",
|
|
15307
15340
|
"pii",
|
|
@@ -15355,7 +15388,16 @@ var FindingInstance = external_exports.object({
|
|
|
15355
15388
|
confidence: external_exports.number().min(0).max(1),
|
|
15356
15389
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15357
15390
|
// that predate the resolution feature stay valid.
|
|
15358
|
-
status: FindingStatus.optional()
|
|
15391
|
+
status: FindingStatus.optional(),
|
|
15392
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15393
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15394
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15395
|
+
// MOST RECENT detection event, not the first.
|
|
15396
|
+
eventId: external_exports.string().optional(),
|
|
15397
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15398
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15399
|
+
// outside a session.
|
|
15400
|
+
sessionId: external_exports.string().optional()
|
|
15359
15401
|
}).meta({ id: "FindingInstance" });
|
|
15360
15402
|
var FindingGroup = external_exports.object({
|
|
15361
15403
|
id: external_exports.string(),
|
|
@@ -15399,7 +15441,11 @@ var FindingFacets = external_exports.object({
|
|
|
15399
15441
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15400
15442
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15401
15443
|
// counted under no value.
|
|
15402
|
-
status: external_exports.array(FindingFacetItem)
|
|
15444
|
+
status: external_exports.array(FindingFacetItem),
|
|
15445
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15446
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15447
|
+
// because a group spans tools.
|
|
15448
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15403
15449
|
}).meta({ id: "FindingFacets" });
|
|
15404
15450
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15405
15451
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15417,6 +15463,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15417
15463
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15418
15464
|
// session → findings drilldown). Findings without a session never match.
|
|
15419
15465
|
sessionId: external_exports.string().optional(),
|
|
15466
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15467
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15468
|
+
// means all time — this list has no default window.
|
|
15469
|
+
from: external_exports.iso.datetime().optional(),
|
|
15470
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15471
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15472
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15473
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15474
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15475
|
+
includeId: external_exports.string().optional(),
|
|
15420
15476
|
groupBy: external_exports.literal("type").optional(),
|
|
15421
15477
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15422
15478
|
cursor: external_exports.string().optional()
|
|
@@ -15461,15 +15517,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15461
15517
|
detection: FindingDetectionRef,
|
|
15462
15518
|
policy: FindingPolicyRef
|
|
15463
15519
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15520
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15521
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15522
|
+
severity: external_exports.array(Severity).optional(),
|
|
15523
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15524
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15525
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15526
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15527
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15528
|
+
// the grouped query's group-level fold.
|
|
15529
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15530
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15531
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15532
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15533
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15534
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15535
|
+
repo: external_exports.string().optional(),
|
|
15536
|
+
file: external_exports.string().optional(),
|
|
15537
|
+
q: external_exports.string().optional(),
|
|
15538
|
+
sessionId: external_exports.string().optional(),
|
|
15539
|
+
from: external_exports.iso.datetime().optional(),
|
|
15540
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15541
|
+
cursor: external_exports.string().optional()
|
|
15542
|
+
});
|
|
15543
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15544
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15545
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15546
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15547
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15548
|
+
// dimension still excludes its own filter.
|
|
15549
|
+
facets: FindingFacets,
|
|
15550
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15551
|
+
nextCursor: external_exports.string().nullable()
|
|
15552
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15553
|
+
var FindingLocationFile = external_exports.object({
|
|
15554
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15555
|
+
// with no file attribution).
|
|
15556
|
+
file: external_exports.string(),
|
|
15557
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15558
|
+
maxSeverity: Severity,
|
|
15559
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15560
|
+
// Folded from the instances' derived statuses with the same
|
|
15561
|
+
// open-dominates precedence a group uses.
|
|
15562
|
+
status: FindingStatus.optional(),
|
|
15563
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15564
|
+
// chips, and the count is what conveys scale.
|
|
15565
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15566
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15567
|
+
var FindingLocationRepo = external_exports.object({
|
|
15568
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15569
|
+
repo: external_exports.string(),
|
|
15570
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15571
|
+
maxSeverity: Severity,
|
|
15572
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15573
|
+
status: FindingStatus.optional(),
|
|
15574
|
+
files: external_exports.array(FindingLocationFile)
|
|
15575
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15576
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15577
|
+
severity: external_exports.array(Severity).optional(),
|
|
15578
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15579
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15580
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15581
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15582
|
+
// instances that match, and folds its status from those.
|
|
15583
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15584
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15585
|
+
q: external_exports.string().optional(),
|
|
15586
|
+
sessionId: external_exports.string().optional(),
|
|
15587
|
+
from: external_exports.iso.datetime().optional(),
|
|
15588
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15589
|
+
});
|
|
15590
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15591
|
+
totals: external_exports.object({
|
|
15592
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15593
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15594
|
+
files: external_exports.number().int().nonnegative()
|
|
15595
|
+
}),
|
|
15596
|
+
/** Sorted by max severity, then most recent. */
|
|
15597
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15598
|
+
/** Whether `limit` truncated the repo list. */
|
|
15599
|
+
hasMore: external_exports.boolean()
|
|
15600
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15464
15601
|
|
|
15465
15602
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15466
|
-
var Harness = external_exports.enum([
|
|
15603
|
+
var Harness = external_exports.enum([
|
|
15604
|
+
"claudecode",
|
|
15605
|
+
"cursor",
|
|
15606
|
+
"copilot",
|
|
15607
|
+
"codex",
|
|
15608
|
+
"antigravity",
|
|
15609
|
+
"windsurf",
|
|
15610
|
+
"claudedesktop",
|
|
15611
|
+
"chatgpt",
|
|
15612
|
+
"claudeai",
|
|
15613
|
+
"api"
|
|
15614
|
+
]).meta({ id: "Harness" });
|
|
15467
15615
|
var TOOL_TO_HARNESS = {
|
|
15468
15616
|
"claude-code": "claudecode",
|
|
15469
15617
|
"claude-desktop": "claudedesktop",
|
|
15470
15618
|
"github-copilot": "copilot",
|
|
15471
15619
|
cursor: "cursor",
|
|
15472
|
-
chatgpt: "chatgpt"
|
|
15620
|
+
chatgpt: "chatgpt",
|
|
15621
|
+
codex: "codex",
|
|
15622
|
+
antigravity: "antigravity",
|
|
15623
|
+
"claude-ai": "claudeai"
|
|
15473
15624
|
};
|
|
15474
15625
|
|
|
15475
15626
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15927,7 +16078,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15927
16078
|
// ../../packages/schema/src/zod/event.ts
|
|
15928
16079
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15929
16080
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15930
|
-
var SourceTool = external_exports.enum([
|
|
16081
|
+
var SourceTool = external_exports.enum([
|
|
16082
|
+
"claude-code",
|
|
16083
|
+
"claude-desktop",
|
|
16084
|
+
"cursor",
|
|
16085
|
+
"chatgpt",
|
|
16086
|
+
"claude-ai",
|
|
16087
|
+
"github-copilot",
|
|
16088
|
+
"codex",
|
|
16089
|
+
"antigravity",
|
|
16090
|
+
"cli",
|
|
16091
|
+
"unknown"
|
|
16092
|
+
]).meta({ id: "SourceTool" });
|
|
15931
16093
|
var EventMetadata = external_exports.object({
|
|
15932
16094
|
sessionId: external_exports.string().optional(),
|
|
15933
16095
|
repo: external_exports.string().optional(),
|
|
@@ -15998,7 +16160,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
15998
16160
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
15999
16161
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16000
16162
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16001
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16163
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16002
16164
|
var AccessCounts = external_exports.object({
|
|
16003
16165
|
open: external_exports.number().int().nonnegative(),
|
|
16004
16166
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16220,6 +16382,7 @@ var ExceptionConditions = external_exports.object({
|
|
|
16220
16382
|
sourceTool: external_exports.string().optional(),
|
|
16221
16383
|
provider: external_exports.string().optional()
|
|
16222
16384
|
}).strict();
|
|
16385
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16223
16386
|
var DetectionException = external_exports.object({
|
|
16224
16387
|
id: external_exports.guid(),
|
|
16225
16388
|
ruleId: external_exports.string(),
|
|
@@ -16236,6 +16399,7 @@ var DetectionException = external_exports.object({
|
|
|
16236
16399
|
keyVersion: external_exports.number().int().positive(),
|
|
16237
16400
|
// maskMatch() preview of the approved value — never the raw value.
|
|
16238
16401
|
maskedValue: external_exports.string(),
|
|
16402
|
+
capability: ExceptionCapability.default("suppress"),
|
|
16239
16403
|
scope: ExceptionScope,
|
|
16240
16404
|
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16241
16405
|
maxUses: external_exports.number().int().positive().nullable(),
|
|
@@ -16259,11 +16423,13 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16259
16423
|
ruleId: true,
|
|
16260
16424
|
valueFingerprint: true,
|
|
16261
16425
|
keyVersion: true,
|
|
16426
|
+
capability: true,
|
|
16262
16427
|
expiresAt: true,
|
|
16263
16428
|
maxUses: true,
|
|
16264
16429
|
useCount: true,
|
|
16265
16430
|
conditions: true
|
|
16266
16431
|
});
|
|
16432
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16267
16433
|
|
|
16268
16434
|
// ../../packages/schema/src/zod/rule.ts
|
|
16269
16435
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17079,6 +17245,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17079
17245
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17080
17246
|
}).meta({ id: "EgressWriteSummary" });
|
|
17081
17247
|
|
|
17248
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17249
|
+
var confirmation = external_exports.string().optional();
|
|
17250
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17251
|
+
reference: external_exports.string(),
|
|
17252
|
+
scope: external_exports.string(),
|
|
17253
|
+
reason: external_exports.string(),
|
|
17254
|
+
confirmation
|
|
17255
|
+
});
|
|
17256
|
+
var AddExceptionInput = external_exports.object({
|
|
17257
|
+
ruleId: external_exports.string(),
|
|
17258
|
+
value: external_exports.string(),
|
|
17259
|
+
scope: external_exports.string(),
|
|
17260
|
+
reason: external_exports.string(),
|
|
17261
|
+
confirmation
|
|
17262
|
+
});
|
|
17263
|
+
var GrantRevealInput = external_exports.object({
|
|
17264
|
+
pointer: external_exports.string(),
|
|
17265
|
+
scope: external_exports.string(),
|
|
17266
|
+
justification: external_exports.string(),
|
|
17267
|
+
confirmation
|
|
17268
|
+
});
|
|
17269
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17270
|
+
id: external_exports.string(),
|
|
17271
|
+
reason: external_exports.string()
|
|
17272
|
+
});
|
|
17273
|
+
var RotateKeyInput = external_exports.object({
|
|
17274
|
+
confirmation: external_exports.string()
|
|
17275
|
+
});
|
|
17276
|
+
|
|
17082
17277
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17083
17278
|
function toApiAction(dbVal) {
|
|
17084
17279
|
const map2 = {
|
|
@@ -17134,6 +17329,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17134
17329
|
repo: r.repo,
|
|
17135
17330
|
file: r.file,
|
|
17136
17331
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17332
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17333
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17137
17334
|
action: toApiAction(effectiveDbAction),
|
|
17138
17335
|
detectedAt: r.occurredAt,
|
|
17139
17336
|
confidence: r.confidence,
|
|
@@ -17265,14 +17462,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17265
17462
|
}
|
|
17266
17463
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17267
17464
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17465
|
+
function compareFindingGroupOrder(a, b) {
|
|
17466
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17467
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17468
|
+
const severityDiff = rankA - rankB;
|
|
17469
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17470
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17471
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17472
|
+
return a.id.localeCompare(b.id);
|
|
17473
|
+
}
|
|
17268
17474
|
function sortFindingGroups(groups) {
|
|
17269
|
-
return [...groups].sort(
|
|
17270
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17271
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17272
|
-
const severityDiff = rankA - rankB;
|
|
17273
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17274
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17275
|
-
});
|
|
17475
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17276
17476
|
}
|
|
17277
17477
|
function computeFindingFacets(allGroups, opts) {
|
|
17278
17478
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17328,15 +17528,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17328
17528
|
for (const g of forStatus) {
|
|
17329
17529
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17330
17530
|
}
|
|
17331
|
-
const
|
|
17531
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17532
|
+
return {
|
|
17533
|
+
severity: toItems2(severityMap),
|
|
17534
|
+
provider: toItems2(providerMap),
|
|
17535
|
+
action: toItems2(actionMap),
|
|
17536
|
+
subtype: toItems2(subtypeMap),
|
|
17537
|
+
status: toItems2(statusMap)
|
|
17538
|
+
};
|
|
17539
|
+
}
|
|
17540
|
+
|
|
17541
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17542
|
+
function rowHaystack(row) {
|
|
17543
|
+
return [
|
|
17544
|
+
row.ruleId,
|
|
17545
|
+
row.category,
|
|
17546
|
+
row.maskedMatch,
|
|
17547
|
+
row.repo,
|
|
17548
|
+
row.file,
|
|
17549
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17550
|
+
row.id
|
|
17551
|
+
].join(" ").toLowerCase();
|
|
17552
|
+
}
|
|
17553
|
+
function matchesDimension(row, opts, dimension) {
|
|
17554
|
+
switch (dimension) {
|
|
17555
|
+
case "severity":
|
|
17556
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17557
|
+
case "subtype":
|
|
17558
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17559
|
+
case "providers":
|
|
17560
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17561
|
+
case "actions":
|
|
17562
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17563
|
+
case "statuses":
|
|
17564
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17565
|
+
case "tools":
|
|
17566
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17567
|
+
case "repo":
|
|
17568
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17569
|
+
case "file":
|
|
17570
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17571
|
+
case "q":
|
|
17572
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17573
|
+
}
|
|
17574
|
+
}
|
|
17575
|
+
var DIMENSIONS = [
|
|
17576
|
+
"severity",
|
|
17577
|
+
"subtype",
|
|
17578
|
+
"providers",
|
|
17579
|
+
"actions",
|
|
17580
|
+
"statuses",
|
|
17581
|
+
"tools",
|
|
17582
|
+
"repo",
|
|
17583
|
+
"file",
|
|
17584
|
+
"q"
|
|
17585
|
+
];
|
|
17586
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17587
|
+
for (const dimension of DIMENSIONS) {
|
|
17588
|
+
if (dimension === except) continue;
|
|
17589
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17590
|
+
}
|
|
17591
|
+
return true;
|
|
17592
|
+
}
|
|
17593
|
+
function toItems(counts) {
|
|
17594
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17595
|
+
}
|
|
17596
|
+
function bump(counts, value) {
|
|
17597
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17598
|
+
}
|
|
17599
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17600
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17601
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17602
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17603
|
+
const action = /* @__PURE__ */ new Map();
|
|
17604
|
+
const status = /* @__PURE__ */ new Map();
|
|
17605
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17606
|
+
return {
|
|
17607
|
+
add(row) {
|
|
17608
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17609
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17610
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17611
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17612
|
+
}
|
|
17613
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17614
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17615
|
+
bump(status, row.status);
|
|
17616
|
+
}
|
|
17617
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17618
|
+
bump(tool, row.toolName);
|
|
17619
|
+
}
|
|
17620
|
+
},
|
|
17621
|
+
facets: () => ({
|
|
17622
|
+
severity: toItems(severity),
|
|
17623
|
+
subtype: toItems(subtype),
|
|
17624
|
+
provider: toItems(provider),
|
|
17625
|
+
action: toItems(action),
|
|
17626
|
+
status: toItems(status),
|
|
17627
|
+
tool: toItems(tool)
|
|
17628
|
+
})
|
|
17629
|
+
};
|
|
17630
|
+
}
|
|
17631
|
+
function toInstanceDetail(row) {
|
|
17632
|
+
const category = toApiCategory(row.category);
|
|
17332
17633
|
return {
|
|
17333
|
-
|
|
17334
|
-
provider:
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17634
|
+
id: row.id,
|
|
17635
|
+
provider: toApiProvider(row.sourceTool),
|
|
17636
|
+
repo: row.repo,
|
|
17637
|
+
file: row.file,
|
|
17638
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17639
|
+
eventId: row.eventId,
|
|
17640
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17641
|
+
action: toApiAction(row.actionTaken),
|
|
17642
|
+
detectedAt: row.occurredAt,
|
|
17643
|
+
confidence: row.confidence,
|
|
17644
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17645
|
+
groupId: row.ruleId,
|
|
17646
|
+
category,
|
|
17647
|
+
subtype: row.ruleId,
|
|
17648
|
+
severity: row.severity,
|
|
17649
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17650
|
+
detection: { id: row.ruleId, name: null },
|
|
17651
|
+
policy: { id: `category:${category}`, name: category }
|
|
17652
|
+
};
|
|
17653
|
+
}
|
|
17654
|
+
var SEVERITY_ORDER2 = {
|
|
17655
|
+
critical: 0,
|
|
17656
|
+
high: 1,
|
|
17657
|
+
medium: 2,
|
|
17658
|
+
low: 3
|
|
17659
|
+
};
|
|
17660
|
+
function newLocationAccumulator() {
|
|
17661
|
+
return {
|
|
17662
|
+
instanceCount: 0,
|
|
17663
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17664
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17665
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17666
|
+
maxSeverity: "low",
|
|
17667
|
+
latestDetectedAt: "",
|
|
17668
|
+
statuses: [],
|
|
17669
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17338
17670
|
};
|
|
17339
17671
|
}
|
|
17672
|
+
function addToLocation(acc, row) {
|
|
17673
|
+
acc.instanceCount += 1;
|
|
17674
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17675
|
+
if (rank < acc.maxSeverityRank) {
|
|
17676
|
+
acc.maxSeverityRank = rank;
|
|
17677
|
+
acc.maxSeverity = row.severity;
|
|
17678
|
+
}
|
|
17679
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17680
|
+
acc.statuses.push(row.status);
|
|
17681
|
+
acc.ruleIds.add(row.ruleId);
|
|
17682
|
+
}
|
|
17340
17683
|
|
|
17341
17684
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17342
17685
|
var InstalledPack = external_exports.object({
|
|
@@ -17368,8 +17711,164 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17368
17711
|
message: "At least one field must be provided"
|
|
17369
17712
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17370
17713
|
|
|
17714
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17715
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17716
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17717
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17718
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17719
|
+
);
|
|
17720
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17721
|
+
function pointerTokenScanner() {
|
|
17722
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
17723
|
+
}
|
|
17724
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17725
|
+
var ParsedPointer = external_exports.object({
|
|
17726
|
+
category: DetectionCategory,
|
|
17727
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17728
|
+
pointerId: external_exports.string(),
|
|
17729
|
+
tag: external_exports.string()
|
|
17730
|
+
});
|
|
17731
|
+
var VaultEntry = external_exports.object({
|
|
17732
|
+
pointerId: external_exports.string(),
|
|
17733
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17734
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17735
|
+
// independently of the vault encryption key below.
|
|
17736
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17737
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17738
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17739
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17740
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17741
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17742
|
+
// value always produces exactly one wire token.
|
|
17743
|
+
category: DetectionCategory,
|
|
17744
|
+
ruleId: external_exports.string(),
|
|
17745
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17746
|
+
maskedMatch: external_exports.string(),
|
|
17747
|
+
provider: external_exports.string().optional(),
|
|
17748
|
+
ciphertext: external_exports.string(),
|
|
17749
|
+
nonce: external_exports.string(),
|
|
17750
|
+
authTag: external_exports.string(),
|
|
17751
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17752
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17753
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17754
|
+
firstSeen: external_exports.string(),
|
|
17755
|
+
lastSeen: external_exports.string()
|
|
17756
|
+
});
|
|
17757
|
+
var PointerDescriptor = external_exports.object({
|
|
17758
|
+
category: DetectionCategory,
|
|
17759
|
+
provider: external_exports.string().optional(),
|
|
17760
|
+
maskedMatch: external_exports.string(),
|
|
17761
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17762
|
+
firstSeen: external_exports.string(),
|
|
17763
|
+
lastSeen: external_exports.string()
|
|
17764
|
+
});
|
|
17765
|
+
var PointerIdentity = external_exports.object({
|
|
17766
|
+
ruleId: external_exports.string(),
|
|
17767
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17768
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17769
|
+
});
|
|
17770
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17771
|
+
var VaultDerefReason = external_exports.enum([
|
|
17772
|
+
"display",
|
|
17773
|
+
"explicit-reveal",
|
|
17774
|
+
"view-render",
|
|
17775
|
+
"model-input",
|
|
17776
|
+
"remediation",
|
|
17777
|
+
"purge"
|
|
17778
|
+
]);
|
|
17779
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17780
|
+
var VaultDeref = external_exports.object({
|
|
17781
|
+
id: external_exports.guid(),
|
|
17782
|
+
pointerId: external_exports.string(),
|
|
17783
|
+
at: external_exports.string(),
|
|
17784
|
+
target: DetokenizeTarget,
|
|
17785
|
+
reason: VaultDerefReason,
|
|
17786
|
+
outcome: VaultDerefOutcome,
|
|
17787
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17788
|
+
grantId: external_exports.string().optional(),
|
|
17789
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17790
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17791
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17792
|
+
});
|
|
17793
|
+
var VaultSightingKind = external_exports.enum([
|
|
17794
|
+
"prompt",
|
|
17795
|
+
"tool-input",
|
|
17796
|
+
"tool-output",
|
|
17797
|
+
"file",
|
|
17798
|
+
"transcript"
|
|
17799
|
+
]);
|
|
17800
|
+
var VaultSighting = external_exports.object({
|
|
17801
|
+
location: external_exports.string(),
|
|
17802
|
+
kind: VaultSightingKind,
|
|
17803
|
+
firstSeen: external_exports.string(),
|
|
17804
|
+
lastSeen: external_exports.string()
|
|
17805
|
+
});
|
|
17806
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17807
|
+
pointerId: external_exports.string(),
|
|
17808
|
+
category: DetectionCategory,
|
|
17809
|
+
provider: external_exports.string().optional(),
|
|
17810
|
+
maskedMatch: external_exports.string(),
|
|
17811
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17812
|
+
firstSeen: external_exports.string(),
|
|
17813
|
+
lastSeen: external_exports.string(),
|
|
17814
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17815
|
+
// the inventory badges it, the row links to revocation.
|
|
17816
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17817
|
+
sightings: external_exports.array(VaultSighting)
|
|
17818
|
+
});
|
|
17819
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17820
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17821
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17822
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17823
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17824
|
+
// Opaque; names the last row of the page just served.
|
|
17825
|
+
cursor: external_exports.string().optional()
|
|
17826
|
+
});
|
|
17827
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17828
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17829
|
+
// independent, so paging never changes what the count claims.
|
|
17830
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17831
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17832
|
+
// `null` once the last page is reached.
|
|
17833
|
+
nextCursor: external_exports.string().nullable()
|
|
17834
|
+
});
|
|
17835
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17836
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17837
|
+
cursor: external_exports.string().optional()
|
|
17838
|
+
});
|
|
17839
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17840
|
+
// Reused values across the whole store — the number the section's claim
|
|
17841
|
+
// ("values detected in more than one place") is about.
|
|
17842
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17843
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17844
|
+
nextCursor: external_exports.string().nullable()
|
|
17845
|
+
});
|
|
17846
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17847
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17848
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17849
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17850
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17851
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17852
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17853
|
+
cursor: external_exports.string().optional()
|
|
17854
|
+
});
|
|
17855
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17856
|
+
items: external_exports.array(VaultDeref),
|
|
17857
|
+
nextCursor: external_exports.string().nullable(),
|
|
17858
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17859
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17860
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17861
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17862
|
+
});
|
|
17863
|
+
var VaultKeyCustody = external_exports.string();
|
|
17864
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17865
|
+
var VaultConsent = external_exports.object({
|
|
17866
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17867
|
+
version: external_exports.number().int().positive()
|
|
17868
|
+
});
|
|
17869
|
+
|
|
17371
17870
|
// ../../packages/schema/src/zod/local.ts
|
|
17372
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17871
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17373
17872
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
17374
17873
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17375
17874
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
@@ -17395,6 +17894,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17395
17894
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17396
17895
|
// Shares writes.
|
|
17397
17896
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17897
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17898
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17899
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17900
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17901
|
+
// purging the vault is the eraser.
|
|
17902
|
+
vaultConsent: VaultConsent.optional(),
|
|
17903
|
+
// Where the vault master key lives.
|
|
17904
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17905
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17906
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17398
17907
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17399
17908
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17400
17909
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -17727,7 +18236,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17727
18236
|
// Omit for both kinds.
|
|
17728
18237
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17729
18238
|
});
|
|
17730
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18239
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17731
18240
|
var ScanCoverageProvider = external_exports.object({
|
|
17732
18241
|
provider: Provider,
|
|
17733
18242
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -17969,6 +18478,138 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
17969
18478
|
);
|
|
17970
18479
|
}
|
|
17971
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
|
+
|
|
18531
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18532
|
+
function backupPath(file2, tag) {
|
|
18533
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18534
|
+
}
|
|
18535
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18536
|
+
function reapStalePartials(file2) {
|
|
18537
|
+
const dir = dirname(file2);
|
|
18538
|
+
const prefix = `${basename(file2)}.`;
|
|
18539
|
+
let entries;
|
|
18540
|
+
try {
|
|
18541
|
+
entries = readdirSync(dir);
|
|
18542
|
+
} catch {
|
|
18543
|
+
return;
|
|
18544
|
+
}
|
|
18545
|
+
for (const name of entries) {
|
|
18546
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18547
|
+
const partial2 = join(dir, name);
|
|
18548
|
+
try {
|
|
18549
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18550
|
+
rmSync2(partial2, { force: true });
|
|
18551
|
+
}
|
|
18552
|
+
} catch {
|
|
18553
|
+
}
|
|
18554
|
+
}
|
|
18555
|
+
}
|
|
18556
|
+
function snapshotStore(db, backup) {
|
|
18557
|
+
const partial2 = `${backup}.partial`;
|
|
18558
|
+
try {
|
|
18559
|
+
rmSync2(partial2, { force: true });
|
|
18560
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18561
|
+
tightenFile(partial2);
|
|
18562
|
+
renameSync2(partial2, backup);
|
|
18563
|
+
} catch (error51) {
|
|
18564
|
+
try {
|
|
18565
|
+
rmSync2(partial2, { force: true });
|
|
18566
|
+
} catch {
|
|
18567
|
+
}
|
|
18568
|
+
throw error51;
|
|
18569
|
+
}
|
|
18570
|
+
}
|
|
18571
|
+
function moveStoreAside(file2, backup) {
|
|
18572
|
+
const undo = [];
|
|
18573
|
+
renameSync2(file2, backup);
|
|
18574
|
+
undo.push([backup, file2]);
|
|
18575
|
+
try {
|
|
18576
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18577
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18578
|
+
try {
|
|
18579
|
+
renameSync2(sidecar, moved);
|
|
18580
|
+
undo.push([moved, sidecar]);
|
|
18581
|
+
} catch {
|
|
18582
|
+
rmSync2(sidecar, { force: true });
|
|
18583
|
+
}
|
|
18584
|
+
}
|
|
18585
|
+
} catch (error51) {
|
|
18586
|
+
for (const [from, to] of undo.reverse()) {
|
|
18587
|
+
try {
|
|
18588
|
+
renameSync2(from, to);
|
|
18589
|
+
} catch {
|
|
18590
|
+
}
|
|
18591
|
+
}
|
|
18592
|
+
throw error51;
|
|
18593
|
+
}
|
|
18594
|
+
tightenPerms(backup);
|
|
18595
|
+
}
|
|
18596
|
+
function discardStore(file2, backup) {
|
|
18597
|
+
try {
|
|
18598
|
+
rmSync2(file2, { force: true });
|
|
18599
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18600
|
+
rmSync2(sidecar, { force: true });
|
|
18601
|
+
}
|
|
18602
|
+
} catch (error51) {
|
|
18603
|
+
if (existsSync(file2)) {
|
|
18604
|
+
try {
|
|
18605
|
+
rmSync2(backup, { force: true });
|
|
18606
|
+
} catch {
|
|
18607
|
+
}
|
|
18608
|
+
}
|
|
18609
|
+
throw error51;
|
|
18610
|
+
}
|
|
18611
|
+
}
|
|
18612
|
+
|
|
17972
18613
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
17973
18614
|
function escapeLikePattern(s) {
|
|
17974
18615
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18110,38 +18751,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18110
18751
|
return out;
|
|
18111
18752
|
}
|
|
18112
18753
|
|
|
18113
|
-
// ../../packages/persistence/src/paths.ts
|
|
18114
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18115
|
-
var DATA_DIR_MODE = 448;
|
|
18116
|
-
var DATA_FILE_MODE = 384;
|
|
18117
|
-
var DB_FILENAME = "aka.db";
|
|
18118
|
-
function chmodBestEffort(path, mode) {
|
|
18119
|
-
try {
|
|
18120
|
-
chmodSync(path, mode);
|
|
18121
|
-
} catch {
|
|
18122
|
-
}
|
|
18123
|
-
}
|
|
18124
|
-
function tightenDir(dir) {
|
|
18125
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18126
|
-
}
|
|
18127
|
-
function ensureDataDirSync(dir) {
|
|
18128
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18129
|
-
tightenDir(dir);
|
|
18130
|
-
}
|
|
18131
|
-
function dbSidecars(file2) {
|
|
18132
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18133
|
-
}
|
|
18134
|
-
function tightenFile(file2) {
|
|
18135
|
-
try {
|
|
18136
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18137
|
-
} catch {
|
|
18138
|
-
}
|
|
18139
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18140
|
-
}
|
|
18141
|
-
function tightenPerms(file2) {
|
|
18142
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18143
|
-
}
|
|
18144
|
-
|
|
18145
18754
|
// ../../packages/persistence/src/migrations.ts
|
|
18146
18755
|
function describeObject(object2) {
|
|
18147
18756
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18257,9 +18866,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18257
18866
|
}
|
|
18258
18867
|
}
|
|
18259
18868
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18869
|
+
reapStalePartials(file2);
|
|
18870
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18871
|
+
snapshotStore(db, backup);
|
|
18263
18872
|
return backup;
|
|
18264
18873
|
}
|
|
18265
18874
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18603,6 +19212,25 @@ function parseJsonObject(s) {
|
|
|
18603
19212
|
return void 0;
|
|
18604
19213
|
}
|
|
18605
19214
|
|
|
19215
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19216
|
+
function encodeKeysetCursor(payload) {
|
|
19217
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19218
|
+
}
|
|
19219
|
+
function decodeKeysetCursor(cursor) {
|
|
19220
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19221
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19222
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19223
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19224
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19225
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19226
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19227
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19228
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19229
|
+
return parsed;
|
|
19230
|
+
}
|
|
19231
|
+
return null;
|
|
19232
|
+
}
|
|
19233
|
+
|
|
18606
19234
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18607
19235
|
var DAY_MS = 864e5;
|
|
18608
19236
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18648,16 +19276,6 @@ function utcWindow(nowMs) {
|
|
|
18648
19276
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18649
19277
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18650
19278
|
}
|
|
18651
|
-
function encodeCursor(payload) {
|
|
18652
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18653
|
-
}
|
|
18654
|
-
function decodeCursor(cursor) {
|
|
18655
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18656
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18657
|
-
return parsed;
|
|
18658
|
-
}
|
|
18659
|
-
return null;
|
|
18660
|
-
}
|
|
18661
19279
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18662
19280
|
session: "session",
|
|
18663
19281
|
prompt: "prompt",
|
|
@@ -18802,7 +19420,7 @@ var SqliteActivityRepository = class {
|
|
|
18802
19420
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18803
19421
|
}
|
|
18804
19422
|
listSessions(query) {
|
|
18805
|
-
const cursor = query.cursor ?
|
|
19423
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18806
19424
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18807
19425
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18808
19426
|
const conditions = [SESSION_ROOT];
|
|
@@ -18876,7 +19494,7 @@ var SqliteActivityRepository = class {
|
|
|
18876
19494
|
)
|
|
18877
19495
|
);
|
|
18878
19496
|
const last = page[page.length - 1];
|
|
18879
|
-
const nextCursor = hasMore && last ?
|
|
19497
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
18880
19498
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
18881
19499
|
}
|
|
18882
19500
|
getSession(sessionId) {
|
|
@@ -19749,7 +20367,7 @@ var SqliteEventsRepository = class {
|
|
|
19749
20367
|
};
|
|
19750
20368
|
|
|
19751
20369
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19752
|
-
import { randomUUID } from "crypto";
|
|
20370
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19753
20371
|
|
|
19754
20372
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19755
20373
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19781,9 +20399,13 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19781
20399
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19782
20400
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19783
20401
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
20402
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
20403
|
+
AND conditions IS NULL
|
|
20404
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19784
20405
|
var SqliteExceptionsRepository = class {
|
|
19785
|
-
constructor(db) {
|
|
20406
|
+
constructor(db, now = () => Date.now()) {
|
|
19786
20407
|
this.db = db;
|
|
20408
|
+
this.now = now;
|
|
19787
20409
|
this.consumeStmt = db.prepare(
|
|
19788
20410
|
`UPDATE exceptions
|
|
19789
20411
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19801,6 +20423,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19801
20423
|
);
|
|
19802
20424
|
}
|
|
19803
20425
|
db;
|
|
20426
|
+
now;
|
|
19804
20427
|
consumeStmt;
|
|
19805
20428
|
insertBlockedStmt;
|
|
19806
20429
|
sweepBlockedStmt;
|
|
@@ -19827,8 +20450,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19827
20450
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19828
20451
|
);
|
|
19829
20452
|
}
|
|
19830
|
-
const id =
|
|
19831
|
-
const now =
|
|
20453
|
+
const id = randomUUID2();
|
|
20454
|
+
const now = this.now();
|
|
19832
20455
|
try {
|
|
19833
20456
|
this.insertExceptionRow(id, input, now);
|
|
19834
20457
|
} catch (err) {
|
|
@@ -19872,11 +20495,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19872
20495
|
this.db.prepare(
|
|
19873
20496
|
`INSERT INTO exceptions (
|
|
19874
20497
|
id, rule_id, category, value_fingerprint, key_version, masked_value,
|
|
19875
|
-
scope, expires_at, max_uses, use_count, last_used_at,
|
|
19876
|
-
conditions, created_by, created_via, created_at, updated_at
|
|
20498
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20499
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19877
20500
|
) VALUES (
|
|
19878
20501
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19879
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20502
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19880
20503
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19881
20504
|
)`
|
|
19882
20505
|
).run({
|
|
@@ -19886,6 +20509,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19886
20509
|
valueFingerprint: input.valueFingerprint,
|
|
19887
20510
|
keyVersion: input.keyVersion,
|
|
19888
20511
|
maskedValue: input.maskedValue,
|
|
20512
|
+
capability: input.capability ?? "suppress",
|
|
19889
20513
|
scope: input.scope,
|
|
19890
20514
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19891
20515
|
maxUses: input.maxUses,
|
|
@@ -19905,7 +20529,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19905
20529
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
19906
20530
|
const rows = allRows(
|
|
19907
20531
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
19908
|
-
opts?.includeTerminal ? {} : { now:
|
|
20532
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
19909
20533
|
);
|
|
19910
20534
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
19911
20535
|
return Promise.resolve(exceptions);
|
|
@@ -19940,7 +20564,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19940
20564
|
* already revoked.
|
|
19941
20565
|
*/
|
|
19942
20566
|
revoke(id, revokedBy, reason) {
|
|
19943
|
-
const now =
|
|
20567
|
+
const now = this.now();
|
|
19944
20568
|
const result = this.db.prepare(
|
|
19945
20569
|
`UPDATE exceptions
|
|
19946
20570
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -19954,7 +20578,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19954
20578
|
* callers must treat identically — means it does not and the detection is
|
|
19955
20579
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
19956
20580
|
*/
|
|
19957
|
-
consume(id, now =
|
|
20581
|
+
consume(id, now = this.now()) {
|
|
19958
20582
|
const result = this.consumeStmt.run({ id, now });
|
|
19959
20583
|
return Promise.resolve(Number(result.changes) === 1);
|
|
19960
20584
|
}
|
|
@@ -19963,7 +20587,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19963
20587
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
19964
20588
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
19965
20589
|
*/
|
|
19966
|
-
activeBundleEntries(keyVersion, now =
|
|
20590
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
19967
20591
|
const rows = allRows(
|
|
19968
20592
|
this.db.prepare(
|
|
19969
20593
|
`SELECT * FROM exceptions
|
|
@@ -19979,6 +20603,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19979
20603
|
ruleId: row.rule_id,
|
|
19980
20604
|
valueFingerprint: row.value_fingerprint,
|
|
19981
20605
|
keyVersion: row.key_version,
|
|
20606
|
+
capability: row.capability,
|
|
19982
20607
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
19983
20608
|
maxUses: row.max_uses,
|
|
19984
20609
|
useCount: row.use_count,
|
|
@@ -19994,7 +20619,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19994
20619
|
* than the retention window on every write, so the ledger self-limits.
|
|
19995
20620
|
*/
|
|
19996
20621
|
recordBlocked(entry) {
|
|
19997
|
-
const now =
|
|
20622
|
+
const now = this.now();
|
|
19998
20623
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
19999
20624
|
this.insertBlockedStmt.run({
|
|
20000
20625
|
reference: entry.reference,
|
|
@@ -20017,7 +20642,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20017
20642
|
WHERE blocked_at > :cutoff
|
|
20018
20643
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20019
20644
|
),
|
|
20020
|
-
{ cutoff:
|
|
20645
|
+
{ cutoff: this.now() - windowMs }
|
|
20021
20646
|
);
|
|
20022
20647
|
return Promise.resolve(
|
|
20023
20648
|
rows.map((row) => ({
|
|
@@ -20033,6 +20658,36 @@ var SqliteExceptionsRepository = class {
|
|
|
20033
20658
|
}))
|
|
20034
20659
|
);
|
|
20035
20660
|
}
|
|
20661
|
+
/**
|
|
20662
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20663
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20664
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20665
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20666
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20667
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20668
|
+
*
|
|
20669
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20670
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20671
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20672
|
+
*/
|
|
20673
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20674
|
+
try {
|
|
20675
|
+
const at = now ?? this.now();
|
|
20676
|
+
const row = getRow(
|
|
20677
|
+
this.db.prepare(
|
|
20678
|
+
`SELECT id FROM exceptions
|
|
20679
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20680
|
+
AND key_version = :keyVersion
|
|
20681
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20682
|
+
LIMIT 1`
|
|
20683
|
+
),
|
|
20684
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20685
|
+
);
|
|
20686
|
+
return Promise.resolve(row ?? null);
|
|
20687
|
+
} catch (err) {
|
|
20688
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20689
|
+
}
|
|
20690
|
+
}
|
|
20036
20691
|
/**
|
|
20037
20692
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20038
20693
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20040,7 +20695,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20040
20695
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20041
20696
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20042
20697
|
*/
|
|
20043
|
-
sweepTerminal(retentionMs, now =
|
|
20698
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20044
20699
|
const result = this.db.prepare(
|
|
20045
20700
|
`DELETE FROM exceptions
|
|
20046
20701
|
WHERE updated_at < :cutoff
|
|
@@ -20060,6 +20715,7 @@ function parseExceptionRow(row) {
|
|
|
20060
20715
|
valueFingerprint: row.value_fingerprint,
|
|
20061
20716
|
keyVersion: row.key_version,
|
|
20062
20717
|
maskedValue: row.masked_value,
|
|
20718
|
+
capability: row.capability,
|
|
20063
20719
|
scope: row.scope,
|
|
20064
20720
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20065
20721
|
maxUses: row.max_uses,
|
|
@@ -20102,6 +20758,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20102
20758
|
|
|
20103
20759
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20104
20760
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20761
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20762
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20763
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20764
|
+
function compareLocationOrder(a, b) {
|
|
20765
|
+
return compareFindingGroupOrder(
|
|
20766
|
+
{
|
|
20767
|
+
severity: a.maxSeverity,
|
|
20768
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20769
|
+
id: ""
|
|
20770
|
+
},
|
|
20771
|
+
{
|
|
20772
|
+
severity: b.maxSeverity,
|
|
20773
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20774
|
+
id: ""
|
|
20775
|
+
}
|
|
20776
|
+
);
|
|
20777
|
+
}
|
|
20105
20778
|
var CONCAT_SEP = ",";
|
|
20106
20779
|
var TUPLE_SEP = "|";
|
|
20107
20780
|
function splitConcat(value) {
|
|
@@ -20114,6 +20787,33 @@ function deriveInstanceStatus(row) {
|
|
|
20114
20787
|
latestResolutionStatus: row.latest_status
|
|
20115
20788
|
});
|
|
20116
20789
|
}
|
|
20790
|
+
function encodeGroupCursor(group) {
|
|
20791
|
+
const payload = {
|
|
20792
|
+
sev: group.severity,
|
|
20793
|
+
t: group.latestDetectedAt,
|
|
20794
|
+
id: group.id
|
|
20795
|
+
};
|
|
20796
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20797
|
+
}
|
|
20798
|
+
function decodeGroupCursor(cursor) {
|
|
20799
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20800
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20801
|
+
return {
|
|
20802
|
+
severity: parsed.sev,
|
|
20803
|
+
latestDetectedAt: parsed.t,
|
|
20804
|
+
id: parsed.id
|
|
20805
|
+
};
|
|
20806
|
+
}
|
|
20807
|
+
return null;
|
|
20808
|
+
}
|
|
20809
|
+
function firstAfter(sorted, cursor) {
|
|
20810
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20811
|
+
return index === -1 ? sorted.length : index;
|
|
20812
|
+
}
|
|
20813
|
+
function findDeepLinked(sorted, page, id) {
|
|
20814
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20815
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20816
|
+
}
|
|
20117
20817
|
var DAY_MS3 = 864e5;
|
|
20118
20818
|
var SqliteFindingsRepository = class {
|
|
20119
20819
|
constructor(db) {
|
|
@@ -20223,8 +20923,13 @@ var SqliteFindingsRepository = class {
|
|
|
20223
20923
|
*/
|
|
20224
20924
|
listGroupedFindings(query) {
|
|
20225
20925
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20226
|
-
const
|
|
20227
|
-
const
|
|
20926
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20927
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20928
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20929
|
+
const sessionParams = {
|
|
20930
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20931
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20932
|
+
};
|
|
20228
20933
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20229
20934
|
predicate,
|
|
20230
20935
|
params: sessionParams
|
|
@@ -20232,7 +20937,8 @@ var SqliteFindingsRepository = class {
|
|
|
20232
20937
|
const rows = allRows(
|
|
20233
20938
|
this.db.prepare(
|
|
20234
20939
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20235
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20940
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20941
|
+
kind, finding_key, latest_status
|
|
20236
20942
|
FROM (
|
|
20237
20943
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20238
20944
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20242,6 +20948,7 @@ var SqliteFindingsRepository = class {
|
|
|
20242
20948
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20243
20949
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20244
20950
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
20951
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20245
20952
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20246
20953
|
latest.status AS latest_status,
|
|
20247
20954
|
ROW_NUMBER() OVER (
|
|
@@ -20273,6 +20980,8 @@ var SqliteFindingsRepository = class {
|
|
|
20273
20980
|
repo: r.repo ?? "",
|
|
20274
20981
|
file: r.file ?? "",
|
|
20275
20982
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
20983
|
+
eventId: r.event_id,
|
|
20984
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20276
20985
|
status: deriveInstanceStatus(r)
|
|
20277
20986
|
}));
|
|
20278
20987
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20296,18 +21005,23 @@ var SqliteFindingsRepository = class {
|
|
|
20296
21005
|
groups: sorted.length
|
|
20297
21006
|
};
|
|
20298
21007
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21008
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21009
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21010
|
+
const page = sorted.slice(start, start + limit);
|
|
21011
|
+
const lastOnPage = page.at(-1);
|
|
21012
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21013
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20299
21014
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20300
|
-
const
|
|
20301
|
-
|
|
20302
|
-
|
|
20303
|
-
|
|
20304
|
-
|
|
20305
|
-
);
|
|
21015
|
+
const narrow = (g) => statusSet ? {
|
|
21016
|
+
...g,
|
|
21017
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21018
|
+
} : g;
|
|
21019
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20306
21020
|
return Promise.resolve({
|
|
20307
21021
|
totals,
|
|
20308
21022
|
facets,
|
|
20309
21023
|
items,
|
|
20310
|
-
nextCursor
|
|
21024
|
+
nextCursor,
|
|
20311
21025
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20312
21026
|
});
|
|
20313
21027
|
}
|
|
@@ -20339,6 +21053,266 @@ var SqliteFindingsRepository = class {
|
|
|
20339
21053
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20340
21054
|
* path repeating across tuples.)
|
|
20341
21055
|
*/
|
|
21056
|
+
/**
|
|
21057
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21058
|
+
* paged by keyset.
|
|
21059
|
+
*
|
|
21060
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21061
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21062
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21063
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21064
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21065
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21066
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21067
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21068
|
+
* none of the mappers names", which no IN-list can say.
|
|
21069
|
+
*
|
|
21070
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21071
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21072
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21073
|
+
* while the counting runs, and only the page itself is retained.
|
|
21074
|
+
*/
|
|
21075
|
+
listFindingInstances(query) {
|
|
21076
|
+
const opts = {
|
|
21077
|
+
severity: query.severity,
|
|
21078
|
+
subtype: query.subtype,
|
|
21079
|
+
providers: query.provider,
|
|
21080
|
+
actions: query.action,
|
|
21081
|
+
statuses: query.status,
|
|
21082
|
+
tools: query.tool,
|
|
21083
|
+
repo: query.repo,
|
|
21084
|
+
file: query.file,
|
|
21085
|
+
q: query.q
|
|
21086
|
+
};
|
|
21087
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21088
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21089
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21090
|
+
const items = [];
|
|
21091
|
+
let total = 0;
|
|
21092
|
+
let last;
|
|
21093
|
+
let hasMore = false;
|
|
21094
|
+
for (const row of this.scanFindingRows({
|
|
21095
|
+
sessionId: query.sessionId,
|
|
21096
|
+
from: query.from
|
|
21097
|
+
})) {
|
|
21098
|
+
accumulator.add(row);
|
|
21099
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21100
|
+
total += 1;
|
|
21101
|
+
if (items.length < limit) {
|
|
21102
|
+
items.push(toInstanceDetail(row));
|
|
21103
|
+
last = row;
|
|
21104
|
+
} else {
|
|
21105
|
+
hasMore = true;
|
|
21106
|
+
}
|
|
21107
|
+
}
|
|
21108
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21109
|
+
if (cursor !== null) {
|
|
21110
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21111
|
+
return Promise.resolve({
|
|
21112
|
+
totals: { findings: total },
|
|
21113
|
+
facets: accumulator.facets(),
|
|
21114
|
+
items: resumed.items,
|
|
21115
|
+
nextCursor: resumed.nextCursor
|
|
21116
|
+
});
|
|
21117
|
+
}
|
|
21118
|
+
return Promise.resolve({
|
|
21119
|
+
totals: { findings: total },
|
|
21120
|
+
facets: accumulator.facets(),
|
|
21121
|
+
items,
|
|
21122
|
+
nextCursor
|
|
21123
|
+
});
|
|
21124
|
+
}
|
|
21125
|
+
/**
|
|
21126
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21127
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21128
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21129
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21130
|
+
*/
|
|
21131
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21132
|
+
const items = [];
|
|
21133
|
+
let last;
|
|
21134
|
+
let hasMore = false;
|
|
21135
|
+
for (const row of this.scanFindingRows({
|
|
21136
|
+
sessionId: query.sessionId,
|
|
21137
|
+
from: query.from,
|
|
21138
|
+
after: cursor
|
|
21139
|
+
})) {
|
|
21140
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21141
|
+
if (items.length < limit) {
|
|
21142
|
+
items.push(toInstanceDetail(row));
|
|
21143
|
+
last = row;
|
|
21144
|
+
} else {
|
|
21145
|
+
hasMore = true;
|
|
21146
|
+
break;
|
|
21147
|
+
}
|
|
21148
|
+
}
|
|
21149
|
+
return {
|
|
21150
|
+
items,
|
|
21151
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21152
|
+
};
|
|
21153
|
+
}
|
|
21154
|
+
/**
|
|
21155
|
+
* The same findings folded by location: repository, then file within it.
|
|
21156
|
+
*
|
|
21157
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21158
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21159
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21160
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21161
|
+
* filter can name it.
|
|
21162
|
+
*/
|
|
21163
|
+
listFindingLocations(query) {
|
|
21164
|
+
const opts = {
|
|
21165
|
+
severity: query.severity,
|
|
21166
|
+
subtype: query.subtype,
|
|
21167
|
+
providers: query.provider,
|
|
21168
|
+
actions: query.action,
|
|
21169
|
+
statuses: query.status,
|
|
21170
|
+
tools: query.tool,
|
|
21171
|
+
q: query.q
|
|
21172
|
+
};
|
|
21173
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21174
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21175
|
+
let total = 0;
|
|
21176
|
+
for (const row of this.scanFindingRows({
|
|
21177
|
+
sessionId: query.sessionId,
|
|
21178
|
+
from: query.from
|
|
21179
|
+
})) {
|
|
21180
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21181
|
+
total += 1;
|
|
21182
|
+
let files = byRepo.get(row.repo);
|
|
21183
|
+
if (files === void 0) {
|
|
21184
|
+
files = /* @__PURE__ */ new Map();
|
|
21185
|
+
byRepo.set(row.repo, files);
|
|
21186
|
+
}
|
|
21187
|
+
let acc = files.get(row.file);
|
|
21188
|
+
if (acc === void 0) {
|
|
21189
|
+
acc = newLocationAccumulator();
|
|
21190
|
+
files.set(row.file, acc);
|
|
21191
|
+
}
|
|
21192
|
+
addToLocation(acc, row);
|
|
21193
|
+
}
|
|
21194
|
+
let fileCount = 0;
|
|
21195
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21196
|
+
fileCount += files.size;
|
|
21197
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21198
|
+
file: file2,
|
|
21199
|
+
instanceCount: acc.instanceCount,
|
|
21200
|
+
maxSeverity: acc.maxSeverity,
|
|
21201
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21202
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21203
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21204
|
+
})).sort(compareLocationOrder);
|
|
21205
|
+
const rollup = fileRows.reduce(
|
|
21206
|
+
(a, f) => ({
|
|
21207
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21208
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21209
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21210
|
+
}),
|
|
21211
|
+
{
|
|
21212
|
+
instanceCount: 0,
|
|
21213
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21214
|
+
latestDetectedAt: ""
|
|
21215
|
+
}
|
|
21216
|
+
);
|
|
21217
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21218
|
+
const folded = foldGroupStatus(statuses);
|
|
21219
|
+
return {
|
|
21220
|
+
repo,
|
|
21221
|
+
instanceCount: rollup.instanceCount,
|
|
21222
|
+
maxSeverity: rollup.maxSeverity,
|
|
21223
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21224
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21225
|
+
files: fileRows
|
|
21226
|
+
};
|
|
21227
|
+
});
|
|
21228
|
+
repos.sort(compareLocationOrder);
|
|
21229
|
+
return Promise.resolve({
|
|
21230
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21231
|
+
items: repos.slice(0, limit),
|
|
21232
|
+
hasMore: repos.length > limit
|
|
21233
|
+
});
|
|
21234
|
+
}
|
|
21235
|
+
/**
|
|
21236
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21237
|
+
*
|
|
21238
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21239
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21240
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21241
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21242
|
+
* rather than one unbounded result set.
|
|
21243
|
+
*
|
|
21244
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21245
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21246
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21247
|
+
* a window over the whole resolution table once per batch.
|
|
21248
|
+
*
|
|
21249
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21250
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21251
|
+
* dimension — see listFindingInstances.
|
|
21252
|
+
*/
|
|
21253
|
+
*scanFindingRows(scope) {
|
|
21254
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21255
|
+
const params = [];
|
|
21256
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21257
|
+
conditions.push("e.root_session_id = ?");
|
|
21258
|
+
params.push(scope.sessionId);
|
|
21259
|
+
}
|
|
21260
|
+
if (scope.from !== void 0) {
|
|
21261
|
+
conditions.push("e.started_at >= ?");
|
|
21262
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21263
|
+
}
|
|
21264
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21265
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21266
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21267
|
+
e.started_at AS occurred_at,
|
|
21268
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21269
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21270
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21271
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21272
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21273
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21274
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21275
|
+
FROM inspection_findings f
|
|
21276
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21277
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21278
|
+
WHERE ${conditions.join(" AND ")}
|
|
21279
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21280
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21281
|
+
LIMIT ?`;
|
|
21282
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21283
|
+
for (; ; ) {
|
|
21284
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21285
|
+
...params,
|
|
21286
|
+
after.startedAtMs,
|
|
21287
|
+
after.startedAtMs,
|
|
21288
|
+
after.id,
|
|
21289
|
+
SCAN_BATCH_ROWS
|
|
21290
|
+
]);
|
|
21291
|
+
for (const r of rows) {
|
|
21292
|
+
yield {
|
|
21293
|
+
id: r.id,
|
|
21294
|
+
ruleId: r.rule_id,
|
|
21295
|
+
category: r.category,
|
|
21296
|
+
severity: r.severity,
|
|
21297
|
+
maskedMatch: r.masked_match,
|
|
21298
|
+
actionTaken: r.action_taken,
|
|
21299
|
+
confidence: r.confidence,
|
|
21300
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21301
|
+
sourceTool: r.source_tool,
|
|
21302
|
+
repo: r.repo ?? "",
|
|
21303
|
+
file: r.file ?? "",
|
|
21304
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21305
|
+
eventId: r.event_id,
|
|
21306
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21307
|
+
status: deriveInstanceStatus(r)
|
|
21308
|
+
};
|
|
21309
|
+
}
|
|
21310
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21311
|
+
const lastRow = rows[rows.length - 1];
|
|
21312
|
+
if (lastRow === void 0) return;
|
|
21313
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21314
|
+
}
|
|
21315
|
+
}
|
|
20342
21316
|
groupAggregates(withSearchText, scope) {
|
|
20343
21317
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20344
21318
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20599,7 +21573,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20599
21573
|
};
|
|
20600
21574
|
|
|
20601
21575
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20602
|
-
import { createHash as createHash2, randomUUID as
|
|
21576
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20603
21577
|
|
|
20604
21578
|
// ../../packages/persistence/src/semver.ts
|
|
20605
21579
|
function parse3(version2) {
|
|
@@ -20750,7 +21724,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20750
21724
|
let behind = false;
|
|
20751
21725
|
for (const row of rows) {
|
|
20752
21726
|
const params = {
|
|
20753
|
-
id:
|
|
21727
|
+
id: randomUUID3(),
|
|
20754
21728
|
namespace: row.namespace,
|
|
20755
21729
|
packId: row.packId,
|
|
20756
21730
|
version: row.version,
|
|
@@ -20762,7 +21736,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20762
21736
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20763
21737
|
this.upsertAvailableStmt.run({
|
|
20764
21738
|
...params,
|
|
20765
|
-
id:
|
|
21739
|
+
id: randomUUID3(),
|
|
20766
21740
|
recordedBy: meta3?.recordedBy ?? null
|
|
20767
21741
|
});
|
|
20768
21742
|
} else {
|
|
@@ -21085,14 +22059,15 @@ var SqliteInventoryRepository = class {
|
|
|
21085
22059
|
};
|
|
21086
22060
|
|
|
21087
22061
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21088
|
-
import { randomUUID as
|
|
22062
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21089
22063
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21090
22064
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21091
22065
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21092
22066
|
var HARNESS_LABELS = {
|
|
21093
22067
|
claudecode: "Claude Code",
|
|
21094
22068
|
cursor: "Cursor",
|
|
21095
|
-
codex: "Codex"
|
|
22069
|
+
codex: "Codex",
|
|
22070
|
+
antigravity: "Antigravity"
|
|
21096
22071
|
};
|
|
21097
22072
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21098
22073
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21107,6 +22082,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21107
22082
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21108
22083
|
if (t.includes("cursor")) return "cursor";
|
|
21109
22084
|
if (t.includes("codex")) return "codex";
|
|
22085
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21110
22086
|
return null;
|
|
21111
22087
|
}
|
|
21112
22088
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21565,7 +22541,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21565
22541
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21566
22542
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21567
22543
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21568
|
-
).run({ id:
|
|
22544
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21569
22545
|
}
|
|
21570
22546
|
return true;
|
|
21571
22547
|
}
|
|
@@ -21586,7 +22562,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21586
22562
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21587
22563
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21588
22564
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21589
|
-
).run({ id:
|
|
22565
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21590
22566
|
}
|
|
21591
22567
|
this.configRowsCache = void 0;
|
|
21592
22568
|
return "ok";
|
|
@@ -21883,7 +22859,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21883
22859
|
};
|
|
21884
22860
|
|
|
21885
22861
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
21886
|
-
import { randomUUID as
|
|
22862
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
21887
22863
|
var SqlitePoliciesRepository = class {
|
|
21888
22864
|
constructor(db) {
|
|
21889
22865
|
this.db = db;
|
|
@@ -21918,7 +22894,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21918
22894
|
failOpenTransaction(this.db, () => {
|
|
21919
22895
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
21920
22896
|
stmt.run({
|
|
21921
|
-
id:
|
|
22897
|
+
id: randomUUID5(),
|
|
21922
22898
|
target: JSON.stringify({ category }),
|
|
21923
22899
|
action,
|
|
21924
22900
|
now: Date.now()
|
|
@@ -21938,7 +22914,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21938
22914
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
21939
22915
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
21940
22916
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
21941
|
-
).run({ id:
|
|
22917
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
21942
22918
|
}
|
|
21943
22919
|
// Caps every global per-category policy currently set to block/redact down
|
|
21944
22920
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22006,7 +22982,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22006
22982
|
};
|
|
22007
22983
|
|
|
22008
22984
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22009
|
-
import { randomUUID as
|
|
22985
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22010
22986
|
var SqliteProjectFilesRepository = class {
|
|
22011
22987
|
constructor(db) {
|
|
22012
22988
|
this.db = db;
|
|
@@ -22038,7 +23014,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22038
23014
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22039
23015
|
for (const file2 of scan2.files) {
|
|
22040
23016
|
this.upsertStmt.run({
|
|
22041
|
-
id:
|
|
23017
|
+
id: randomUUID6(),
|
|
22042
23018
|
projectId,
|
|
22043
23019
|
path: file2.path,
|
|
22044
23020
|
name: file2.name,
|
|
@@ -22052,7 +23028,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22052
23028
|
};
|
|
22053
23029
|
|
|
22054
23030
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22055
|
-
import { randomUUID as
|
|
23031
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22056
23032
|
var SqliteResolutionsRepository = class {
|
|
22057
23033
|
constructor(db, now = () => Date.now()) {
|
|
22058
23034
|
this.db = db;
|
|
@@ -22106,7 +23082,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22106
23082
|
*/
|
|
22107
23083
|
insertResolution(r) {
|
|
22108
23084
|
this.insertStmt.run({
|
|
22109
|
-
id:
|
|
23085
|
+
id: randomUUID7(),
|
|
22110
23086
|
findingKey: r.findingKey,
|
|
22111
23087
|
status: FindingStatus.parse(r.status),
|
|
22112
23088
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22165,13 +23141,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22165
23141
|
this.readStmt = db.prepare(
|
|
22166
23142
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22167
23143
|
);
|
|
23144
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23145
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23146
|
+
);
|
|
23147
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23148
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23149
|
+
);
|
|
22168
23150
|
}
|
|
22169
23151
|
db;
|
|
22170
23152
|
upsertStmt;
|
|
22171
23153
|
readStmt;
|
|
23154
|
+
countQuarantinedStmt;
|
|
23155
|
+
clearQuarantinedStmt;
|
|
22172
23156
|
getVerdict(ruleKey) {
|
|
22173
23157
|
return getRow(this.readStmt, { ruleKey });
|
|
22174
23158
|
}
|
|
23159
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23160
|
+
countQuarantined() {
|
|
23161
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23162
|
+
}
|
|
23163
|
+
/**
|
|
23164
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23165
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23166
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23167
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23168
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23169
|
+
*
|
|
23170
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23171
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23172
|
+
*
|
|
23173
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23174
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23175
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23176
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23177
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23178
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23179
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23180
|
+
* to restore are silently still disabled.
|
|
23181
|
+
*/
|
|
23182
|
+
clearQuarantined() {
|
|
23183
|
+
const before = this.countQuarantined();
|
|
23184
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23185
|
+
this.clearQuarantinedStmt.run();
|
|
23186
|
+
});
|
|
23187
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23188
|
+
}
|
|
22175
23189
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22176
23190
|
failOpenTransaction(this.db, () => {
|
|
22177
23191
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22225,113 +23239,528 @@ var SqliteScanLedgerRepository = class {
|
|
|
22225
23239
|
}
|
|
22226
23240
|
};
|
|
22227
23241
|
|
|
22228
|
-
// ../../packages/persistence/src/repositories/
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
redact: "redacted",
|
|
22234
|
-
warn: "warned"
|
|
22235
|
-
};
|
|
22236
|
-
var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
22237
|
-
var SCAN_COVERAGE = [
|
|
22238
|
-
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22239
|
-
{ provider: "cursor", coverage: 0, supported: false },
|
|
22240
|
-
{ provider: "codex", coverage: 0, supported: false },
|
|
22241
|
-
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22242
|
-
{ provider: "copilot", coverage: 0, supported: false },
|
|
22243
|
-
{ provider: "api", coverage: 0, supported: false }
|
|
22244
|
-
];
|
|
22245
|
-
var GRANULARITY = {
|
|
22246
|
-
"7d": "day",
|
|
22247
|
-
"30d": "day",
|
|
22248
|
-
"3m": "week",
|
|
22249
|
-
"6m": "week"
|
|
22250
|
-
};
|
|
22251
|
-
function granularityFor(range) {
|
|
22252
|
-
return GRANULARITY[range];
|
|
22253
|
-
}
|
|
22254
|
-
function startOfUtcDay2(ms) {
|
|
22255
|
-
return Math.floor(ms / DAY_MS4) * DAY_MS4;
|
|
23242
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
23243
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23244
|
+
function pageLimit(requested, fallback) {
|
|
23245
|
+
if (requested === void 0) return fallback;
|
|
23246
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
22256
23247
|
}
|
|
22257
|
-
function
|
|
22258
|
-
return
|
|
23248
|
+
function encodeReuseCursor(payload) {
|
|
23249
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
22259
23250
|
}
|
|
22260
|
-
function
|
|
22261
|
-
|
|
23251
|
+
function decodeReuseCursor(cursor) {
|
|
23252
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23253
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23254
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23255
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23256
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23257
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23258
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23259
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23260
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23261
|
+
}
|
|
23262
|
+
return null;
|
|
22262
23263
|
}
|
|
22263
|
-
var
|
|
22264
|
-
|
|
23264
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23265
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23266
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23267
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23268
|
+
function toSighting(row) {
|
|
23269
|
+
return {
|
|
23270
|
+
location: row.location,
|
|
23271
|
+
kind: row.kind,
|
|
23272
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23273
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23274
|
+
};
|
|
23275
|
+
}
|
|
23276
|
+
var SELECT_COLUMNS = `
|
|
23277
|
+
pointer_id AS pointerId,
|
|
23278
|
+
value_fingerprint AS valueFingerprint,
|
|
23279
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
23280
|
+
key_version AS keyVersion,
|
|
23281
|
+
format_version AS formatVersion,
|
|
23282
|
+
category,
|
|
23283
|
+
rule_id AS ruleId,
|
|
23284
|
+
masked_match AS maskedMatch,
|
|
23285
|
+
provider,
|
|
23286
|
+
ciphertext,
|
|
23287
|
+
nonce,
|
|
23288
|
+
auth_tag AS authTag,
|
|
23289
|
+
occurrence_count AS occurrenceCount,
|
|
23290
|
+
first_seen AS firstSeen,
|
|
23291
|
+
last_seen AS lastSeen`;
|
|
23292
|
+
function toRow(raw) {
|
|
23293
|
+
const { provider, ...rest } = raw;
|
|
23294
|
+
return provider === null ? rest : { ...rest, provider };
|
|
23295
|
+
}
|
|
23296
|
+
var SqliteSecretVaultRepository = class {
|
|
23297
|
+
constructor(db) {
|
|
22265
23298
|
this.db = db;
|
|
22266
|
-
this.
|
|
23299
|
+
this.insertStmt = db.prepare(
|
|
23300
|
+
`INSERT INTO secret_vault (
|
|
23301
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
23302
|
+
format_version, category, rule_id, masked_match, provider,
|
|
23303
|
+
ciphertext, nonce, auth_tag,
|
|
23304
|
+
occurrence_count, first_seen, last_seen
|
|
23305
|
+
) VALUES (
|
|
23306
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
23307
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
23308
|
+
:ciphertext, :nonce, :authTag,
|
|
23309
|
+
1, :now, :now
|
|
23310
|
+
)`
|
|
23311
|
+
);
|
|
23312
|
+
this.bumpStmt = db.prepare(
|
|
23313
|
+
`UPDATE secret_vault
|
|
23314
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
23315
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
23316
|
+
);
|
|
23317
|
+
this.byPointerStmt = db.prepare(
|
|
23318
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
23319
|
+
);
|
|
23320
|
+
this.byFingerprintStmt = db.prepare(
|
|
23321
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
23322
|
+
);
|
|
23323
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
23324
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
23325
|
+
`UPDATE secret_vault
|
|
23326
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
23327
|
+
WHERE pointer_id = :pointerId`
|
|
23328
|
+
);
|
|
23329
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
23330
|
+
`UPDATE secret_vault
|
|
23331
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
23332
|
+
WHERE pointer_id = :pointerId`
|
|
23333
|
+
);
|
|
23334
|
+
this.derefStmt = db.prepare(
|
|
23335
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
23336
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
23337
|
+
);
|
|
22267
23338
|
}
|
|
22268
23339
|
db;
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
|
|
22299
|
-
|
|
22300
|
-
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
AND (latest.status IS NULL OR latest.status != 'resolved') THEN 1
|
|
22320
|
-
ELSE 0
|
|
22321
|
-
END) AS open_at_rest
|
|
22322
|
-
FROM inspection_findings f
|
|
22323
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
22324
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
22325
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
22326
|
-
ON latest.finding_key = f.finding_key
|
|
22327
|
-
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
22328
|
-
GROUP BY d.severity`
|
|
22329
|
-
)
|
|
23340
|
+
insertStmt;
|
|
23341
|
+
bumpStmt;
|
|
23342
|
+
byPointerStmt;
|
|
23343
|
+
byFingerprintStmt;
|
|
23344
|
+
listStmt;
|
|
23345
|
+
replaceCiphertextStmt;
|
|
23346
|
+
refreshFingerprintStmt;
|
|
23347
|
+
derefStmt;
|
|
23348
|
+
/**
|
|
23349
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
23350
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
23351
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
23352
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
23353
|
+
* wire token. `minted` is true only when this call created the row.
|
|
23354
|
+
*
|
|
23355
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
23356
|
+
* writers cannot both decide they are minting.
|
|
23357
|
+
*/
|
|
23358
|
+
upsert(input, now) {
|
|
23359
|
+
let minted = false;
|
|
23360
|
+
withTransaction(
|
|
23361
|
+
this.db,
|
|
23362
|
+
() => {
|
|
23363
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
23364
|
+
valueFingerprint: input.valueFingerprint
|
|
23365
|
+
});
|
|
23366
|
+
if (existing === void 0) {
|
|
23367
|
+
this.insertStmt.run(
|
|
23368
|
+
bindParams({
|
|
23369
|
+
pointerId: input.pointerId,
|
|
23370
|
+
valueFingerprint: input.valueFingerprint,
|
|
23371
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
23372
|
+
keyVersion: input.keyVersion,
|
|
23373
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
23374
|
+
category: input.category,
|
|
23375
|
+
ruleId: input.ruleId,
|
|
23376
|
+
maskedMatch: input.maskedMatch,
|
|
23377
|
+
provider: input.provider,
|
|
23378
|
+
ciphertext: input.ciphertext,
|
|
23379
|
+
nonce: input.nonce,
|
|
23380
|
+
authTag: input.authTag,
|
|
23381
|
+
now
|
|
23382
|
+
})
|
|
23383
|
+
);
|
|
23384
|
+
minted = true;
|
|
23385
|
+
return;
|
|
23386
|
+
}
|
|
23387
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
23388
|
+
},
|
|
23389
|
+
"IMMEDIATE"
|
|
22330
23390
|
);
|
|
22331
|
-
const
|
|
22332
|
-
|
|
22333
|
-
|
|
22334
|
-
|
|
23391
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
23392
|
+
valueFingerprint: input.valueFingerprint
|
|
23393
|
+
});
|
|
23394
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
23395
|
+
return { row: toRow(row), minted };
|
|
23396
|
+
}
|
|
23397
|
+
byPointerId(pointerId) {
|
|
23398
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
23399
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23400
|
+
}
|
|
23401
|
+
byValueFingerprint(fingerprint) {
|
|
23402
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
23403
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23404
|
+
}
|
|
23405
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
23406
|
+
recordDeref(entry) {
|
|
23407
|
+
this.derefStmt.run(
|
|
23408
|
+
bindParams({
|
|
23409
|
+
id: entry.id,
|
|
23410
|
+
pointerId: entry.pointerId,
|
|
23411
|
+
at: entry.at,
|
|
23412
|
+
target: entry.target,
|
|
23413
|
+
reason: entry.reason,
|
|
23414
|
+
outcome: entry.outcome,
|
|
23415
|
+
grantId: entry.grantId,
|
|
23416
|
+
pointerCount: entry.pointerCount ?? 1
|
|
23417
|
+
})
|
|
23418
|
+
);
|
|
23419
|
+
}
|
|
23420
|
+
listAll() {
|
|
23421
|
+
return allRows(this.listStmt).map(toRow);
|
|
23422
|
+
}
|
|
23423
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
23424
|
+
replaceCiphertext(pointerId, next) {
|
|
23425
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
23426
|
+
}
|
|
23427
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
23428
|
+
refreshFingerprint(pointerId, next) {
|
|
23429
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
23430
|
+
}
|
|
23431
|
+
/**
|
|
23432
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
23433
|
+
* audit is left alone on purpose — see the table note above.
|
|
23434
|
+
*/
|
|
23435
|
+
purgeAll() {
|
|
23436
|
+
let destroyed = 0;
|
|
23437
|
+
withTransaction(
|
|
23438
|
+
this.db,
|
|
23439
|
+
() => {
|
|
23440
|
+
destroyed = this.countEntries();
|
|
23441
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
23442
|
+
},
|
|
23443
|
+
"IMMEDIATE"
|
|
23444
|
+
);
|
|
23445
|
+
return destroyed;
|
|
23446
|
+
}
|
|
23447
|
+
/**
|
|
23448
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
23449
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
23450
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
23451
|
+
* so callers wrap this, not the other way around.
|
|
23452
|
+
*/
|
|
23453
|
+
recordSighting(entry, now) {
|
|
23454
|
+
this.db.prepare(
|
|
23455
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
23456
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
23457
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
23458
|
+
).run({
|
|
23459
|
+
id: randomUUID8(),
|
|
23460
|
+
pointerId: entry.pointerId,
|
|
23461
|
+
location: entry.location,
|
|
23462
|
+
kind: entry.kind,
|
|
23463
|
+
now
|
|
23464
|
+
});
|
|
23465
|
+
}
|
|
23466
|
+
/**
|
|
23467
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23468
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23469
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23470
|
+
*
|
|
23471
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23472
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23473
|
+
*/
|
|
23474
|
+
sightingsFor(pointerIds) {
|
|
23475
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23476
|
+
if (pointerIds.length === 0) return byPointer;
|
|
23477
|
+
const rows = allRows(
|
|
23478
|
+
this.db.prepare(
|
|
23479
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23480
|
+
FROM secret_vault_sighting
|
|
23481
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23482
|
+
ORDER BY last_seen DESC`
|
|
23483
|
+
),
|
|
23484
|
+
pointerIds
|
|
23485
|
+
);
|
|
23486
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23487
|
+
return byPointer;
|
|
23488
|
+
}
|
|
23489
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23490
|
+
toInventoryEntries(rows) {
|
|
23491
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
23492
|
+
return rows.map((r) => ({
|
|
23493
|
+
pointerId: r.pointer_id,
|
|
23494
|
+
category: r.category,
|
|
23495
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23496
|
+
maskedMatch: r.masked_match,
|
|
23497
|
+
occurrences: r.occurrence_count,
|
|
23498
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
23499
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23500
|
+
revealGrantId: r.grant_id,
|
|
23501
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
23502
|
+
}));
|
|
23503
|
+
}
|
|
23504
|
+
/**
|
|
23505
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23506
|
+
* value's descriptor data joined with its sightings and the active
|
|
23507
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23508
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23509
|
+
*
|
|
23510
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23511
|
+
* sees never depends on how far they have paged.
|
|
23512
|
+
*/
|
|
23513
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23514
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23515
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23516
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
23517
|
+
const rows = allRows(
|
|
23518
|
+
this.db.prepare(
|
|
23519
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23520
|
+
(SELECT e.id FROM exceptions e
|
|
23521
|
+
WHERE e.rule_id = v.rule_id
|
|
23522
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23523
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23524
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23525
|
+
LIMIT 1) AS grant_id
|
|
23526
|
+
FROM secret_vault v
|
|
23527
|
+
${where}
|
|
23528
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23529
|
+
LIMIT :limit`
|
|
23530
|
+
),
|
|
23531
|
+
bindParams({
|
|
23532
|
+
now,
|
|
23533
|
+
limit: limit + 1,
|
|
23534
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23535
|
+
})
|
|
23536
|
+
);
|
|
23537
|
+
const hasMore = rows.length > limit;
|
|
23538
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23539
|
+
const last = page[page.length - 1];
|
|
23540
|
+
return {
|
|
23541
|
+
totals: { values: this.countEntries() },
|
|
23542
|
+
items: this.toInventoryEntries(page),
|
|
23543
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23544
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23545
|
+
};
|
|
23546
|
+
}
|
|
23547
|
+
/**
|
|
23548
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23549
|
+
* than one location — most-reused first, one page at a time.
|
|
23550
|
+
*
|
|
23551
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23552
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23553
|
+
* under-report exactly the values a reader most needs to see.
|
|
23554
|
+
*/
|
|
23555
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23556
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23557
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23558
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23559
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23560
|
+
const rows = allRows(
|
|
23561
|
+
this.db.prepare(
|
|
23562
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23563
|
+
(SELECT e.id FROM exceptions e
|
|
23564
|
+
WHERE e.rule_id = v.rule_id
|
|
23565
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23566
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23567
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23568
|
+
LIMIT 1) AS grant_id
|
|
23569
|
+
FROM secret_vault v
|
|
23570
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23571
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23572
|
+
LIMIT :limit`
|
|
23573
|
+
),
|
|
23574
|
+
bindParams({
|
|
23575
|
+
now,
|
|
23576
|
+
limit: limit + 1,
|
|
23577
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23578
|
+
})
|
|
23579
|
+
);
|
|
23580
|
+
const hasMore = rows.length > limit;
|
|
23581
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23582
|
+
const last = page[page.length - 1];
|
|
23583
|
+
return {
|
|
23584
|
+
totals: { reused: this.countReused() },
|
|
23585
|
+
items: this.toInventoryEntries(page),
|
|
23586
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23587
|
+
};
|
|
23588
|
+
}
|
|
23589
|
+
/**
|
|
23590
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23591
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23592
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23593
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23594
|
+
*
|
|
23595
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23596
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23597
|
+
* the reader pages.
|
|
23598
|
+
*/
|
|
23599
|
+
listDerefs(query = {}) {
|
|
23600
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23601
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23602
|
+
const conditions = [];
|
|
23603
|
+
if (query.includeBatched !== true) {
|
|
23604
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23605
|
+
}
|
|
23606
|
+
if (cursor !== null) {
|
|
23607
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23608
|
+
}
|
|
23609
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
23610
|
+
const rows = allRows(
|
|
23611
|
+
this.db.prepare(
|
|
23612
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
23613
|
+
FROM secret_vault_deref ${where}
|
|
23614
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
23615
|
+
),
|
|
23616
|
+
bindParams({
|
|
23617
|
+
limit: limit + 1,
|
|
23618
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23619
|
+
})
|
|
23620
|
+
);
|
|
23621
|
+
const hasMore = rows.length > limit;
|
|
23622
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23623
|
+
const last = page[page.length - 1];
|
|
23624
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
23625
|
+
this.db,
|
|
23626
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
23627
|
+
);
|
|
23628
|
+
return {
|
|
23629
|
+
items: page.map((r) => ({
|
|
23630
|
+
id: r.id,
|
|
23631
|
+
pointerId: r.pointer_id,
|
|
23632
|
+
at: new Date(r.at).toISOString(),
|
|
23633
|
+
target: r.target,
|
|
23634
|
+
reason: r.reason,
|
|
23635
|
+
outcome: r.outcome,
|
|
23636
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
23637
|
+
pointerCount: r.pointer_count
|
|
23638
|
+
})),
|
|
23639
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
23640
|
+
hiddenBatched
|
|
23641
|
+
};
|
|
23642
|
+
}
|
|
23643
|
+
countEntries() {
|
|
23644
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
23645
|
+
}
|
|
23646
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23647
|
+
countReused() {
|
|
23648
|
+
return countScalar(
|
|
23649
|
+
this.db,
|
|
23650
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23651
|
+
);
|
|
23652
|
+
}
|
|
23653
|
+
};
|
|
23654
|
+
|
|
23655
|
+
// ../../packages/persistence/src/repositories/security.ts
|
|
23656
|
+
var DAY_MS4 = 864e5;
|
|
23657
|
+
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
23658
|
+
var ACTION_TO_KIND = {
|
|
23659
|
+
block: "blocked",
|
|
23660
|
+
redact: "redacted",
|
|
23661
|
+
warn: "warned"
|
|
23662
|
+
};
|
|
23663
|
+
var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
23664
|
+
var SCAN_COVERAGE = [
|
|
23665
|
+
{ provider: "claudecode", coverage: 100, supported: true },
|
|
23666
|
+
{ provider: "cursor", coverage: 0, supported: false },
|
|
23667
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23668
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23669
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
23670
|
+
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
23671
|
+
{ provider: "copilot", coverage: 0, supported: false },
|
|
23672
|
+
{ provider: "api", coverage: 0, supported: false }
|
|
23673
|
+
];
|
|
23674
|
+
var GRANULARITY = {
|
|
23675
|
+
"7d": "day",
|
|
23676
|
+
"30d": "day",
|
|
23677
|
+
"3m": "week",
|
|
23678
|
+
"6m": "week"
|
|
23679
|
+
};
|
|
23680
|
+
function granularityFor(range) {
|
|
23681
|
+
return GRANULARITY[range];
|
|
23682
|
+
}
|
|
23683
|
+
function startOfUtcDay2(ms) {
|
|
23684
|
+
return Math.floor(ms / DAY_MS4) * DAY_MS4;
|
|
23685
|
+
}
|
|
23686
|
+
function toUtcDateString(ms) {
|
|
23687
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
23688
|
+
}
|
|
23689
|
+
function isTimeseriesSeverity(s) {
|
|
23690
|
+
return s === "critical" || s === "high" || s === "medium";
|
|
23691
|
+
}
|
|
23692
|
+
var SqliteSecurityRepository = class {
|
|
23693
|
+
constructor(db, now = () => Date.now()) {
|
|
23694
|
+
this.db = db;
|
|
23695
|
+
this.now = now;
|
|
23696
|
+
}
|
|
23697
|
+
db;
|
|
23698
|
+
now;
|
|
23699
|
+
// Status-aware: every finding is classified by origin (its parent event's
|
|
23700
|
+
// kind — 'code_change' is at-rest, everything else is in-flight) and, for
|
|
23701
|
+
// at-rest findings, whether its finding_key's LATEST finding_resolution row
|
|
23702
|
+
// (max created_at, not "does ANY row exist") has status 'resolved' — mirrors
|
|
23703
|
+
// SqliteResolutionsRepository's LATEST-RESOLUTION-WINS convention. "Any row
|
|
23704
|
+
// exists" would let a fixed-at-source key that is later redetected (the same
|
|
23705
|
+
// secret re-added) stay silently "caught" forever under its stale resolved
|
|
23706
|
+
// row; latest-wins lets the scanner supersede it with a fresh status:'open'
|
|
23707
|
+
// row (see scan.ts's reopenRedetectedFindings) so the invariant holds: a
|
|
23708
|
+
// finding_key present in the current scan is OPEN, regardless of history.
|
|
23709
|
+
// In-flight findings are born caught (enforcement already ran); at-rest
|
|
23710
|
+
// findings are caught only once their latest disposition is resolved,
|
|
23711
|
+
// otherwise they are open-at-rest.
|
|
23712
|
+
//
|
|
23713
|
+
// NOTE for future manual-resolution writers: only latest status
|
|
23714
|
+
// 'resolved' counts as caught above. When acknowledged/dismissed/
|
|
23715
|
+
// false-positive manual dispositions land, this must keep filtering by
|
|
23716
|
+
// status/method — 'acknowledged' is accepted risk, not a fix, and must NOT
|
|
23717
|
+
// be bucketed as caught alongside 'resolved'.
|
|
23718
|
+
//
|
|
23719
|
+
// Legacy at-rest findings from pre-branch scans carry finding_key = NULL —
|
|
23720
|
+
// the resolution lifecycle is keyed by finding_key, so it can never attach a
|
|
23721
|
+
// disposition to (or clear) one of these on re-scan. They are excluded from
|
|
23722
|
+
// both caught and openAtRest (untracked, not "needs remediation forever"),
|
|
23723
|
+
// but still counted in total/count below — this keeps this predicate
|
|
23724
|
+
// consistent with SqliteResolutionsRepository.openAtRestKeysForPath, which
|
|
23725
|
+
// already filters `finding_key IS NOT NULL`.
|
|
23726
|
+
//
|
|
23727
|
+
// One GROUP BY aggregate: the result set stays O(distinct severities) no
|
|
23728
|
+
// matter how many findings the store has accumulated (this backs `aka stats`
|
|
23729
|
+
// and the dashboard severity card, both hot paths on a table that only
|
|
23730
|
+
// grows). The latest-resolution status comes from the shared derived-table
|
|
23731
|
+
// fragment (see resolution-sql.ts) rather than a correlated subquery per
|
|
23732
|
+
// finding — its rn = 1 filter is also what makes the LEFT JOIN safe against
|
|
23733
|
+
// double-counting a key that accumulated several append-only rows.
|
|
23734
|
+
severitySummary() {
|
|
23735
|
+
const rows = allRows(
|
|
23736
|
+
this.db.prepare(
|
|
23737
|
+
`SELECT d.severity AS severity,
|
|
23738
|
+
COUNT(*) AS count,
|
|
23739
|
+
SUM(CASE
|
|
23740
|
+
WHEN e.event_type != 'code_change' THEN 1
|
|
23741
|
+
WHEN f.finding_key IS NULL THEN 0
|
|
23742
|
+
WHEN latest.status = 'resolved' THEN 1
|
|
23743
|
+
ELSE 0
|
|
23744
|
+
END) AS caught,
|
|
23745
|
+
SUM(CASE
|
|
23746
|
+
WHEN e.event_type = 'code_change'
|
|
23747
|
+
AND f.finding_key IS NOT NULL
|
|
23748
|
+
AND (latest.status IS NULL OR latest.status != 'resolved') THEN 1
|
|
23749
|
+
ELSE 0
|
|
23750
|
+
END) AS open_at_rest
|
|
23751
|
+
FROM inspection_findings f
|
|
23752
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
23753
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
23754
|
+
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
23755
|
+
ON latest.finding_key = f.finding_key
|
|
23756
|
+
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
23757
|
+
GROUP BY d.severity`
|
|
23758
|
+
)
|
|
23759
|
+
);
|
|
23760
|
+
const byRow = new Map(rows.map((r) => [r.severity, r]));
|
|
23761
|
+
const bySeverity = SEVERITIES.map((severity) => ({
|
|
23762
|
+
severity,
|
|
23763
|
+
count: byRow.get(severity)?.count ?? 0,
|
|
22335
23764
|
caught: byRow.get(severity)?.caught ?? 0,
|
|
22336
23765
|
openAtRest: byRow.get(severity)?.open_at_rest ?? 0
|
|
22337
23766
|
}));
|
|
@@ -22570,7 +23999,7 @@ var SqliteSecurityRepository = class {
|
|
|
22570
23999
|
};
|
|
22571
24000
|
|
|
22572
24001
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22573
|
-
import { randomUUID as
|
|
24002
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
22574
24003
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22575
24004
|
var IN_CHUNK = 500;
|
|
22576
24005
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22826,7 +24255,7 @@ var SqliteSharesRepository = class {
|
|
|
22826
24255
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22827
24256
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22828
24257
|
).run({
|
|
22829
|
-
id:
|
|
24258
|
+
id: randomUUID9(),
|
|
22830
24259
|
destinationId,
|
|
22831
24260
|
host: dest.host,
|
|
22832
24261
|
decision,
|
|
@@ -22975,7 +24404,7 @@ var SqliteSharesRepository = class {
|
|
|
22975
24404
|
let destinationId = destIds.get(hit.host);
|
|
22976
24405
|
if (destinationId === void 0) {
|
|
22977
24406
|
destStmt.run({
|
|
22978
|
-
id:
|
|
24407
|
+
id: randomUUID9(),
|
|
22979
24408
|
kind: hit.kind,
|
|
22980
24409
|
name: hit.name,
|
|
22981
24410
|
host: hit.host,
|
|
@@ -22991,7 +24420,7 @@ var SqliteSharesRepository = class {
|
|
|
22991
24420
|
let endpointId = endpointIds.get(endpointKey);
|
|
22992
24421
|
if (endpointId === void 0) {
|
|
22993
24422
|
endpointStmt.run({
|
|
22994
|
-
id:
|
|
24423
|
+
id: randomUUID9(),
|
|
22995
24424
|
destinationId,
|
|
22996
24425
|
method: hit.method,
|
|
22997
24426
|
transport: hit.transport,
|
|
@@ -23004,7 +24433,7 @@ var SqliteSharesRepository = class {
|
|
|
23004
24433
|
endpointIds.set(endpointKey, endpointId);
|
|
23005
24434
|
}
|
|
23006
24435
|
siteStmt.run({
|
|
23007
|
-
id:
|
|
24436
|
+
id: randomUUID9(),
|
|
23008
24437
|
endpointId,
|
|
23009
24438
|
project: input.project,
|
|
23010
24439
|
projectKey: input.projectKey,
|
|
@@ -23369,6 +24798,9 @@ function purgeSampleData(db) {
|
|
|
23369
24798
|
}
|
|
23370
24799
|
|
|
23371
24800
|
// ../../packages/persistence/src/database.ts
|
|
24801
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24802
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24803
|
+
);
|
|
23372
24804
|
function linkHost(input, hostId) {
|
|
23373
24805
|
return hostId ? { ...input, hostId } : input;
|
|
23374
24806
|
}
|
|
@@ -23390,21 +24822,34 @@ function openWithPragmas(file2) {
|
|
|
23390
24822
|
}
|
|
23391
24823
|
return db;
|
|
23392
24824
|
}
|
|
23393
|
-
function backupLegacyStore(file2) {
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
|
|
24825
|
+
function backupLegacyStore(db, file2) {
|
|
24826
|
+
reapStalePartials(file2);
|
|
24827
|
+
const backup = backupPath(file2, "legacy");
|
|
24828
|
+
let snapshotted = false;
|
|
24829
|
+
let snapshotError;
|
|
24830
|
+
try {
|
|
24831
|
+
snapshotStore(db, backup);
|
|
24832
|
+
snapshotted = true;
|
|
24833
|
+
} catch (error51) {
|
|
24834
|
+
snapshotError = error51;
|
|
24835
|
+
} finally {
|
|
24836
|
+
db.close();
|
|
24837
|
+
}
|
|
24838
|
+
if (!snapshotted) {
|
|
24839
|
+
akaWarn(
|
|
24840
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24841
|
+
);
|
|
24842
|
+
moveStoreAside(file2, backup);
|
|
24843
|
+
return backup;
|
|
23399
24844
|
}
|
|
24845
|
+
discardStore(file2, backup);
|
|
23400
24846
|
return backup;
|
|
23401
24847
|
}
|
|
23402
24848
|
function openAndInitialize(file2) {
|
|
23403
24849
|
let db = openWithPragmas(file2);
|
|
23404
24850
|
try {
|
|
23405
24851
|
if (isForeignSqliteLineage(db)) {
|
|
23406
|
-
db
|
|
23407
|
-
const backup = backupLegacyStore(file2);
|
|
24852
|
+
const backup = backupLegacyStore(db, file2);
|
|
23408
24853
|
db = openWithPragmas(file2);
|
|
23409
24854
|
akaWarn(
|
|
23410
24855
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23420,6 +24865,7 @@ function openAndInitialize(file2) {
|
|
|
23420
24865
|
policies,
|
|
23421
24866
|
installedPacks,
|
|
23422
24867
|
scanLedger: new SqliteScanLedgerRepository(db),
|
|
24868
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23423
24869
|
exceptions: new SqliteExceptionsRepository(db),
|
|
23424
24870
|
resolutions: new SqliteResolutionsRepository(db),
|
|
23425
24871
|
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
@@ -23447,7 +24893,7 @@ function openAndInitialize(file2) {
|
|
|
23447
24893
|
}
|
|
23448
24894
|
function openLocalDatabase(dir) {
|
|
23449
24895
|
ensureDataDirSync(dir);
|
|
23450
|
-
const file2 =
|
|
24896
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23451
24897
|
const {
|
|
23452
24898
|
db,
|
|
23453
24899
|
events,
|
|
@@ -23455,6 +24901,7 @@ function openLocalDatabase(dir) {
|
|
|
23455
24901
|
policies,
|
|
23456
24902
|
installedPacks,
|
|
23457
24903
|
scanLedger,
|
|
24904
|
+
secretVault,
|
|
23458
24905
|
exceptions,
|
|
23459
24906
|
resolutions,
|
|
23460
24907
|
ruleProbeCache,
|
|
@@ -23563,7 +25010,7 @@ function openLocalDatabase(dir) {
|
|
|
23563
25010
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23564
25011
|
if (!definitionId) continue;
|
|
23565
25012
|
inspectionFindings.insertFinding({
|
|
23566
|
-
id:
|
|
25013
|
+
id: randomUUID10(),
|
|
23567
25014
|
auditEventId: record2.scanEvent.id,
|
|
23568
25015
|
inspectionDefinitionId: definitionId,
|
|
23569
25016
|
span: finding.span,
|
|
@@ -23640,6 +25087,7 @@ function openLocalDatabase(dir) {
|
|
|
23640
25087
|
policies,
|
|
23641
25088
|
installedPacks,
|
|
23642
25089
|
scanLedger,
|
|
25090
|
+
secretVault,
|
|
23643
25091
|
exceptions,
|
|
23644
25092
|
resolutions,
|
|
23645
25093
|
ruleProbeCache,
|
|
@@ -23668,35 +25116,51 @@ function openLocalDatabase(dir) {
|
|
|
23668
25116
|
transaction,
|
|
23669
25117
|
close: () => {
|
|
23670
25118
|
db.close();
|
|
23671
|
-
}
|
|
25119
|
+
},
|
|
25120
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25121
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
23672
25122
|
};
|
|
23673
25123
|
}
|
|
23674
25124
|
|
|
25125
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25126
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25127
|
+
import {
|
|
25128
|
+
closeSync,
|
|
25129
|
+
existsSync as existsSync2,
|
|
25130
|
+
openSync,
|
|
25131
|
+
readFileSync,
|
|
25132
|
+
rmSync as rmSync3,
|
|
25133
|
+
statSync as statSync2,
|
|
25134
|
+
writeFileSync as writeFileSync2
|
|
25135
|
+
} from "fs";
|
|
25136
|
+
import { hostname as hostname3 } from "os";
|
|
25137
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25138
|
+
|
|
23675
25139
|
// ../../packages/persistence/src/finding-key.ts
|
|
23676
25140
|
import { createHash as createHash3 } from "crypto";
|
|
23677
25141
|
|
|
23678
25142
|
// ../../packages/persistence/src/fingerprint.ts
|
|
23679
25143
|
import { createHmac, randomBytes } from "crypto";
|
|
23680
|
-
import { existsSync as
|
|
23681
|
-
import { join as
|
|
25144
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25145
|
+
import { join as join3 } from "path";
|
|
23682
25146
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23683
25147
|
|
|
23684
25148
|
// ../../packages/persistence/src/local-layout.ts
|
|
23685
25149
|
import { renameSync as renameSync3 } from "fs";
|
|
23686
25150
|
import { mkdir } from "fs/promises";
|
|
23687
25151
|
import { homedir } from "os";
|
|
23688
|
-
import { join as
|
|
25152
|
+
import { join as join4 } from "path";
|
|
23689
25153
|
function defaultDataDir() {
|
|
23690
|
-
return
|
|
25154
|
+
return join4(homedir(), ".aka");
|
|
23691
25155
|
}
|
|
23692
25156
|
function settingsDir(base = defaultDataDir()) {
|
|
23693
|
-
return
|
|
25157
|
+
return join4(base, "settings");
|
|
23694
25158
|
}
|
|
23695
25159
|
function dataDir(base = defaultDataDir()) {
|
|
23696
|
-
return
|
|
25160
|
+
return join4(base, "data");
|
|
23697
25161
|
}
|
|
23698
25162
|
function dbPath(base = defaultDataDir()) {
|
|
23699
|
-
return
|
|
25163
|
+
return join4(dataDir(base), "aka.db");
|
|
23700
25164
|
}
|
|
23701
25165
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
23702
25166
|
ensureDataDirSync(dir);
|
|
@@ -23709,8 +25173,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23709
25173
|
for (const { name, dest } of moves) {
|
|
23710
25174
|
try {
|
|
23711
25175
|
ensureDataDirSync(dest);
|
|
23712
|
-
const moved =
|
|
23713
|
-
renameSync3(
|
|
25176
|
+
const moved = join4(dest, name);
|
|
25177
|
+
renameSync3(join4(base, name), moved);
|
|
23714
25178
|
tightenFile(moved);
|
|
23715
25179
|
} catch {
|
|
23716
25180
|
}
|
|
@@ -23718,10 +25182,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23718
25182
|
}
|
|
23719
25183
|
|
|
23720
25184
|
// ../../packages/persistence/src/settings.ts
|
|
23721
|
-
import { readFileSync as
|
|
23722
|
-
import { join as
|
|
25185
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25186
|
+
import { join as join5 } from "path";
|
|
25187
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
23723
25188
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
23724
|
-
const record2 = readJson(
|
|
25189
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
23725
25190
|
if (!record2) return defaultWorkspaceSettings();
|
|
23726
25191
|
try {
|
|
23727
25192
|
return WorkspaceSettings.parse(record2);
|
|
@@ -23732,20 +25197,46 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
23732
25197
|
function readJson(file2) {
|
|
23733
25198
|
let text;
|
|
23734
25199
|
try {
|
|
23735
|
-
text =
|
|
25200
|
+
text = readFileSync3(file2, "utf8");
|
|
23736
25201
|
} catch {
|
|
23737
25202
|
return null;
|
|
23738
25203
|
}
|
|
23739
25204
|
return parseJsonObject(text) ?? null;
|
|
23740
25205
|
}
|
|
23741
25206
|
|
|
25207
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
25208
|
+
import {
|
|
25209
|
+
createCipheriv,
|
|
25210
|
+
createDecipheriv,
|
|
25211
|
+
createHmac as createHmac2,
|
|
25212
|
+
hkdfSync,
|
|
25213
|
+
timingSafeEqual
|
|
25214
|
+
} from "crypto";
|
|
25215
|
+
|
|
25216
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25217
|
+
import { execFileSync } from "child_process";
|
|
25218
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
25219
|
+
import {
|
|
25220
|
+
chmodSync as chmodSync2,
|
|
25221
|
+
mkdirSync as mkdirSync2,
|
|
25222
|
+
readFileSync as readFileSync4,
|
|
25223
|
+
renameSync as renameSync4,
|
|
25224
|
+
rmSync as rmSync4,
|
|
25225
|
+
statSync as statSync3,
|
|
25226
|
+
writeFileSync as writeFileSync3
|
|
25227
|
+
} from "fs";
|
|
25228
|
+
import { join as join6 } from "path";
|
|
25229
|
+
|
|
25230
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
25231
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
25232
|
+
|
|
23742
25233
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
23743
|
-
import { existsSync as
|
|
23744
|
-
import { join as
|
|
25234
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25235
|
+
import { join as join7 } from "path";
|
|
23745
25236
|
|
|
23746
25237
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23747
|
-
import { existsSync as
|
|
23748
|
-
import { join as
|
|
25238
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25239
|
+
import { join as join8 } from "path";
|
|
23749
25240
|
|
|
23750
25241
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
23751
25242
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -23796,11 +25287,11 @@ function resolveProvider() {
|
|
|
23796
25287
|
}
|
|
23797
25288
|
|
|
23798
25289
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23799
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25290
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
23800
25291
|
try {
|
|
23801
25292
|
ensureLayoutDirSync(base);
|
|
23802
|
-
const settingsFile =
|
|
23803
|
-
if (
|
|
25293
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25294
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
23804
25295
|
} catch {
|
|
23805
25296
|
}
|
|
23806
25297
|
migrateLegacyLayout(base);
|
|
@@ -23811,21 +25302,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
23811
25302
|
dbPath: dbPath(base),
|
|
23812
25303
|
settingsDir: settingsDir(base),
|
|
23813
25304
|
onboarded: settings.onboardedAt != null,
|
|
23814
|
-
provider: resolveProviderSafe()
|
|
25305
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
23815
25306
|
};
|
|
23816
25307
|
}
|
|
23817
|
-
function resolveProviderSafe() {
|
|
25308
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
23818
25309
|
try {
|
|
23819
|
-
return
|
|
25310
|
+
return resolveProviderFn();
|
|
23820
25311
|
} catch {
|
|
23821
25312
|
return { provider: "anthropic" };
|
|
23822
25313
|
}
|
|
23823
25314
|
}
|
|
23824
25315
|
|
|
23825
25316
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23826
|
-
import { readdirSync, readFileSync as
|
|
25317
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
23827
25318
|
import { homedir as homedir2 } from "os";
|
|
23828
|
-
import { basename as
|
|
25319
|
+
import { basename as basename3, join as join10 } from "path";
|
|
23829
25320
|
|
|
23830
25321
|
// ../../packages/detections/src/egress/registry.ts
|
|
23831
25322
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -24613,12 +26104,12 @@ function redact(text, findings) {
|
|
|
24613
26104
|
const regions = [];
|
|
24614
26105
|
for (const f of sorted) {
|
|
24615
26106
|
const rank = SEVERITY_RANK2[f.severity];
|
|
24616
|
-
const
|
|
24617
|
-
if (
|
|
24618
|
-
|
|
24619
|
-
if (rank >
|
|
24620
|
-
|
|
24621
|
-
|
|
26107
|
+
const open2 = regions[regions.length - 1];
|
|
26108
|
+
if (open2 && f.span.start < open2.end) {
|
|
26109
|
+
open2.end = Math.max(open2.end, f.span.end);
|
|
26110
|
+
if (rank > open2.rank) {
|
|
26111
|
+
open2.rank = rank;
|
|
26112
|
+
open2.category = f.category;
|
|
24622
26113
|
}
|
|
24623
26114
|
} else {
|
|
24624
26115
|
regions.push({ start: f.span.start, end: f.span.end, category: f.category, rank });
|
|
@@ -24647,6 +26138,24 @@ function maskMatch(raw) {
|
|
|
24647
26138
|
return `${raw.charAt(0)}${"*".repeat(6)}${raw.charAt(raw.length - 1)}`;
|
|
24648
26139
|
}
|
|
24649
26140
|
|
|
26141
|
+
// ../../packages/detections/src/pointer-shield.ts
|
|
26142
|
+
function shieldPointers(text) {
|
|
26143
|
+
const spans = [];
|
|
26144
|
+
let out = null;
|
|
26145
|
+
for (const match of text.matchAll(pointerTokenScanner())) {
|
|
26146
|
+
spans.push({ start: match.index, end: match.index + match[0].length });
|
|
26147
|
+
out ??= text;
|
|
26148
|
+
out = out.slice(0, match.index) + " ".repeat(match[0].length) + out.slice(match.index + match[0].length);
|
|
26149
|
+
}
|
|
26150
|
+
return { text: out ?? text, spans };
|
|
26151
|
+
}
|
|
26152
|
+
function dropShieldedFindings(findings, spans) {
|
|
26153
|
+
if (spans.length === 0) return findings;
|
|
26154
|
+
return findings.filter(
|
|
26155
|
+
(finding) => !spans.some((s) => finding.span.start < s.end && finding.span.end > s.start)
|
|
26156
|
+
);
|
|
26157
|
+
}
|
|
26158
|
+
|
|
24650
26159
|
// ../../packages/detections/src/posture/config-posture.ts
|
|
24651
26160
|
var RULE_VERSION = "1";
|
|
24652
26161
|
var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -26383,7 +27892,7 @@ var gcp_service_account_default = {
|
|
|
26383
27892
|
severity: "critical",
|
|
26384
27893
|
matcher: {
|
|
26385
27894
|
type: "regex",
|
|
26386
|
-
pattern: "
|
|
27895
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
26387
27896
|
flags: "g"
|
|
26388
27897
|
},
|
|
26389
27898
|
examples: [
|
|
@@ -26768,7 +28277,8 @@ function scanText(text, ruleVersions) {
|
|
|
26768
28277
|
if (!ensureBundledPacks()) return { masked: "[REDACTED]", findings: [] };
|
|
26769
28278
|
try {
|
|
26770
28279
|
const rules = getLoadedRules();
|
|
26771
|
-
const
|
|
28280
|
+
const shielded = shieldPointers(text);
|
|
28281
|
+
const matches = dropShieldedFindings(scan(shielded.text, rules), shielded.spans);
|
|
26772
28282
|
if (matches.length === 0) return { masked: text, findings: [] };
|
|
26773
28283
|
const byId = new Map(rules.map((r) => [r.id, r]));
|
|
26774
28284
|
const findings = matches.map((m) => {
|
|
@@ -26794,22 +28304,27 @@ function maskText(text) {
|
|
|
26794
28304
|
}
|
|
26795
28305
|
|
|
26796
28306
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
26797
|
-
import { existsSync as
|
|
26798
|
-
import { basename, dirname, isAbsolute, join as
|
|
28307
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
28308
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
26799
28309
|
|
|
26800
28310
|
// ../../packages/plugin-sdk/src/events.ts
|
|
26801
|
-
import { createHash as createHash4, randomUUID as
|
|
28311
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28312
|
+
|
|
28313
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
28314
|
+
import { existsSync as existsSync7 } from "fs";
|
|
28315
|
+
import { fileURLToPath } from "url";
|
|
28316
|
+
import { Worker } from "worker_threads";
|
|
26802
28317
|
|
|
26803
28318
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
26804
|
-
import { arch, hostname as
|
|
28319
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
26805
28320
|
|
|
26806
28321
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
26807
|
-
import { mkdirSync as
|
|
26808
|
-
import { join as
|
|
28322
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
28323
|
+
import { join as join11 } from "path";
|
|
26809
28324
|
|
|
26810
28325
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
26811
|
-
import { readdirSync as
|
|
26812
|
-
import { basename as
|
|
28326
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
28327
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
26813
28328
|
|
|
26814
28329
|
// ../../packages/plugin-sdk/src/posture.ts
|
|
26815
28330
|
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
@@ -26823,8 +28338,34 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
26823
28338
|
|
|
26824
28339
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
26825
28340
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
26826
|
-
import { existsSync as
|
|
26827
|
-
import { basename as
|
|
28341
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
28342
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
28343
|
+
|
|
28344
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
28345
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
28346
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28347
|
+
return v;
|
|
28348
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28349
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
28350
|
+
if (typeof v !== "string") return false;
|
|
28351
|
+
const normalized = v.trim().toLowerCase();
|
|
28352
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
28353
|
+
}, external_exports.boolean()).catch(false);
|
|
28354
|
+
var antigravityProviderEnvShape = {
|
|
28355
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
28356
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
28357
|
+
};
|
|
28358
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
28359
|
+
|
|
28360
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
28361
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
28362
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28363
|
+
return v;
|
|
28364
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28365
|
+
var codexProviderEnvShape = {
|
|
28366
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
28367
|
+
};
|
|
28368
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
26828
28369
|
|
|
26829
28370
|
// ../../packages/plugin-sdk/src/raw-egress.ts
|
|
26830
28371
|
var RawEgressError = class extends Error {
|
|
@@ -26875,7 +28416,7 @@ function assertRawFree(text, rawValues) {
|
|
|
26875
28416
|
}
|
|
26876
28417
|
|
|
26877
28418
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
26878
|
-
import { randomUUID as
|
|
28419
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
26879
28420
|
|
|
26880
28421
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
26881
28422
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -26915,128 +28456,72 @@ async function applySetupTriageSuppressions(entries, writer, opts) {
|
|
|
26915
28456
|
}
|
|
26916
28457
|
|
|
26917
28458
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
26918
|
-
import { mkdirSync as
|
|
26919
|
-
import { join as
|
|
28459
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
28460
|
+
import { join as join13 } from "path";
|
|
26920
28461
|
|
|
26921
|
-
// src/
|
|
26922
|
-
import {
|
|
26923
|
-
import {
|
|
26924
|
-
var COMMAND_NAMESPACE = "aka";
|
|
26925
|
-
var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
|
|
26926
|
-
function readRegisteredCommands() {
|
|
26927
|
-
return readdirSync4(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
26928
|
-
}
|
|
26929
|
-
function selectRegisteredCommands(curated, registry2) {
|
|
26930
|
-
const registered = new Set(registry2);
|
|
26931
|
-
const missing = curated.filter((c) => !registered.has(c));
|
|
26932
|
-
if (missing.length > 0) {
|
|
26933
|
-
throw new Error(
|
|
26934
|
-
`Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
|
|
26935
|
-
);
|
|
26936
|
-
}
|
|
26937
|
-
return [...curated];
|
|
26938
|
-
}
|
|
28462
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
28463
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
28464
|
+
import { join as join14 } from "path";
|
|
26939
28465
|
|
|
26940
|
-
//
|
|
26941
|
-
|
|
26942
|
-
|
|
26943
|
-
|
|
26944
|
-
return `${FRAME_JSON_BEGIN}
|
|
26945
|
-
${JSON.stringify(payload)}
|
|
26946
|
-
${FRAME_JSON_END}
|
|
26947
|
-
`;
|
|
28466
|
+
// ../../packages/setup-wizard/src/triage/dedupe.ts
|
|
28467
|
+
function dedupeKey(hit) {
|
|
28468
|
+
if (hit.valueFingerprint === void 0) return void 0;
|
|
28469
|
+
return `${hit.ruleId}\u2588${hit.valueFingerprint}`;
|
|
26948
28470
|
}
|
|
26949
|
-
|
|
26950
|
-
|
|
26951
|
-
|
|
26952
|
-
|
|
26953
|
-
|
|
26954
|
-
|
|
26955
|
-
|
|
26956
|
-
|
|
26957
|
-
|
|
28471
|
+
function dedupeForJudge(hits) {
|
|
28472
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28473
|
+
const out = [];
|
|
28474
|
+
for (const h of hits) {
|
|
28475
|
+
const key = dedupeKey(h);
|
|
28476
|
+
if (key === void 0) {
|
|
28477
|
+
out.push(h);
|
|
28478
|
+
continue;
|
|
28479
|
+
}
|
|
28480
|
+
if (seen.has(key)) continue;
|
|
28481
|
+
seen.add(key);
|
|
28482
|
+
out.push(h);
|
|
28483
|
+
}
|
|
28484
|
+
return out;
|
|
26958
28485
|
}
|
|
26959
28486
|
|
|
26960
|
-
// src/
|
|
26961
|
-
|
|
26962
|
-
|
|
26963
|
-
|
|
26964
|
-
|
|
26965
|
-
|
|
26966
|
-
|
|
26967
|
-
|
|
26968
|
-
|
|
26969
|
-
|
|
26970
|
-
}
|
|
26971
|
-
var fg = (hex3) => (text) => {
|
|
26972
|
-
const r = Number.parseInt(hex3.slice(1, 3), 16);
|
|
26973
|
-
const g = Number.parseInt(hex3.slice(3, 5), 16);
|
|
26974
|
-
const b = Number.parseInt(hex3.slice(5, 7), 16);
|
|
26975
|
-
return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
|
|
26976
|
-
};
|
|
26977
|
-
var paint = {
|
|
26978
|
-
brand: fg("#33e6c6"),
|
|
26979
|
-
// --color-brand · ▸▸ AKA wordmark (accent text)
|
|
26980
|
-
dim: fg("#838995"),
|
|
26981
|
-
// --color-text-3 · separators · "/100" · the "unreviewed" label
|
|
26982
|
-
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
26983
|
-
// the health score number
|
|
26984
|
-
ok: fg("#0db15f"),
|
|
26985
|
-
// --color-ok · healthy ● dot
|
|
26986
|
-
critical: fg("#e63448"),
|
|
26987
|
-
// --color-sev-critical · ■ and the open-findings flag
|
|
26988
|
-
high: fg("#e97a0a"),
|
|
26989
|
-
// --color-sev-high · ■ and the mid-health dot
|
|
26990
|
-
medium: fg("#f7bd00"),
|
|
26991
|
-
// --color-sev-medium · ■
|
|
26992
|
-
low: fg("#0581d4")
|
|
26993
|
-
// --color-sev-low · ■ (azure blue, not purple)
|
|
26994
|
-
};
|
|
26995
|
-
function padEnd(text, width) {
|
|
26996
|
-
const pad = width - visibleLength(text);
|
|
26997
|
-
return pad > 0 ? text + " ".repeat(pad) : text;
|
|
26998
|
-
}
|
|
26999
|
-
function indent(text, spaces = 2) {
|
|
27000
|
-
const pad = " ".repeat(spaces);
|
|
27001
|
-
return text.split("\n").map((line) => pad + line).join("\n");
|
|
27002
|
-
}
|
|
27003
|
-
function table(headers, rows, opts = {}) {
|
|
27004
|
-
const gap = opts.gap ?? 3;
|
|
27005
|
-
const widths = headers.map(
|
|
27006
|
-
(h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
|
|
27007
|
-
);
|
|
27008
|
-
const sep5 = " ".repeat(gap);
|
|
27009
|
-
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
|
|
27010
|
-
const headerLine = fmt(headers.map((h) => h.toUpperCase()));
|
|
27011
|
-
if (opts.rowSep === true) {
|
|
27012
|
-
const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
27013
|
-
const rule = "\u2500".repeat(fullWidth);
|
|
27014
|
-
const body = [];
|
|
27015
|
-
rows.forEach((row, i) => {
|
|
27016
|
-
if (i > 0) body.push(rule);
|
|
27017
|
-
body.push(fmt(row));
|
|
27018
|
-
});
|
|
27019
|
-
return [headerLine, rule, ...body].join("\n");
|
|
28487
|
+
// ../../packages/setup-wizard/src/triage/false-positive-patterns.ts
|
|
28488
|
+
function deriveFalsePositivePatterns(hits, rec, plan) {
|
|
28489
|
+
const hitById = new Map(hits.filter((h) => h.id !== void 0).map((h) => [h.id, h]));
|
|
28490
|
+
const seenCategory = /* @__PURE__ */ new Set();
|
|
28491
|
+
const markedIds = /* @__PURE__ */ new Set();
|
|
28492
|
+
for (const c of rec.perCategory) {
|
|
28493
|
+
if (seenCategory.has(c.category)) continue;
|
|
28494
|
+
seenCategory.add(c.category);
|
|
28495
|
+
if (plan.posture[c.category] === void 0) continue;
|
|
28496
|
+
for (const id of c.fpIds) markedIds.add(id);
|
|
27020
28497
|
}
|
|
27021
|
-
const
|
|
27022
|
-
|
|
27023
|
-
|
|
27024
|
-
|
|
27025
|
-
|
|
27026
|
-
|
|
27027
|
-
|
|
27028
|
-
|
|
27029
|
-
|
|
27030
|
-
|
|
28498
|
+
const groups = /* @__PURE__ */ new Map();
|
|
28499
|
+
for (const id of markedIds) {
|
|
28500
|
+
const h = hitById.get(id);
|
|
28501
|
+
if (h === void 0) continue;
|
|
28502
|
+
const pattern = safeMaskedMatch(h.rawMatch);
|
|
28503
|
+
const group = groups.get(pattern) ?? { count: 0, values: [] };
|
|
28504
|
+
group.count += 1;
|
|
28505
|
+
if (h.valueFingerprint !== void 0 && h.keyVersion !== void 0) {
|
|
28506
|
+
group.values.push({
|
|
28507
|
+
ruleId: h.ruleId,
|
|
28508
|
+
category: h.category,
|
|
28509
|
+
valueFingerprint: h.valueFingerprint,
|
|
28510
|
+
keyVersion: h.keyVersion
|
|
28511
|
+
});
|
|
28512
|
+
}
|
|
28513
|
+
groups.set(pattern, group);
|
|
28514
|
+
}
|
|
28515
|
+
return [...groups.entries()].filter(([, g]) => g.values.length > 0).map(([pattern, g]) => ({ pattern, count: g.count, values: g.values }));
|
|
27031
28516
|
}
|
|
27032
28517
|
|
|
27033
|
-
// src/triage/gate-display.ts
|
|
27034
|
-
function findContext(entry,
|
|
27035
|
-
const byFingerprint =
|
|
28518
|
+
// ../../packages/setup-wizard/src/triage/gate-display.ts
|
|
28519
|
+
function findContext(entry, join18) {
|
|
28520
|
+
const byFingerprint = join18.find(
|
|
27036
28521
|
(j) => j.valueFingerprint !== void 0 && j.valueFingerprint === entry.valueFingerprint
|
|
27037
28522
|
);
|
|
27038
28523
|
if (byFingerprint) return byFingerprint.maskedContext;
|
|
27039
|
-
const byRuleAndMask =
|
|
28524
|
+
const byRuleAndMask = join18.find(
|
|
27040
28525
|
(j) => j.ruleId === entry.ruleId && j.maskedMatch === entry.maskedValue
|
|
27041
28526
|
);
|
|
27042
28527
|
return byRuleAndMask?.maskedContext;
|
|
@@ -27107,13 +28592,13 @@ function renderShowcase(showcase) {
|
|
|
27107
28592
|
|
|
27108
28593
|
${blocks.join("\n\n")}`;
|
|
27109
28594
|
}
|
|
27110
|
-
function renderSuppressionGate(entries,
|
|
28595
|
+
function renderSuppressionGate(entries, join18) {
|
|
27111
28596
|
if (entries.length === 0) {
|
|
27112
28597
|
return "No false-positive suppressions to confirm \u2014 nothing will be written.";
|
|
27113
28598
|
}
|
|
27114
28599
|
const header = entries.length === 1 ? "This looks like a false positive \u2014 take a look before I suppress it:" : `These ${String(entries.length)} look like false positives \u2014 take a look before I suppress them:`;
|
|
27115
28600
|
const blocks = entries.map((entry, i) => {
|
|
27116
|
-
const context = findContext(entry,
|
|
28601
|
+
const context = findContext(entry, join18);
|
|
27117
28602
|
const lines = [
|
|
27118
28603
|
`${String(i + 1)}. ${entry.ruleId} [${entry.category}]`,
|
|
27119
28604
|
` value: ${entry.maskedValue}`,
|
|
@@ -27127,153 +28612,7 @@ function renderSuppressionGate(entries, join15) {
|
|
|
27127
28612
|
${blocks.join("\n\n")}`;
|
|
27128
28613
|
}
|
|
27129
28614
|
|
|
27130
|
-
// src/
|
|
27131
|
-
var STORE_UNAVAILABLE_NOTE = "I couldn't check my records just now \u2014 we can check again soon. Your Claude session keeps going, and I'll fill in as you work.";
|
|
27132
|
-
var SEVERITY_GLYPH = {
|
|
27133
|
-
critical: SHADE.full,
|
|
27134
|
-
high: SHADE.dark,
|
|
27135
|
-
medium: SHADE.medium,
|
|
27136
|
-
low: SHADE.light
|
|
27137
|
-
};
|
|
27138
|
-
var CATEGORY_ORDER2 = DetectionCategory.options;
|
|
27139
|
-
function categoryRank(category) {
|
|
27140
|
-
const i = CATEGORY_ORDER2.indexOf(category);
|
|
27141
|
-
return i === -1 ? CATEGORY_ORDER2.length : i;
|
|
27142
|
-
}
|
|
27143
|
-
function renderRecommendedPosture(posture) {
|
|
27144
|
-
const rows = Object.keys(posture).map((category) => ({
|
|
27145
|
-
category,
|
|
27146
|
-
level: posture[category] ?? ""
|
|
27147
|
-
}));
|
|
27148
|
-
const width = Math.max(0, ...rows.map((r) => r.category.length));
|
|
27149
|
-
return rows.sort((a, b) => categoryRank(a.category) - categoryRank(b.category)).map((r) => ` ${r.category.padEnd(width)} ${r.level}`).join("\n");
|
|
27150
|
-
}
|
|
27151
|
-
var GRID_MARK = "\u25CF";
|
|
27152
|
-
function renderPostureGrid(posture) {
|
|
27153
|
-
const packs2 = Object.keys(posture).sort(
|
|
27154
|
-
(a, b) => categoryRank(a) - categoryRank(b)
|
|
27155
|
-
);
|
|
27156
|
-
const rows = packs2.map((category) => [
|
|
27157
|
-
category,
|
|
27158
|
-
...BUILTIN_ORDER.map((level) => posture[category] === level ? GRID_MARK : "")
|
|
27159
|
-
]);
|
|
27160
|
-
return indent(table(["Category", ...BUILTIN_ORDER], rows));
|
|
27161
|
-
}
|
|
27162
|
-
var READY_COMMANDS = ["/aka:health", "/aka:findings", "/aka:recommend"];
|
|
27163
|
-
function renderCategoriesTuned(categoriesTuned) {
|
|
27164
|
-
return `\u2713 Set all ${String(categoriesTuned)} detection categories`;
|
|
27165
|
-
}
|
|
27166
|
-
function renderApplied(categoriesTuned, dismissed, registry2) {
|
|
27167
|
-
const routine = dismissed > 0 ? `set aside ${String(dismissed)} routine result${dismissed === 1 ? "" : "s"}` : "nothing routine to set aside";
|
|
27168
|
-
const ready = `Ready: ${selectRegisteredCommands(READY_COMMANDS, registry2).join(" \xB7 ")}`;
|
|
27169
|
-
return `${renderCategoriesTuned(categoriesTuned)} \xB7 ${routine} \xB7 ${ready}`;
|
|
27170
|
-
}
|
|
27171
|
-
|
|
27172
|
-
// src/calibration.ts
|
|
27173
|
-
var SURFACED_KIND_LABEL = {
|
|
27174
|
-
secret: "live keys",
|
|
27175
|
-
pii: "personal data",
|
|
27176
|
-
financial: "financial records",
|
|
27177
|
-
phi: "health records",
|
|
27178
|
-
code_context: "source context",
|
|
27179
|
-
code_flaw: "code flaws",
|
|
27180
|
-
custom: "custom matches",
|
|
27181
|
-
config: "configuration secrets"
|
|
27182
|
-
};
|
|
27183
|
-
function frameCalibration(preview, maskedFindings = [], falsePositivePatterns = []) {
|
|
27184
|
-
const important = preview.categories.reduce((n, c) => n + c.genuineCount, 0);
|
|
27185
|
-
const routine = preview.categories.reduce((n, c) => n + c.fpCount, 0);
|
|
27186
|
-
const total = important + routine;
|
|
27187
|
-
const surfacedCategories = preview.categories.filter((c) => c.genuineCount > 0).map((c) => c.category);
|
|
27188
|
-
const routineCategories = preview.categories.filter((c) => c.fpCount > 0).map((c) => c.category);
|
|
27189
|
-
const findingKinds = preview.categories.filter((c) => c.genuineCount + c.fpCount > 0).map((c) => ({ category: c.category, count: c.genuineCount + c.fpCount, egress: c.egress }));
|
|
27190
|
-
const frame = {
|
|
27191
|
-
counts: { total, important, routine },
|
|
27192
|
-
routineCategories,
|
|
27193
|
-
surfacedCategories,
|
|
27194
|
-
findingKinds,
|
|
27195
|
-
posture: preview.posture,
|
|
27196
|
-
...maskedFindings.length > 0 ? { maskedFindings: [...maskedFindings] } : {},
|
|
27197
|
-
...falsePositivePatterns.length > 0 ? { falsePositivePatterns: [...falsePositivePatterns] } : {}
|
|
27198
|
-
};
|
|
27199
|
-
const kind = surfacedCategories.map((c) => SURFACED_KIND_LABEL[c]).join(", ");
|
|
27200
|
-
const parenthetical = kind ? ` (${kind})` : "";
|
|
27201
|
-
const headline = `I went through Claude's recent work \u2014 ${String(total)} detection${total === 1 ? "" : "s"}, ${String(important)} result${important === 1 ? "" : "s"} worth a look.${parenthetical}`;
|
|
27202
|
-
const copy = headline;
|
|
27203
|
-
return { frame, copy };
|
|
27204
|
-
}
|
|
27205
|
-
var SCAN_CLEAN_HEADLINE = "I looked over Claude's recent work \u2014 nothing needs your attention right now. You're starting clean; here's what I'd recommend:";
|
|
27206
|
-
var NO_HISTORY_HEADLINE = "Nothing to learn from yet \u2014 Claude hasn't left any work on this machine. I'll start each detection category at a careful default:";
|
|
27207
|
-
function zeroCountFrame(posture) {
|
|
27208
|
-
return {
|
|
27209
|
-
counts: { total: 0, important: 0, routine: 0 },
|
|
27210
|
-
routineCategories: [],
|
|
27211
|
-
surfacedCategories: [],
|
|
27212
|
-
findingKinds: [],
|
|
27213
|
-
posture
|
|
27214
|
-
};
|
|
27215
|
-
}
|
|
27216
|
-
function frameEmptyState(cause, posture) {
|
|
27217
|
-
const frame = zeroCountFrame(posture);
|
|
27218
|
-
const copy = cause === "scan-clean" ? `${SCAN_CLEAN_HEADLINE}
|
|
27219
|
-
${renderRecommendedPosture(posture)}` : `${NO_HISTORY_HEADLINE}
|
|
27220
|
-
${renderPostureGrid(posture)}`;
|
|
27221
|
-
return { frame, copy };
|
|
27222
|
-
}
|
|
27223
|
-
|
|
27224
|
-
// src/triage/dedupe.ts
|
|
27225
|
-
function dedupeKey(hit) {
|
|
27226
|
-
if (hit.valueFingerprint === void 0) return void 0;
|
|
27227
|
-
return `${hit.ruleId}\u2588${hit.valueFingerprint}`;
|
|
27228
|
-
}
|
|
27229
|
-
function dedupeForJudge(hits) {
|
|
27230
|
-
const seen = /* @__PURE__ */ new Set();
|
|
27231
|
-
const out = [];
|
|
27232
|
-
for (const h of hits) {
|
|
27233
|
-
const key = dedupeKey(h);
|
|
27234
|
-
if (key === void 0) {
|
|
27235
|
-
out.push(h);
|
|
27236
|
-
continue;
|
|
27237
|
-
}
|
|
27238
|
-
if (seen.has(key)) continue;
|
|
27239
|
-
seen.add(key);
|
|
27240
|
-
out.push(h);
|
|
27241
|
-
}
|
|
27242
|
-
return out;
|
|
27243
|
-
}
|
|
27244
|
-
|
|
27245
|
-
// src/triage/false-positive-patterns.ts
|
|
27246
|
-
function deriveFalsePositivePatterns(hits, rec, plan) {
|
|
27247
|
-
const hitById = new Map(hits.filter((h) => h.id !== void 0).map((h) => [h.id, h]));
|
|
27248
|
-
const seenCategory = /* @__PURE__ */ new Set();
|
|
27249
|
-
const markedIds = /* @__PURE__ */ new Set();
|
|
27250
|
-
for (const c of rec.perCategory) {
|
|
27251
|
-
if (seenCategory.has(c.category)) continue;
|
|
27252
|
-
seenCategory.add(c.category);
|
|
27253
|
-
if (plan.posture[c.category] === void 0) continue;
|
|
27254
|
-
for (const id of c.fpIds) markedIds.add(id);
|
|
27255
|
-
}
|
|
27256
|
-
const groups = /* @__PURE__ */ new Map();
|
|
27257
|
-
for (const id of markedIds) {
|
|
27258
|
-
const h = hitById.get(id);
|
|
27259
|
-
if (h === void 0) continue;
|
|
27260
|
-
const pattern = safeMaskedMatch(h.rawMatch);
|
|
27261
|
-
const group = groups.get(pattern) ?? { count: 0, values: [] };
|
|
27262
|
-
group.count += 1;
|
|
27263
|
-
if (h.valueFingerprint !== void 0 && h.keyVersion !== void 0) {
|
|
27264
|
-
group.values.push({
|
|
27265
|
-
ruleId: h.ruleId,
|
|
27266
|
-
category: h.category,
|
|
27267
|
-
valueFingerprint: h.valueFingerprint,
|
|
27268
|
-
keyVersion: h.keyVersion
|
|
27269
|
-
});
|
|
27270
|
-
}
|
|
27271
|
-
groups.set(pattern, group);
|
|
27272
|
-
}
|
|
27273
|
-
return [...groups.entries()].filter(([, g]) => g.values.length > 0).map(([pattern, g]) => ({ pattern, count: g.count, values: g.values }));
|
|
27274
|
-
}
|
|
27275
|
-
|
|
27276
|
-
// src/triage/merge.ts
|
|
28615
|
+
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
27277
28616
|
var RANK = { monitor: 0, warn: 1, redact: 2, block: 3 };
|
|
27278
28617
|
function chunkForJudge(hits, maxBytes = 262144) {
|
|
27279
28618
|
const chunks = [];
|
|
@@ -27342,10 +28681,10 @@ function mergeRecommendations(verdicts) {
|
|
|
27342
28681
|
};
|
|
27343
28682
|
}
|
|
27344
28683
|
|
|
27345
|
-
// src/triage/plan-file.ts
|
|
27346
|
-
import { mkdtempSync, readFileSync as
|
|
28684
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
28685
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
27347
28686
|
import { tmpdir } from "os";
|
|
27348
|
-
import { basename as
|
|
28687
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
27349
28688
|
var SuppressionEntrySchema = external_exports.object({
|
|
27350
28689
|
ruleId: external_exports.string(),
|
|
27351
28690
|
category: DetectionCategory,
|
|
@@ -27400,27 +28739,27 @@ function serializePlan(plan, current) {
|
|
|
27400
28739
|
function writePlanFile(plan, current, rawValues, deps = {}) {
|
|
27401
28740
|
const serialized = serializePlan(plan, current);
|
|
27402
28741
|
assertRawFree(serialized, rawValues);
|
|
27403
|
-
const dir = (deps.mkTempDir ?? (() => mkdtempSync(
|
|
27404
|
-
const path =
|
|
27405
|
-
|
|
28742
|
+
const dir = (deps.mkTempDir ?? (() => mkdtempSync(join15(tmpdir(), "aka-plan-"))))();
|
|
28743
|
+
const path = join15(dir, "setup-plan.json");
|
|
28744
|
+
writeFileSync8(path, serialized, { encoding: "utf8", mode: 384 });
|
|
27406
28745
|
return path;
|
|
27407
28746
|
}
|
|
27408
28747
|
function readPlanFile(path) {
|
|
27409
|
-
const text =
|
|
28748
|
+
const text = readFileSync9(path, "utf8");
|
|
27410
28749
|
const json2 = JSON.parse(text);
|
|
27411
28750
|
return PersistedPlanSchema.parse(json2);
|
|
27412
28751
|
}
|
|
27413
28752
|
function deletePlanFile(path) {
|
|
27414
|
-
|
|
27415
|
-
const dir =
|
|
27416
|
-
if (!
|
|
28753
|
+
rmSync5(path, { force: true });
|
|
28754
|
+
const dir = dirname4(path);
|
|
28755
|
+
if (!basename6(dir).startsWith("aka-plan-")) return;
|
|
27417
28756
|
try {
|
|
27418
28757
|
rmdirSync(dir);
|
|
27419
28758
|
} catch {
|
|
27420
28759
|
}
|
|
27421
28760
|
}
|
|
27422
28761
|
|
|
27423
|
-
// src/triage/surfaced-secrets.ts
|
|
28762
|
+
// ../../packages/setup-wizard/src/triage/surfaced-secrets.ts
|
|
27424
28763
|
var DEFAULT_STATE = "unknown";
|
|
27425
28764
|
var UNKNOWN_LOCATION = "(location unavailable)";
|
|
27426
28765
|
function deriveProvider(ruleId) {
|
|
@@ -27452,7 +28791,7 @@ function deriveSurfacedSecretFindings(hits, rec, plan) {
|
|
|
27452
28791
|
}));
|
|
27453
28792
|
}
|
|
27454
28793
|
|
|
27455
|
-
// src/triage/join-file.ts
|
|
28794
|
+
// ../../packages/setup-wizard/src/triage/join-file.ts
|
|
27456
28795
|
function buildJoinEntries(hits) {
|
|
27457
28796
|
const rawValues = hits.map((h) => h.rawMatch);
|
|
27458
28797
|
return hits.map((h) => {
|
|
@@ -27479,9 +28818,9 @@ function buildJoinEntries(hits) {
|
|
|
27479
28818
|
});
|
|
27480
28819
|
}
|
|
27481
28820
|
|
|
27482
|
-
// src/triage/resolve.ts
|
|
27483
|
-
function resolveSuppressions(rec,
|
|
27484
|
-
const byId = new Map(
|
|
28821
|
+
// ../../packages/setup-wizard/src/triage/resolve.ts
|
|
28822
|
+
function resolveSuppressions(rec, join18) {
|
|
28823
|
+
const byId = new Map(join18.map((e) => [e.id, e]));
|
|
27485
28824
|
const entries = [];
|
|
27486
28825
|
const skipped = [];
|
|
27487
28826
|
for (const cat of rec.perCategory) {
|
|
@@ -27519,7 +28858,7 @@ function resolveSuppressions(rec, join15) {
|
|
|
27519
28858
|
return { entries, skipped };
|
|
27520
28859
|
}
|
|
27521
28860
|
|
|
27522
|
-
// src/triage/writeback.ts
|
|
28861
|
+
// ../../packages/setup-wizard/src/triage/writeback.ts
|
|
27523
28862
|
var SCRUBBED_NOTES = "[notes withheld: model text referenced a raw detected value]";
|
|
27524
28863
|
var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
|
|
27525
28864
|
function isSentinel(v) {
|
|
@@ -27583,7 +28922,7 @@ function parseTriageStream(text) {
|
|
|
27583
28922
|
return { hits, status: "complete" };
|
|
27584
28923
|
}
|
|
27585
28924
|
function planTriageWriteback(hits, rec) {
|
|
27586
|
-
const
|
|
28925
|
+
const join18 = buildJoinEntries(hits);
|
|
27587
28926
|
const rawValues = hits.map((h) => h.rawMatch);
|
|
27588
28927
|
const skipped = [];
|
|
27589
28928
|
const posture = {};
|
|
@@ -27623,7 +28962,7 @@ function planTriageWriteback(hits, rec) {
|
|
|
27623
28962
|
}
|
|
27624
28963
|
const { entries, skipped: resolveSkips } = resolveSuppressions(
|
|
27625
28964
|
{ perCategory: safeCategories, notes: rec.notes },
|
|
27626
|
-
|
|
28965
|
+
join18
|
|
27627
28966
|
);
|
|
27628
28967
|
skipped.push(...resolveSkips);
|
|
27629
28968
|
let notes = rec.notes;
|
|
@@ -27633,7 +28972,7 @@ function planTriageWriteback(hits, rec) {
|
|
|
27633
28972
|
if (err instanceof RawEgressError) notes = SCRUBBED_NOTES;
|
|
27634
28973
|
else throw err;
|
|
27635
28974
|
}
|
|
27636
|
-
return { entries, posture, showcase, join:
|
|
28975
|
+
return { entries, posture, showcase, join: join18, notes, skipped };
|
|
27637
28976
|
}
|
|
27638
28977
|
function recommendedPosture(evidence) {
|
|
27639
28978
|
return { ...severityFloorPosture(), ...evidence };
|
|
@@ -27653,7 +28992,7 @@ async function performTriageWriteback(plan, writers, opts) {
|
|
|
27653
28992
|
return { written, skippedDuplicate, categoriesWritten: tuned.size };
|
|
27654
28993
|
}
|
|
27655
28994
|
|
|
27656
|
-
// src/triage/adapter.ts
|
|
28995
|
+
// ../../packages/setup-wizard/src/triage/adapter.ts
|
|
27657
28996
|
var DEFAULT_PLAN_IO = {
|
|
27658
28997
|
write: writePlanFile,
|
|
27659
28998
|
read: readPlanFile,
|
|
@@ -27679,28 +29018,31 @@ async function runApply(deps) {
|
|
|
27679
29018
|
return confirmed ? runConfirm(deps, planIO) : runPreview(deps, planIO);
|
|
27680
29019
|
}
|
|
27681
29020
|
function runPreview(deps, planIO) {
|
|
29021
|
+
const { present } = deps;
|
|
27682
29022
|
const streamPath = getFlag(deps.argv, "stream");
|
|
27683
29023
|
const streamText = deps.readStream(streamPath === "" ? void 0 : streamPath);
|
|
27684
29024
|
const { hits, status } = parseTriageStream(streamText);
|
|
27685
29025
|
if (hits.length === 0) {
|
|
27686
29026
|
if (status === "complete") {
|
|
27687
|
-
const empty = frameEmptyState("scan-clean", severityFloorPosture());
|
|
27688
|
-
deps.stdout(show(fenced(empty.copy)));
|
|
27689
|
-
deps.stdout(frameJsonBlock(empty.frame));
|
|
29027
|
+
const empty = present.frameEmptyState("scan-clean", severityFloorPosture());
|
|
29028
|
+
deps.stdout(present.show(present.fenced(empty.copy)));
|
|
29029
|
+
deps.stdout(present.frameJsonBlock(empty.frame));
|
|
27690
29030
|
return 0;
|
|
27691
29031
|
}
|
|
27692
29032
|
if (status === "complete:no-history") {
|
|
27693
|
-
const empty = frameEmptyState("no-history", severityFloorPosture());
|
|
27694
|
-
deps.stdout(show(fenced(empty.copy)));
|
|
27695
|
-
deps.stdout(frameJsonBlock(empty.frame));
|
|
29033
|
+
const empty = present.frameEmptyState("no-history", severityFloorPosture());
|
|
29034
|
+
deps.stdout(present.show(present.fenced(empty.copy)));
|
|
29035
|
+
deps.stdout(present.frameJsonBlock(empty.frame));
|
|
27696
29036
|
return 0;
|
|
27697
29037
|
}
|
|
27698
|
-
deps.stdout(show("I didn't review anything \u2014 historical access wasn't granted."));
|
|
29038
|
+
deps.stdout(present.show("I didn't review anything \u2014 historical access wasn't granted."));
|
|
27699
29039
|
return 0;
|
|
27700
29040
|
}
|
|
27701
29041
|
if (!deps.modelJudgeConsent()) {
|
|
27702
|
-
deps.stdout(
|
|
27703
|
-
|
|
29042
|
+
deps.stdout(
|
|
29043
|
+
present.show("I didn't send anything to the model \u2014 model-judge consent wasn't granted.")
|
|
29044
|
+
);
|
|
29045
|
+
deps.stdout(present.frameJsonBlock(present.zeroCountFrame(severityFloorPosture())));
|
|
27704
29046
|
return 0;
|
|
27705
29047
|
}
|
|
27706
29048
|
const rawValues = hits.map((h) => h.rawMatch);
|
|
@@ -27709,7 +29051,7 @@ function runPreview(deps, planIO) {
|
|
|
27709
29051
|
const chunks = chunkForJudge(reps, resolveMaxJudgeBytes(deps));
|
|
27710
29052
|
if (chunks.length > 1) {
|
|
27711
29053
|
deps.stdout(
|
|
27712
|
-
show(
|
|
29054
|
+
present.show(
|
|
27713
29055
|
`Reviewing ${String(reps.length)} distinct values in ${String(chunks.length)} batches \u2014 this is the large-history path, so give it a moment.`
|
|
27714
29056
|
)
|
|
27715
29057
|
);
|
|
@@ -27740,7 +29082,7 @@ function runPreview(deps, planIO) {
|
|
|
27740
29082
|
}
|
|
27741
29083
|
const gate = [];
|
|
27742
29084
|
if (storeUnavailable) {
|
|
27743
|
-
gate.push(
|
|
29085
|
+
gate.push(present.storeUnavailableNote);
|
|
27744
29086
|
}
|
|
27745
29087
|
gate.push(renderPosturePlan(plan.posture, storeUnavailable ? {} : current));
|
|
27746
29088
|
gate.push(renderShowcase(plan.showcase));
|
|
@@ -27762,11 +29104,11 @@ function runPreview(deps, planIO) {
|
|
|
27762
29104
|
};
|
|
27763
29105
|
const maskedFindings = deriveSurfacedSecretFindings(hits, rec, plan);
|
|
27764
29106
|
const falsePositivePatterns = deriveFalsePositivePatterns(reps, rec, plan);
|
|
27765
|
-
const calibration = frameCalibration(preview, maskedFindings, falsePositivePatterns);
|
|
29107
|
+
const calibration = present.frameCalibration(preview, maskedFindings, falsePositivePatterns);
|
|
27766
29108
|
gate.push(calibration.copy);
|
|
27767
|
-
gate.push(renderRecommendedPosture(preview.posture));
|
|
27768
|
-
deps.stdout(show(fenced(gate.join("\n\n"))));
|
|
27769
|
-
deps.stdout(frameJsonBlock(calibration.frame));
|
|
29109
|
+
gate.push(present.renderRecommendedPosture(preview.posture));
|
|
29110
|
+
deps.stdout(present.show(present.fenced(gate.join("\n\n"))));
|
|
29111
|
+
deps.stdout(present.frameJsonBlock(calibration.frame));
|
|
27770
29112
|
const planPath = planIO.write(plan, storeUnavailable ? {} : current, rawValues);
|
|
27771
29113
|
deps.stdout(`
|
|
27772
29114
|
Plan saved to: ${planPath}
|
|
@@ -27823,7 +29165,7 @@ async function runConfirm(deps, planIO) {
|
|
|
27823
29165
|
if (drifted.length > 0) {
|
|
27824
29166
|
closeOnce();
|
|
27825
29167
|
deps.stderr(
|
|
27826
|
-
`AKA apply-suppressions failed: the detection store changed since this plan was previewed (${drifted.join(", ")}). Refusing to apply a stale plan \u2014 re-run
|
|
29168
|
+
`AKA apply-suppressions failed: the detection store changed since this plan was previewed (${drifted.join(", ")}). Refusing to apply a stale plan \u2014 re-run ${deps.present.rerunHint} to review against the current store.
|
|
27827
29169
|
`
|
|
27828
29170
|
);
|
|
27829
29171
|
return 1;
|
|
@@ -27873,7 +29215,7 @@ async function runConfirm(deps, planIO) {
|
|
|
27873
29215
|
}
|
|
27874
29216
|
try {
|
|
27875
29217
|
deps.stdout(
|
|
27876
|
-
show(renderApplied(res.categoriesWritten, res.written
|
|
29218
|
+
deps.present.show(deps.present.renderApplied(res.categoriesWritten, res.written))
|
|
27877
29219
|
);
|
|
27878
29220
|
} catch {
|
|
27879
29221
|
}
|
|
@@ -27888,14 +29230,7 @@ async function runConfirm(deps, planIO) {
|
|
|
27888
29230
|
}
|
|
27889
29231
|
}
|
|
27890
29232
|
|
|
27891
|
-
// src/triage/
|
|
27892
|
-
import { execFileSync } from "child_process";
|
|
27893
|
-
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
|
|
27894
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
27895
|
-
import { dirname as dirname4, join as join13 } from "path";
|
|
27896
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
27897
|
-
|
|
27898
|
-
// src/triage/parse-verdict.ts
|
|
29233
|
+
// ../../packages/setup-wizard/src/triage/parse-verdict.ts
|
|
27899
29234
|
var FENCE_RE = /```json\s*([\s\S]*?)```/g;
|
|
27900
29235
|
function parseRecommendation(text) {
|
|
27901
29236
|
const fences = [...text.matchAll(FENCE_RE)];
|
|
@@ -27905,8 +29240,23 @@ function parseRecommendation(text) {
|
|
|
27905
29240
|
}
|
|
27906
29241
|
|
|
27907
29242
|
// src/triage/judge.ts
|
|
27908
|
-
|
|
27909
|
-
|
|
29243
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
29244
|
+
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync10, rmSync as rmSync6 } from "fs";
|
|
29245
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
29246
|
+
import { dirname as dirname5, join as join16 } from "path";
|
|
29247
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
29248
|
+
var TRIAGE_DIR = dirname5(fileURLToPath2(import.meta.url));
|
|
29249
|
+
var DEFAULT_RUBRIC_PATH = join16(
|
|
29250
|
+
TRIAGE_DIR,
|
|
29251
|
+
"..",
|
|
29252
|
+
"..",
|
|
29253
|
+
"..",
|
|
29254
|
+
"..",
|
|
29255
|
+
"packages",
|
|
29256
|
+
"setup-wizard",
|
|
29257
|
+
"assets",
|
|
29258
|
+
"triage-rubric.md"
|
|
29259
|
+
);
|
|
27910
29260
|
function parseVerdict(stdout) {
|
|
27911
29261
|
let envelope;
|
|
27912
29262
|
try {
|
|
@@ -27925,20 +29275,20 @@ function parseVerdict(stdout) {
|
|
|
27925
29275
|
throw new Error("claude -p returned an unparseable TriageRecommendation");
|
|
27926
29276
|
}
|
|
27927
29277
|
}
|
|
27928
|
-
function judgeEnv() {
|
|
29278
|
+
function judgeEnv(platform2 = process.platform) {
|
|
27929
29279
|
const env = {
|
|
27930
29280
|
// eslint-disable-next-line n/no-process-env -- subprocess must inherit PATH/auth
|
|
27931
29281
|
...process.env,
|
|
27932
29282
|
CLAUDE_CODE_SKIP_PROMPT_HISTORY: "1",
|
|
27933
29283
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
27934
29284
|
};
|
|
27935
|
-
if (
|
|
27936
|
-
env.CLAUDE_CONFIG_DIR = mkdtempSync2(
|
|
29285
|
+
if (platform2 === "darwin") {
|
|
29286
|
+
env.CLAUDE_CONFIG_DIR = mkdtempSync2(join16(tmpdir2(), "aka-judge-cfg-"));
|
|
27937
29287
|
}
|
|
27938
29288
|
return env;
|
|
27939
29289
|
}
|
|
27940
29290
|
function spawnClaude(argv, env, stdin) {
|
|
27941
|
-
return
|
|
29291
|
+
return execFileSync2("claude", [...argv], {
|
|
27942
29292
|
env,
|
|
27943
29293
|
input: stdin,
|
|
27944
29294
|
encoding: "utf8",
|
|
@@ -27962,7 +29312,10 @@ function toJudgePayload(hit) {
|
|
|
27962
29312
|
return payload;
|
|
27963
29313
|
}
|
|
27964
29314
|
function runJudge(hits, deps) {
|
|
27965
|
-
|
|
29315
|
+
if (typeof deps.spawn !== "function") {
|
|
29316
|
+
throw new TypeError("runJudge requires deps.spawn \u2014 there is no live-spawn fallback");
|
|
29317
|
+
}
|
|
29318
|
+
const rubric = deps.loadRubric?.() ?? readFileSync10(DEFAULT_RUBRIC_PATH, "utf8");
|
|
27966
29319
|
const hitsJsonl = hits.map((h) => JSON.stringify(toJudgePayload(h))).join("\n");
|
|
27967
29320
|
const fullPrompt = `${rubric}
|
|
27968
29321
|
|
|
@@ -27973,7 +29326,8 @@ ${hitsJsonl}
|
|
|
27973
29326
|
\`\`\`
|
|
27974
29327
|
`;
|
|
27975
29328
|
const argv = ["-p", "--no-session-persistence", "--output-format", "json"];
|
|
27976
|
-
const
|
|
29329
|
+
const platform2 = deps.platform ?? process.platform;
|
|
29330
|
+
const env = judgeEnv(platform2);
|
|
27977
29331
|
try {
|
|
27978
29332
|
let stdout;
|
|
27979
29333
|
try {
|
|
@@ -27983,12 +29337,239 @@ ${hitsJsonl}
|
|
|
27983
29337
|
}
|
|
27984
29338
|
return parseVerdict(stdout);
|
|
27985
29339
|
} finally {
|
|
27986
|
-
if (
|
|
27987
|
-
|
|
29340
|
+
if (platform2 === "darwin" && env.CLAUDE_CONFIG_DIR) {
|
|
29341
|
+
try {
|
|
29342
|
+
rmSync6(env.CLAUDE_CONFIG_DIR, { recursive: true, force: true });
|
|
29343
|
+
} catch {
|
|
29344
|
+
}
|
|
27988
29345
|
}
|
|
27989
29346
|
}
|
|
27990
29347
|
}
|
|
27991
29348
|
|
|
29349
|
+
// src/command-registry.ts
|
|
29350
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
29351
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
29352
|
+
var COMMAND_NAMESPACE = "aka";
|
|
29353
|
+
var COMMANDS_DIR = fileURLToPath3(new URL("../commands", import.meta.url));
|
|
29354
|
+
function readRegisteredCommands() {
|
|
29355
|
+
return readdirSync5(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
29356
|
+
}
|
|
29357
|
+
function selectRegisteredCommands(curated, registry2) {
|
|
29358
|
+
const registered = new Set(registry2);
|
|
29359
|
+
const missing = curated.filter((c) => !registered.has(c));
|
|
29360
|
+
if (missing.length > 0) {
|
|
29361
|
+
throw new Error(
|
|
29362
|
+
`Curated command(s) not registered in the installed plugin: ${missing.join(", ")}`
|
|
29363
|
+
);
|
|
29364
|
+
}
|
|
29365
|
+
return [...curated];
|
|
29366
|
+
}
|
|
29367
|
+
|
|
29368
|
+
// src/setup-frame-json.ts
|
|
29369
|
+
var FRAME_JSON_BEGIN = "<<<AKA_FRAME_JSON";
|
|
29370
|
+
var FRAME_JSON_END = "AKA_FRAME_JSON>>>";
|
|
29371
|
+
function frameJsonBlock(payload) {
|
|
29372
|
+
return `${FRAME_JSON_BEGIN}
|
|
29373
|
+
${JSON.stringify(payload)}
|
|
29374
|
+
${FRAME_JSON_END}
|
|
29375
|
+
`;
|
|
29376
|
+
}
|
|
29377
|
+
|
|
29378
|
+
// src/setup-show.ts
|
|
29379
|
+
var SHOW_BEGIN = "<<<AKA_SHOW";
|
|
29380
|
+
var SHOW_END = "AKA_SHOW>>>";
|
|
29381
|
+
function showBlock(body) {
|
|
29382
|
+
return `${SHOW_BEGIN}
|
|
29383
|
+
${body}
|
|
29384
|
+
${SHOW_END}
|
|
29385
|
+
`;
|
|
29386
|
+
}
|
|
29387
|
+
|
|
29388
|
+
// src/present.ts
|
|
29389
|
+
var SHADE = {
|
|
29390
|
+
light: "\u2591",
|
|
29391
|
+
medium: "\u2592",
|
|
29392
|
+
dark: "\u2593",
|
|
29393
|
+
full: "\u2588"
|
|
29394
|
+
};
|
|
29395
|
+
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
29396
|
+
function visibleLength(text) {
|
|
29397
|
+
return text.replace(ANSI_RE, "").length;
|
|
29398
|
+
}
|
|
29399
|
+
var fg = (hex3) => (text) => {
|
|
29400
|
+
const r = Number.parseInt(hex3.slice(1, 3), 16);
|
|
29401
|
+
const g = Number.parseInt(hex3.slice(3, 5), 16);
|
|
29402
|
+
const b = Number.parseInt(hex3.slice(5, 7), 16);
|
|
29403
|
+
return `\x1B[38;2;${String(r)};${String(g)};${String(b)}m${text}\x1B[0m`;
|
|
29404
|
+
};
|
|
29405
|
+
var paint = {
|
|
29406
|
+
brand: fg("#33e6c6"),
|
|
29407
|
+
// --color-brand · ▸▸ AKA wordmark (accent text)
|
|
29408
|
+
dim: fg("#838995"),
|
|
29409
|
+
// --color-text-3 · separators · "/100" · the "unreviewed" label
|
|
29410
|
+
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
29411
|
+
// the health score number
|
|
29412
|
+
ok: fg("#0db15f"),
|
|
29413
|
+
// --color-ok · healthy ● dot
|
|
29414
|
+
critical: fg("#e63448"),
|
|
29415
|
+
// --color-sev-critical · ■ and the open-findings flag
|
|
29416
|
+
high: fg("#e97a0a"),
|
|
29417
|
+
// --color-sev-high · ■ and the mid-health dot
|
|
29418
|
+
medium: fg("#f7bd00"),
|
|
29419
|
+
// --color-sev-medium · ■
|
|
29420
|
+
low: fg("#0581d4")
|
|
29421
|
+
// --color-sev-low · ■ (azure blue, not purple)
|
|
29422
|
+
};
|
|
29423
|
+
function padEnd(text, width) {
|
|
29424
|
+
const pad = width - visibleLength(text);
|
|
29425
|
+
return pad > 0 ? text + " ".repeat(pad) : text;
|
|
29426
|
+
}
|
|
29427
|
+
function indent(text, spaces = 2) {
|
|
29428
|
+
const pad = " ".repeat(spaces);
|
|
29429
|
+
return text.split("\n").map((line) => pad + line).join("\n");
|
|
29430
|
+
}
|
|
29431
|
+
function table(headers, rows, opts = {}) {
|
|
29432
|
+
const gap = opts.gap ?? 3;
|
|
29433
|
+
const widths = headers.map(
|
|
29434
|
+
(h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
|
|
29435
|
+
);
|
|
29436
|
+
const sep5 = " ".repeat(gap);
|
|
29437
|
+
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
|
|
29438
|
+
const headerLine = fmt(headers.map((h) => h.toUpperCase()));
|
|
29439
|
+
if (opts.rowSep === true) {
|
|
29440
|
+
const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
29441
|
+
const rule = "\u2500".repeat(fullWidth);
|
|
29442
|
+
const body = [];
|
|
29443
|
+
rows.forEach((row, i) => {
|
|
29444
|
+
if (i > 0) body.push(rule);
|
|
29445
|
+
body.push(fmt(row));
|
|
29446
|
+
});
|
|
29447
|
+
return [headerLine, rule, ...body].join("\n");
|
|
29448
|
+
}
|
|
29449
|
+
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep5);
|
|
29450
|
+
return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
|
|
29451
|
+
}
|
|
29452
|
+
function fenced(body) {
|
|
29453
|
+
const longestRun = Math.max(0, ...[...body.matchAll(/`+/g)].map((m) => m[0].length));
|
|
29454
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
29455
|
+
return [fence, body, fence].join("\n");
|
|
29456
|
+
}
|
|
29457
|
+
function show(body) {
|
|
29458
|
+
return showBlock(body);
|
|
29459
|
+
}
|
|
29460
|
+
|
|
29461
|
+
// src/render.ts
|
|
29462
|
+
var STORE_UNAVAILABLE_NOTE = "I couldn't check my records just now \u2014 we can check again soon. Your Claude session keeps going, and I'll fill in as you work.";
|
|
29463
|
+
var SEVERITY_GLYPH = {
|
|
29464
|
+
critical: SHADE.full,
|
|
29465
|
+
high: SHADE.dark,
|
|
29466
|
+
medium: SHADE.medium,
|
|
29467
|
+
low: SHADE.light
|
|
29468
|
+
};
|
|
29469
|
+
var CATEGORY_ORDER2 = DetectionCategory.options;
|
|
29470
|
+
function categoryRank(category) {
|
|
29471
|
+
const i = CATEGORY_ORDER2.indexOf(category);
|
|
29472
|
+
return i === -1 ? CATEGORY_ORDER2.length : i;
|
|
29473
|
+
}
|
|
29474
|
+
function renderRecommendedPosture(posture) {
|
|
29475
|
+
const rows = Object.keys(posture).map((category) => ({
|
|
29476
|
+
category,
|
|
29477
|
+
level: posture[category] ?? ""
|
|
29478
|
+
}));
|
|
29479
|
+
const width = Math.max(0, ...rows.map((r) => r.category.length));
|
|
29480
|
+
return rows.sort((a, b) => categoryRank(a.category) - categoryRank(b.category)).map((r) => ` ${r.category.padEnd(width)} ${r.level}`).join("\n");
|
|
29481
|
+
}
|
|
29482
|
+
var GRID_MARK = "\u25CF";
|
|
29483
|
+
function renderPostureGrid(posture) {
|
|
29484
|
+
const packs2 = Object.keys(posture).sort(
|
|
29485
|
+
(a, b) => categoryRank(a) - categoryRank(b)
|
|
29486
|
+
);
|
|
29487
|
+
const rows = packs2.map((category) => [
|
|
29488
|
+
category,
|
|
29489
|
+
...BUILTIN_ORDER.map((level) => posture[category] === level ? GRID_MARK : "")
|
|
29490
|
+
]);
|
|
29491
|
+
return indent(table(["Category", ...BUILTIN_ORDER], rows));
|
|
29492
|
+
}
|
|
29493
|
+
var READY_COMMANDS = ["/aka:health", "/aka:findings", "/aka:recommend"];
|
|
29494
|
+
function renderCategoriesTuned(categoriesTuned) {
|
|
29495
|
+
return `\u2713 Set all ${String(categoriesTuned)} detection categories`;
|
|
29496
|
+
}
|
|
29497
|
+
function renderApplied(categoriesTuned, dismissed, registry2) {
|
|
29498
|
+
const routine = dismissed > 0 ? `set aside ${String(dismissed)} routine result${dismissed === 1 ? "" : "s"}` : "nothing routine to set aside";
|
|
29499
|
+
const ready = `Ready: ${selectRegisteredCommands(READY_COMMANDS, registry2).join(" \xB7 ")}`;
|
|
29500
|
+
return `${renderCategoriesTuned(categoriesTuned)} \xB7 ${routine} \xB7 ${ready}`;
|
|
29501
|
+
}
|
|
29502
|
+
|
|
29503
|
+
// src/calibration.ts
|
|
29504
|
+
var SURFACED_KIND_LABEL = {
|
|
29505
|
+
secret: "live keys",
|
|
29506
|
+
pii: "personal data",
|
|
29507
|
+
financial: "financial records",
|
|
29508
|
+
phi: "health records",
|
|
29509
|
+
code_context: "source context",
|
|
29510
|
+
code_flaw: "code flaws",
|
|
29511
|
+
custom: "custom matches",
|
|
29512
|
+
config: "configuration secrets"
|
|
29513
|
+
};
|
|
29514
|
+
function frameCalibration(preview, maskedFindings = [], falsePositivePatterns = []) {
|
|
29515
|
+
const important = preview.categories.reduce((n, c) => n + c.genuineCount, 0);
|
|
29516
|
+
const routine = preview.categories.reduce((n, c) => n + c.fpCount, 0);
|
|
29517
|
+
const total = important + routine;
|
|
29518
|
+
const surfacedCategories = preview.categories.filter((c) => c.genuineCount > 0).map((c) => c.category);
|
|
29519
|
+
const routineCategories = preview.categories.filter((c) => c.fpCount > 0).map((c) => c.category);
|
|
29520
|
+
const findingKinds = preview.categories.filter((c) => c.genuineCount + c.fpCount > 0).map((c) => ({ category: c.category, count: c.genuineCount + c.fpCount, egress: c.egress }));
|
|
29521
|
+
const frame = {
|
|
29522
|
+
counts: { total, important, routine },
|
|
29523
|
+
routineCategories,
|
|
29524
|
+
surfacedCategories,
|
|
29525
|
+
findingKinds,
|
|
29526
|
+
posture: preview.posture,
|
|
29527
|
+
...maskedFindings.length > 0 ? { maskedFindings: [...maskedFindings] } : {},
|
|
29528
|
+
...falsePositivePatterns.length > 0 ? { falsePositivePatterns: [...falsePositivePatterns] } : {}
|
|
29529
|
+
};
|
|
29530
|
+
const kind = surfacedCategories.map((c) => SURFACED_KIND_LABEL[c]).join(", ");
|
|
29531
|
+
const parenthetical = kind ? ` (${kind})` : "";
|
|
29532
|
+
const headline = `I went through Claude's recent work \u2014 ${String(total)} detection${total === 1 ? "" : "s"}, ${String(important)} result${important === 1 ? "" : "s"} worth a look.${parenthetical}`;
|
|
29533
|
+
const copy = headline;
|
|
29534
|
+
return { frame, copy };
|
|
29535
|
+
}
|
|
29536
|
+
var SCAN_CLEAN_HEADLINE = "I looked over Claude's recent work \u2014 nothing needs your attention right now. You're starting clean; here's what I'd recommend:";
|
|
29537
|
+
var NO_HISTORY_HEADLINE = "Nothing to learn from yet \u2014 Claude hasn't left any work on this machine. I'll start each detection category at a careful default:";
|
|
29538
|
+
function zeroCountFrame(posture) {
|
|
29539
|
+
return {
|
|
29540
|
+
counts: { total: 0, important: 0, routine: 0 },
|
|
29541
|
+
routineCategories: [],
|
|
29542
|
+
surfacedCategories: [],
|
|
29543
|
+
findingKinds: [],
|
|
29544
|
+
posture
|
|
29545
|
+
};
|
|
29546
|
+
}
|
|
29547
|
+
function frameEmptyState(cause, posture) {
|
|
29548
|
+
const frame = zeroCountFrame(posture);
|
|
29549
|
+
const copy = cause === "scan-clean" ? `${SCAN_CLEAN_HEADLINE}
|
|
29550
|
+
${renderRecommendedPosture(posture)}` : `${NO_HISTORY_HEADLINE}
|
|
29551
|
+
${renderPostureGrid(posture)}`;
|
|
29552
|
+
return { frame, copy };
|
|
29553
|
+
}
|
|
29554
|
+
|
|
29555
|
+
// src/triage/presenter.ts
|
|
29556
|
+
var adapterPresenter = {
|
|
29557
|
+
show,
|
|
29558
|
+
fenced,
|
|
29559
|
+
frameJsonBlock,
|
|
29560
|
+
frameEmptyState,
|
|
29561
|
+
frameCalibration,
|
|
29562
|
+
zeroCountFrame,
|
|
29563
|
+
renderRecommendedPosture,
|
|
29564
|
+
// The applied card's Ready line resolves against the installed command
|
|
29565
|
+
// registry — closed over here so the shared core never reads it.
|
|
29566
|
+
renderApplied: (categoriesTuned, dismissed) => renderApplied(categoriesTuned, dismissed, readRegisteredCommands()),
|
|
29567
|
+
storeUnavailableNote: STORE_UNAVAILABLE_NOTE,
|
|
29568
|
+
// The stale-plan refusal tells the user to restart the wizard by its
|
|
29569
|
+
// Claude Code command name.
|
|
29570
|
+
rerunHint: "/aka:setup"
|
|
29571
|
+
};
|
|
29572
|
+
|
|
27992
29573
|
// src/apply-suppressions.ts
|
|
27993
29574
|
function fail(message) {
|
|
27994
29575
|
process.stderr.write(`AKA apply-suppressions failed: ${message}
|
|
@@ -28003,10 +29584,13 @@ function resolveCreatedBy() {
|
|
|
28003
29584
|
}
|
|
28004
29585
|
}
|
|
28005
29586
|
function loadRubric() {
|
|
28006
|
-
const here =
|
|
28007
|
-
const shipped =
|
|
28008
|
-
if (
|
|
28009
|
-
return
|
|
29587
|
+
const here = dirname6(fileURLToPath4(import.meta.url));
|
|
29588
|
+
const shipped = join17(here, "triage-rubric.md");
|
|
29589
|
+
if (existsSync9(shipped)) return readFileSync11(shipped, "utf8");
|
|
29590
|
+
return readFileSync11(
|
|
29591
|
+
join17(here, "..", "..", "..", "packages", "setup-wizard", "assets", "triage-rubric.md"),
|
|
29592
|
+
"utf8"
|
|
29593
|
+
);
|
|
28010
29594
|
}
|
|
28011
29595
|
async function main() {
|
|
28012
29596
|
const argv = process.argv.slice(2);
|
|
@@ -28014,7 +29598,7 @@ async function main() {
|
|
|
28014
29598
|
argv,
|
|
28015
29599
|
// fd 0 = stdin; the wizard pipes `backfill.js --triage` into this on preview.
|
|
28016
29600
|
// Called only on the preview path — the confirm path never reads a stream.
|
|
28017
|
-
readStream: (streamPath) => streamPath !== void 0 ?
|
|
29601
|
+
readStream: (streamPath) => streamPath !== void 0 ? readFileSync11(streamPath, "utf8") : readFileSync11(0, "utf8"),
|
|
28018
29602
|
runJudge: (hits) => runJudge(hits, { spawn: spawnClaude, loadRubric }),
|
|
28019
29603
|
// The distinct model-judge egress consent, read from settings.json. When it
|
|
28020
29604
|
// is absent or stale the preview skips the judge instead of sending findings
|
|
@@ -28036,6 +29620,7 @@ async function main() {
|
|
|
28036
29620
|
now: () => Date.now(),
|
|
28037
29621
|
createdBy: resolveCreatedBy,
|
|
28038
29622
|
stdout: (s) => process.stdout.write(s),
|
|
29623
|
+
present: adapterPresenter,
|
|
28039
29624
|
stderr: (s) => process.stderr.write(s)
|
|
28040
29625
|
});
|
|
28041
29626
|
process.exit(code);
|