@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
package/scripts/onboard.js
CHANGED
|
@@ -492,9 +492,8 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/persistence/src/database.ts
|
|
495
|
-
import { randomUUID as
|
|
496
|
-
import {
|
|
497
|
-
import { join, sep } from "path";
|
|
495
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
496
|
+
import { join as join2, sep } from "path";
|
|
498
497
|
import { DatabaseSync } from "node:sqlite";
|
|
499
498
|
|
|
500
499
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -558,6 +557,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
558
557
|
{
|
|
559
558
|
tag: "0014_drop_legacy_events_findings",
|
|
560
559
|
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"
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
tag: "0015_busy_vengeance",
|
|
563
|
+
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`);"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
tag: "0016_breezy_zodiak",
|
|
567
|
+
sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
tag: "0017_rainy_kat_farrell",
|
|
571
|
+
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`);"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
tag: "0018_serious_tana_nile",
|
|
575
|
+
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
tag: "0019_audit_started_at_index",
|
|
579
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
583
|
+
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');"
|
|
561
584
|
}
|
|
562
585
|
];
|
|
563
586
|
|
|
@@ -15295,7 +15318,17 @@ var Finding = external_exports.object({
|
|
|
15295
15318
|
}).meta({ id: "Finding" });
|
|
15296
15319
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15297
15320
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15298
|
-
var FindingProvider = external_exports.enum([
|
|
15321
|
+
var FindingProvider = external_exports.enum([
|
|
15322
|
+
"claudecode",
|
|
15323
|
+
"claudedesktop",
|
|
15324
|
+
"cursor",
|
|
15325
|
+
"copilot",
|
|
15326
|
+
"chatgpt",
|
|
15327
|
+
"claudeai",
|
|
15328
|
+
"codex",
|
|
15329
|
+
"antigravity",
|
|
15330
|
+
"api"
|
|
15331
|
+
]).meta({ id: "FindingProvider" });
|
|
15299
15332
|
var FindingCategory = external_exports.enum([
|
|
15300
15333
|
"secret",
|
|
15301
15334
|
"pii",
|
|
@@ -15349,7 +15382,16 @@ var FindingInstance = external_exports.object({
|
|
|
15349
15382
|
confidence: external_exports.number().min(0).max(1),
|
|
15350
15383
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15351
15384
|
// that predate the resolution feature stay valid.
|
|
15352
|
-
status: FindingStatus.optional()
|
|
15385
|
+
status: FindingStatus.optional(),
|
|
15386
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15387
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15388
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15389
|
+
// MOST RECENT detection event, not the first.
|
|
15390
|
+
eventId: external_exports.string().optional(),
|
|
15391
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15392
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15393
|
+
// outside a session.
|
|
15394
|
+
sessionId: external_exports.string().optional()
|
|
15353
15395
|
}).meta({ id: "FindingInstance" });
|
|
15354
15396
|
var FindingGroup = external_exports.object({
|
|
15355
15397
|
id: external_exports.string(),
|
|
@@ -15393,7 +15435,11 @@ var FindingFacets = external_exports.object({
|
|
|
15393
15435
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15394
15436
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15395
15437
|
// counted under no value.
|
|
15396
|
-
status: external_exports.array(FindingFacetItem)
|
|
15438
|
+
status: external_exports.array(FindingFacetItem),
|
|
15439
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15440
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15441
|
+
// because a group spans tools.
|
|
15442
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15397
15443
|
}).meta({ id: "FindingFacets" });
|
|
15398
15444
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15399
15445
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15411,6 +15457,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15411
15457
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15412
15458
|
// session → findings drilldown). Findings without a session never match.
|
|
15413
15459
|
sessionId: external_exports.string().optional(),
|
|
15460
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15461
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15462
|
+
// means all time — this list has no default window.
|
|
15463
|
+
from: external_exports.iso.datetime().optional(),
|
|
15464
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15465
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15466
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15467
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15468
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15469
|
+
includeId: external_exports.string().optional(),
|
|
15414
15470
|
groupBy: external_exports.literal("type").optional(),
|
|
15415
15471
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15416
15472
|
cursor: external_exports.string().optional()
|
|
@@ -15455,15 +15511,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15455
15511
|
detection: FindingDetectionRef,
|
|
15456
15512
|
policy: FindingPolicyRef
|
|
15457
15513
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15514
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15515
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15516
|
+
severity: external_exports.array(Severity).optional(),
|
|
15517
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15518
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15519
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15520
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15521
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15522
|
+
// the grouped query's group-level fold.
|
|
15523
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15524
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15525
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15526
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15527
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15528
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15529
|
+
repo: external_exports.string().optional(),
|
|
15530
|
+
file: external_exports.string().optional(),
|
|
15531
|
+
q: external_exports.string().optional(),
|
|
15532
|
+
sessionId: external_exports.string().optional(),
|
|
15533
|
+
from: external_exports.iso.datetime().optional(),
|
|
15534
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15535
|
+
cursor: external_exports.string().optional()
|
|
15536
|
+
});
|
|
15537
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15538
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15539
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15540
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15541
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15542
|
+
// dimension still excludes its own filter.
|
|
15543
|
+
facets: FindingFacets,
|
|
15544
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15545
|
+
nextCursor: external_exports.string().nullable()
|
|
15546
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15547
|
+
var FindingLocationFile = external_exports.object({
|
|
15548
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15549
|
+
// with no file attribution).
|
|
15550
|
+
file: external_exports.string(),
|
|
15551
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15552
|
+
maxSeverity: Severity,
|
|
15553
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15554
|
+
// Folded from the instances' derived statuses with the same
|
|
15555
|
+
// open-dominates precedence a group uses.
|
|
15556
|
+
status: FindingStatus.optional(),
|
|
15557
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15558
|
+
// chips, and the count is what conveys scale.
|
|
15559
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15560
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15561
|
+
var FindingLocationRepo = external_exports.object({
|
|
15562
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15563
|
+
repo: external_exports.string(),
|
|
15564
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15565
|
+
maxSeverity: Severity,
|
|
15566
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15567
|
+
status: FindingStatus.optional(),
|
|
15568
|
+
files: external_exports.array(FindingLocationFile)
|
|
15569
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15570
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15571
|
+
severity: external_exports.array(Severity).optional(),
|
|
15572
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15573
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15574
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15575
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15576
|
+
// instances that match, and folds its status from those.
|
|
15577
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15578
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15579
|
+
q: external_exports.string().optional(),
|
|
15580
|
+
sessionId: external_exports.string().optional(),
|
|
15581
|
+
from: external_exports.iso.datetime().optional(),
|
|
15582
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15583
|
+
});
|
|
15584
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15585
|
+
totals: external_exports.object({
|
|
15586
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15587
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15588
|
+
files: external_exports.number().int().nonnegative()
|
|
15589
|
+
}),
|
|
15590
|
+
/** Sorted by max severity, then most recent. */
|
|
15591
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15592
|
+
/** Whether `limit` truncated the repo list. */
|
|
15593
|
+
hasMore: external_exports.boolean()
|
|
15594
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15458
15595
|
|
|
15459
15596
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15460
|
-
var Harness = external_exports.enum([
|
|
15597
|
+
var Harness = external_exports.enum([
|
|
15598
|
+
"claudecode",
|
|
15599
|
+
"cursor",
|
|
15600
|
+
"copilot",
|
|
15601
|
+
"codex",
|
|
15602
|
+
"antigravity",
|
|
15603
|
+
"windsurf",
|
|
15604
|
+
"claudedesktop",
|
|
15605
|
+
"chatgpt",
|
|
15606
|
+
"claudeai",
|
|
15607
|
+
"api"
|
|
15608
|
+
]).meta({ id: "Harness" });
|
|
15461
15609
|
var TOOL_TO_HARNESS = {
|
|
15462
15610
|
"claude-code": "claudecode",
|
|
15463
15611
|
"claude-desktop": "claudedesktop",
|
|
15464
15612
|
"github-copilot": "copilot",
|
|
15465
15613
|
cursor: "cursor",
|
|
15466
|
-
chatgpt: "chatgpt"
|
|
15614
|
+
chatgpt: "chatgpt",
|
|
15615
|
+
codex: "codex",
|
|
15616
|
+
antigravity: "antigravity",
|
|
15617
|
+
"claude-ai": "claudeai"
|
|
15467
15618
|
};
|
|
15468
15619
|
|
|
15469
15620
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15921,7 +16072,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15921
16072
|
// ../../packages/schema/src/zod/event.ts
|
|
15922
16073
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15923
16074
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15924
|
-
var SourceTool = external_exports.enum([
|
|
16075
|
+
var SourceTool = external_exports.enum([
|
|
16076
|
+
"claude-code",
|
|
16077
|
+
"claude-desktop",
|
|
16078
|
+
"cursor",
|
|
16079
|
+
"chatgpt",
|
|
16080
|
+
"claude-ai",
|
|
16081
|
+
"github-copilot",
|
|
16082
|
+
"codex",
|
|
16083
|
+
"antigravity",
|
|
16084
|
+
"cli",
|
|
16085
|
+
"unknown"
|
|
16086
|
+
]).meta({ id: "SourceTool" });
|
|
15925
16087
|
var EventMetadata = external_exports.object({
|
|
15926
16088
|
sessionId: external_exports.string().optional(),
|
|
15927
16089
|
repo: external_exports.string().optional(),
|
|
@@ -15992,7 +16154,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
15992
16154
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
15993
16155
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
15994
16156
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
15995
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16157
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
15996
16158
|
var AccessCounts = external_exports.object({
|
|
15997
16159
|
open: external_exports.number().int().nonnegative(),
|
|
15998
16160
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16214,6 +16376,7 @@ var ExceptionConditions = external_exports.object({
|
|
|
16214
16376
|
sourceTool: external_exports.string().optional(),
|
|
16215
16377
|
provider: external_exports.string().optional()
|
|
16216
16378
|
}).strict();
|
|
16379
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16217
16380
|
var DetectionException = external_exports.object({
|
|
16218
16381
|
id: external_exports.guid(),
|
|
16219
16382
|
ruleId: external_exports.string(),
|
|
@@ -16230,6 +16393,7 @@ var DetectionException = external_exports.object({
|
|
|
16230
16393
|
keyVersion: external_exports.number().int().positive(),
|
|
16231
16394
|
// maskMatch() preview of the approved value — never the raw value.
|
|
16232
16395
|
maskedValue: external_exports.string(),
|
|
16396
|
+
capability: ExceptionCapability.default("suppress"),
|
|
16233
16397
|
scope: ExceptionScope,
|
|
16234
16398
|
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16235
16399
|
maxUses: external_exports.number().int().positive().nullable(),
|
|
@@ -16253,11 +16417,13 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16253
16417
|
ruleId: true,
|
|
16254
16418
|
valueFingerprint: true,
|
|
16255
16419
|
keyVersion: true,
|
|
16420
|
+
capability: true,
|
|
16256
16421
|
expiresAt: true,
|
|
16257
16422
|
maxUses: true,
|
|
16258
16423
|
useCount: true,
|
|
16259
16424
|
conditions: true
|
|
16260
16425
|
});
|
|
16426
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16261
16427
|
|
|
16262
16428
|
// ../../packages/schema/src/zod/rule.ts
|
|
16263
16429
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17073,6 +17239,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17073
17239
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17074
17240
|
}).meta({ id: "EgressWriteSummary" });
|
|
17075
17241
|
|
|
17242
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17243
|
+
var confirmation = external_exports.string().optional();
|
|
17244
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17245
|
+
reference: external_exports.string(),
|
|
17246
|
+
scope: external_exports.string(),
|
|
17247
|
+
reason: external_exports.string(),
|
|
17248
|
+
confirmation
|
|
17249
|
+
});
|
|
17250
|
+
var AddExceptionInput = external_exports.object({
|
|
17251
|
+
ruleId: external_exports.string(),
|
|
17252
|
+
value: external_exports.string(),
|
|
17253
|
+
scope: external_exports.string(),
|
|
17254
|
+
reason: external_exports.string(),
|
|
17255
|
+
confirmation
|
|
17256
|
+
});
|
|
17257
|
+
var GrantRevealInput = external_exports.object({
|
|
17258
|
+
pointer: external_exports.string(),
|
|
17259
|
+
scope: external_exports.string(),
|
|
17260
|
+
justification: external_exports.string(),
|
|
17261
|
+
confirmation
|
|
17262
|
+
});
|
|
17263
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17264
|
+
id: external_exports.string(),
|
|
17265
|
+
reason: external_exports.string()
|
|
17266
|
+
});
|
|
17267
|
+
var RotateKeyInput = external_exports.object({
|
|
17268
|
+
confirmation: external_exports.string()
|
|
17269
|
+
});
|
|
17270
|
+
|
|
17076
17271
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17077
17272
|
function toApiAction(dbVal) {
|
|
17078
17273
|
const map2 = {
|
|
@@ -17128,6 +17323,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17128
17323
|
repo: r.repo,
|
|
17129
17324
|
file: r.file,
|
|
17130
17325
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17326
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17327
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17131
17328
|
action: toApiAction(effectiveDbAction),
|
|
17132
17329
|
detectedAt: r.occurredAt,
|
|
17133
17330
|
confidence: r.confidence,
|
|
@@ -17259,14 +17456,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17259
17456
|
}
|
|
17260
17457
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17261
17458
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17459
|
+
function compareFindingGroupOrder(a, b) {
|
|
17460
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17461
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17462
|
+
const severityDiff = rankA - rankB;
|
|
17463
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17464
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17465
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17466
|
+
return a.id.localeCompare(b.id);
|
|
17467
|
+
}
|
|
17262
17468
|
function sortFindingGroups(groups) {
|
|
17263
|
-
return [...groups].sort(
|
|
17264
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17265
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17266
|
-
const severityDiff = rankA - rankB;
|
|
17267
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17268
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17269
|
-
});
|
|
17469
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17270
17470
|
}
|
|
17271
17471
|
function computeFindingFacets(allGroups, opts) {
|
|
17272
17472
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17322,15 +17522,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17322
17522
|
for (const g of forStatus) {
|
|
17323
17523
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17324
17524
|
}
|
|
17325
|
-
const
|
|
17525
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17526
|
+
return {
|
|
17527
|
+
severity: toItems2(severityMap),
|
|
17528
|
+
provider: toItems2(providerMap),
|
|
17529
|
+
action: toItems2(actionMap),
|
|
17530
|
+
subtype: toItems2(subtypeMap),
|
|
17531
|
+
status: toItems2(statusMap)
|
|
17532
|
+
};
|
|
17533
|
+
}
|
|
17534
|
+
|
|
17535
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17536
|
+
function rowHaystack(row) {
|
|
17537
|
+
return [
|
|
17538
|
+
row.ruleId,
|
|
17539
|
+
row.category,
|
|
17540
|
+
row.maskedMatch,
|
|
17541
|
+
row.repo,
|
|
17542
|
+
row.file,
|
|
17543
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17544
|
+
row.id
|
|
17545
|
+
].join(" ").toLowerCase();
|
|
17546
|
+
}
|
|
17547
|
+
function matchesDimension(row, opts, dimension) {
|
|
17548
|
+
switch (dimension) {
|
|
17549
|
+
case "severity":
|
|
17550
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17551
|
+
case "subtype":
|
|
17552
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17553
|
+
case "providers":
|
|
17554
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17555
|
+
case "actions":
|
|
17556
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17557
|
+
case "statuses":
|
|
17558
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17559
|
+
case "tools":
|
|
17560
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17561
|
+
case "repo":
|
|
17562
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17563
|
+
case "file":
|
|
17564
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17565
|
+
case "q":
|
|
17566
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17567
|
+
}
|
|
17568
|
+
}
|
|
17569
|
+
var DIMENSIONS = [
|
|
17570
|
+
"severity",
|
|
17571
|
+
"subtype",
|
|
17572
|
+
"providers",
|
|
17573
|
+
"actions",
|
|
17574
|
+
"statuses",
|
|
17575
|
+
"tools",
|
|
17576
|
+
"repo",
|
|
17577
|
+
"file",
|
|
17578
|
+
"q"
|
|
17579
|
+
];
|
|
17580
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17581
|
+
for (const dimension of DIMENSIONS) {
|
|
17582
|
+
if (dimension === except) continue;
|
|
17583
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17584
|
+
}
|
|
17585
|
+
return true;
|
|
17586
|
+
}
|
|
17587
|
+
function toItems(counts) {
|
|
17588
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17589
|
+
}
|
|
17590
|
+
function bump(counts, value) {
|
|
17591
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17592
|
+
}
|
|
17593
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17594
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17595
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17596
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17597
|
+
const action = /* @__PURE__ */ new Map();
|
|
17598
|
+
const status = /* @__PURE__ */ new Map();
|
|
17599
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17326
17600
|
return {
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17601
|
+
add(row) {
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17603
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17604
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17605
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17606
|
+
}
|
|
17607
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17608
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17609
|
+
bump(status, row.status);
|
|
17610
|
+
}
|
|
17611
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17612
|
+
bump(tool, row.toolName);
|
|
17613
|
+
}
|
|
17614
|
+
},
|
|
17615
|
+
facets: () => ({
|
|
17616
|
+
severity: toItems(severity),
|
|
17617
|
+
subtype: toItems(subtype),
|
|
17618
|
+
provider: toItems(provider),
|
|
17619
|
+
action: toItems(action),
|
|
17620
|
+
status: toItems(status),
|
|
17621
|
+
tool: toItems(tool)
|
|
17622
|
+
})
|
|
17332
17623
|
};
|
|
17333
17624
|
}
|
|
17625
|
+
function toInstanceDetail(row) {
|
|
17626
|
+
const category = toApiCategory(row.category);
|
|
17627
|
+
return {
|
|
17628
|
+
id: row.id,
|
|
17629
|
+
provider: toApiProvider(row.sourceTool),
|
|
17630
|
+
repo: row.repo,
|
|
17631
|
+
file: row.file,
|
|
17632
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17633
|
+
eventId: row.eventId,
|
|
17634
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17635
|
+
action: toApiAction(row.actionTaken),
|
|
17636
|
+
detectedAt: row.occurredAt,
|
|
17637
|
+
confidence: row.confidence,
|
|
17638
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17639
|
+
groupId: row.ruleId,
|
|
17640
|
+
category,
|
|
17641
|
+
subtype: row.ruleId,
|
|
17642
|
+
severity: row.severity,
|
|
17643
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17644
|
+
detection: { id: row.ruleId, name: null },
|
|
17645
|
+
policy: { id: `category:${category}`, name: category }
|
|
17646
|
+
};
|
|
17647
|
+
}
|
|
17648
|
+
var SEVERITY_ORDER2 = {
|
|
17649
|
+
critical: 0,
|
|
17650
|
+
high: 1,
|
|
17651
|
+
medium: 2,
|
|
17652
|
+
low: 3
|
|
17653
|
+
};
|
|
17654
|
+
function newLocationAccumulator() {
|
|
17655
|
+
return {
|
|
17656
|
+
instanceCount: 0,
|
|
17657
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17658
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17659
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17660
|
+
maxSeverity: "low",
|
|
17661
|
+
latestDetectedAt: "",
|
|
17662
|
+
statuses: [],
|
|
17663
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17664
|
+
};
|
|
17665
|
+
}
|
|
17666
|
+
function addToLocation(acc, row) {
|
|
17667
|
+
acc.instanceCount += 1;
|
|
17668
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17669
|
+
if (rank < acc.maxSeverityRank) {
|
|
17670
|
+
acc.maxSeverityRank = rank;
|
|
17671
|
+
acc.maxSeverity = row.severity;
|
|
17672
|
+
}
|
|
17673
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17674
|
+
acc.statuses.push(row.status);
|
|
17675
|
+
acc.ruleIds.add(row.ruleId);
|
|
17676
|
+
}
|
|
17334
17677
|
|
|
17335
17678
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17336
17679
|
var InstalledPack = external_exports.object({
|
|
@@ -17362,8 +17705,162 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17362
17705
|
message: "At least one field must be provided"
|
|
17363
17706
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17364
17707
|
|
|
17708
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17709
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17710
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17711
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17712
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17713
|
+
);
|
|
17714
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17715
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17716
|
+
var ParsedPointer = external_exports.object({
|
|
17717
|
+
category: DetectionCategory,
|
|
17718
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17719
|
+
pointerId: external_exports.string(),
|
|
17720
|
+
tag: external_exports.string()
|
|
17721
|
+
});
|
|
17722
|
+
var VaultEntry = external_exports.object({
|
|
17723
|
+
pointerId: external_exports.string(),
|
|
17724
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17725
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17726
|
+
// independently of the vault encryption key below.
|
|
17727
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17728
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17729
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17730
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17731
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17732
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17733
|
+
// value always produces exactly one wire token.
|
|
17734
|
+
category: DetectionCategory,
|
|
17735
|
+
ruleId: external_exports.string(),
|
|
17736
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17737
|
+
maskedMatch: external_exports.string(),
|
|
17738
|
+
provider: external_exports.string().optional(),
|
|
17739
|
+
ciphertext: external_exports.string(),
|
|
17740
|
+
nonce: external_exports.string(),
|
|
17741
|
+
authTag: external_exports.string(),
|
|
17742
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17743
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17744
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17745
|
+
firstSeen: external_exports.string(),
|
|
17746
|
+
lastSeen: external_exports.string()
|
|
17747
|
+
});
|
|
17748
|
+
var PointerDescriptor = external_exports.object({
|
|
17749
|
+
category: DetectionCategory,
|
|
17750
|
+
provider: external_exports.string().optional(),
|
|
17751
|
+
maskedMatch: external_exports.string(),
|
|
17752
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17753
|
+
firstSeen: external_exports.string(),
|
|
17754
|
+
lastSeen: external_exports.string()
|
|
17755
|
+
});
|
|
17756
|
+
var PointerIdentity = external_exports.object({
|
|
17757
|
+
ruleId: external_exports.string(),
|
|
17758
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17759
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17760
|
+
});
|
|
17761
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17762
|
+
var VaultDerefReason = external_exports.enum([
|
|
17763
|
+
"display",
|
|
17764
|
+
"explicit-reveal",
|
|
17765
|
+
"view-render",
|
|
17766
|
+
"model-input",
|
|
17767
|
+
"remediation",
|
|
17768
|
+
"purge"
|
|
17769
|
+
]);
|
|
17770
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17771
|
+
var VaultDeref = external_exports.object({
|
|
17772
|
+
id: external_exports.guid(),
|
|
17773
|
+
pointerId: external_exports.string(),
|
|
17774
|
+
at: external_exports.string(),
|
|
17775
|
+
target: DetokenizeTarget,
|
|
17776
|
+
reason: VaultDerefReason,
|
|
17777
|
+
outcome: VaultDerefOutcome,
|
|
17778
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17779
|
+
grantId: external_exports.string().optional(),
|
|
17780
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17781
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17782
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17783
|
+
});
|
|
17784
|
+
var VaultSightingKind = external_exports.enum([
|
|
17785
|
+
"prompt",
|
|
17786
|
+
"tool-input",
|
|
17787
|
+
"tool-output",
|
|
17788
|
+
"file",
|
|
17789
|
+
"transcript"
|
|
17790
|
+
]);
|
|
17791
|
+
var VaultSighting = external_exports.object({
|
|
17792
|
+
location: external_exports.string(),
|
|
17793
|
+
kind: VaultSightingKind,
|
|
17794
|
+
firstSeen: external_exports.string(),
|
|
17795
|
+
lastSeen: external_exports.string()
|
|
17796
|
+
});
|
|
17797
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17798
|
+
pointerId: external_exports.string(),
|
|
17799
|
+
category: DetectionCategory,
|
|
17800
|
+
provider: external_exports.string().optional(),
|
|
17801
|
+
maskedMatch: external_exports.string(),
|
|
17802
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17803
|
+
firstSeen: external_exports.string(),
|
|
17804
|
+
lastSeen: external_exports.string(),
|
|
17805
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17806
|
+
// the inventory badges it, the row links to revocation.
|
|
17807
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17808
|
+
sightings: external_exports.array(VaultSighting)
|
|
17809
|
+
});
|
|
17810
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17811
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17812
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17813
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17814
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17815
|
+
// Opaque; names the last row of the page just served.
|
|
17816
|
+
cursor: external_exports.string().optional()
|
|
17817
|
+
});
|
|
17818
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17819
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17820
|
+
// independent, so paging never changes what the count claims.
|
|
17821
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17822
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17823
|
+
// `null` once the last page is reached.
|
|
17824
|
+
nextCursor: external_exports.string().nullable()
|
|
17825
|
+
});
|
|
17826
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17827
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17828
|
+
cursor: external_exports.string().optional()
|
|
17829
|
+
});
|
|
17830
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17831
|
+
// Reused values across the whole store — the number the section's claim
|
|
17832
|
+
// ("values detected in more than one place") is about.
|
|
17833
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17834
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17835
|
+
nextCursor: external_exports.string().nullable()
|
|
17836
|
+
});
|
|
17837
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17838
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17839
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17840
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17841
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17842
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17843
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17844
|
+
cursor: external_exports.string().optional()
|
|
17845
|
+
});
|
|
17846
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17847
|
+
items: external_exports.array(VaultDeref),
|
|
17848
|
+
nextCursor: external_exports.string().nullable(),
|
|
17849
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17850
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17851
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17852
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17853
|
+
});
|
|
17854
|
+
var VaultKeyCustody = external_exports.string();
|
|
17855
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17856
|
+
var VAULT_CONSENT_VERSION = 1;
|
|
17857
|
+
var VaultConsent = external_exports.object({
|
|
17858
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17859
|
+
version: external_exports.number().int().positive()
|
|
17860
|
+
});
|
|
17861
|
+
|
|
17365
17862
|
// ../../packages/schema/src/zod/local.ts
|
|
17366
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17863
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17367
17864
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
17368
17865
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17369
17866
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
@@ -17386,6 +17883,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17386
17883
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17387
17884
|
// Shares writes.
|
|
17388
17885
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17886
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17887
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17888
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17889
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17890
|
+
// purging the vault is the eraser.
|
|
17891
|
+
vaultConsent: VaultConsent.optional(),
|
|
17892
|
+
// Where the vault master key lives.
|
|
17893
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17894
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17895
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17389
17896
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17390
17897
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17391
17898
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -17718,7 +18225,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17718
18225
|
// Omit for both kinds.
|
|
17719
18226
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17720
18227
|
});
|
|
17721
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18228
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17722
18229
|
var ScanCoverageProvider = external_exports.object({
|
|
17723
18230
|
provider: Provider,
|
|
17724
18231
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -17960,6 +18467,155 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
17960
18467
|
);
|
|
17961
18468
|
}
|
|
17962
18469
|
|
|
18470
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18471
|
+
import { randomUUID } from "crypto";
|
|
18472
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18473
|
+
import { basename, dirname, join } from "path";
|
|
18474
|
+
|
|
18475
|
+
// ../../packages/persistence/src/paths.ts
|
|
18476
|
+
import {
|
|
18477
|
+
chmodSync,
|
|
18478
|
+
linkSync,
|
|
18479
|
+
lstatSync,
|
|
18480
|
+
mkdirSync,
|
|
18481
|
+
renameSync,
|
|
18482
|
+
rmSync,
|
|
18483
|
+
writeFileSync
|
|
18484
|
+
} from "fs";
|
|
18485
|
+
import { threadId } from "worker_threads";
|
|
18486
|
+
var DATA_DIR_MODE = 448;
|
|
18487
|
+
var DATA_FILE_MODE = 384;
|
|
18488
|
+
var DB_FILENAME = "aka.db";
|
|
18489
|
+
function isSymlink(path) {
|
|
18490
|
+
try {
|
|
18491
|
+
return lstatSync(path).isSymbolicLink();
|
|
18492
|
+
} catch {
|
|
18493
|
+
return false;
|
|
18494
|
+
}
|
|
18495
|
+
}
|
|
18496
|
+
function chmodBestEffort(path, mode) {
|
|
18497
|
+
if (isSymlink(path)) return;
|
|
18498
|
+
try {
|
|
18499
|
+
chmodSync(path, mode);
|
|
18500
|
+
} catch {
|
|
18501
|
+
}
|
|
18502
|
+
}
|
|
18503
|
+
function tightenDir(dir) {
|
|
18504
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18505
|
+
}
|
|
18506
|
+
function ensureDataDirSync(dir) {
|
|
18507
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18508
|
+
tightenDir(dir);
|
|
18509
|
+
}
|
|
18510
|
+
function dbSidecars(file2) {
|
|
18511
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18512
|
+
}
|
|
18513
|
+
function tightenFile(file2) {
|
|
18514
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18515
|
+
}
|
|
18516
|
+
function tightenPerms(file2) {
|
|
18517
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18518
|
+
}
|
|
18519
|
+
function writeOwnerOnlyFileSync(file2, data) {
|
|
18520
|
+
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18521
|
+
try {
|
|
18522
|
+
rmSync(tmp, { force: true });
|
|
18523
|
+
} catch {
|
|
18524
|
+
}
|
|
18525
|
+
try {
|
|
18526
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18527
|
+
renameSync(tmp, file2);
|
|
18528
|
+
} finally {
|
|
18529
|
+
try {
|
|
18530
|
+
rmSync(tmp, { force: true });
|
|
18531
|
+
} catch {
|
|
18532
|
+
}
|
|
18533
|
+
}
|
|
18534
|
+
tightenFile(file2);
|
|
18535
|
+
}
|
|
18536
|
+
|
|
18537
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18538
|
+
function backupPath(file2, tag) {
|
|
18539
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18540
|
+
}
|
|
18541
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18542
|
+
function reapStalePartials(file2) {
|
|
18543
|
+
const dir = dirname(file2);
|
|
18544
|
+
const prefix = `${basename(file2)}.`;
|
|
18545
|
+
let entries;
|
|
18546
|
+
try {
|
|
18547
|
+
entries = readdirSync(dir);
|
|
18548
|
+
} catch {
|
|
18549
|
+
return;
|
|
18550
|
+
}
|
|
18551
|
+
for (const name of entries) {
|
|
18552
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18553
|
+
const partial2 = join(dir, name);
|
|
18554
|
+
try {
|
|
18555
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18556
|
+
rmSync2(partial2, { force: true });
|
|
18557
|
+
}
|
|
18558
|
+
} catch {
|
|
18559
|
+
}
|
|
18560
|
+
}
|
|
18561
|
+
}
|
|
18562
|
+
function snapshotStore(db, backup) {
|
|
18563
|
+
const partial2 = `${backup}.partial`;
|
|
18564
|
+
try {
|
|
18565
|
+
rmSync2(partial2, { force: true });
|
|
18566
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18567
|
+
tightenFile(partial2);
|
|
18568
|
+
renameSync2(partial2, backup);
|
|
18569
|
+
} catch (error51) {
|
|
18570
|
+
try {
|
|
18571
|
+
rmSync2(partial2, { force: true });
|
|
18572
|
+
} catch {
|
|
18573
|
+
}
|
|
18574
|
+
throw error51;
|
|
18575
|
+
}
|
|
18576
|
+
}
|
|
18577
|
+
function moveStoreAside(file2, backup) {
|
|
18578
|
+
const undo = [];
|
|
18579
|
+
renameSync2(file2, backup);
|
|
18580
|
+
undo.push([backup, file2]);
|
|
18581
|
+
try {
|
|
18582
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18583
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18584
|
+
try {
|
|
18585
|
+
renameSync2(sidecar, moved);
|
|
18586
|
+
undo.push([moved, sidecar]);
|
|
18587
|
+
} catch {
|
|
18588
|
+
rmSync2(sidecar, { force: true });
|
|
18589
|
+
}
|
|
18590
|
+
}
|
|
18591
|
+
} catch (error51) {
|
|
18592
|
+
for (const [from, to] of undo.reverse()) {
|
|
18593
|
+
try {
|
|
18594
|
+
renameSync2(from, to);
|
|
18595
|
+
} catch {
|
|
18596
|
+
}
|
|
18597
|
+
}
|
|
18598
|
+
throw error51;
|
|
18599
|
+
}
|
|
18600
|
+
tightenPerms(backup);
|
|
18601
|
+
}
|
|
18602
|
+
function discardStore(file2, backup) {
|
|
18603
|
+
try {
|
|
18604
|
+
rmSync2(file2, { force: true });
|
|
18605
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18606
|
+
rmSync2(sidecar, { force: true });
|
|
18607
|
+
}
|
|
18608
|
+
} catch (error51) {
|
|
18609
|
+
if (existsSync(file2)) {
|
|
18610
|
+
try {
|
|
18611
|
+
rmSync2(backup, { force: true });
|
|
18612
|
+
} catch {
|
|
18613
|
+
}
|
|
18614
|
+
}
|
|
18615
|
+
throw error51;
|
|
18616
|
+
}
|
|
18617
|
+
}
|
|
18618
|
+
|
|
17963
18619
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
17964
18620
|
function escapeLikePattern(s) {
|
|
17965
18621
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18101,65 +18757,16 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18101
18757
|
return out;
|
|
18102
18758
|
}
|
|
18103
18759
|
|
|
18104
|
-
// ../../packages/persistence/src/
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
var DATA_FILE_MODE = 384;
|
|
18108
|
-
var DB_FILENAME = "aka.db";
|
|
18109
|
-
function chmodBestEffort(path, mode) {
|
|
18110
|
-
try {
|
|
18111
|
-
chmodSync(path, mode);
|
|
18112
|
-
} catch {
|
|
18113
|
-
}
|
|
18114
|
-
}
|
|
18115
|
-
function tightenDir(dir) {
|
|
18116
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18117
|
-
}
|
|
18118
|
-
function ensureDataDirSync(dir) {
|
|
18119
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18120
|
-
tightenDir(dir);
|
|
18121
|
-
}
|
|
18122
|
-
function dbSidecars(file2) {
|
|
18123
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18760
|
+
// ../../packages/persistence/src/migrations.ts
|
|
18761
|
+
function describeObject(object2) {
|
|
18762
|
+
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
18124
18763
|
}
|
|
18125
|
-
function
|
|
18126
|
-
|
|
18127
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18128
|
-
} catch {
|
|
18129
|
-
}
|
|
18130
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18764
|
+
function splitStatements(sql) {
|
|
18765
|
+
return sql.split(/-->\s*statement-breakpoint/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
18131
18766
|
}
|
|
18132
|
-
function
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18136
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18137
|
-
try {
|
|
18138
|
-
rmSync(tmp, { force: true });
|
|
18139
|
-
} catch {
|
|
18140
|
-
}
|
|
18141
|
-
try {
|
|
18142
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18143
|
-
renameSync(tmp, file2);
|
|
18144
|
-
} finally {
|
|
18145
|
-
try {
|
|
18146
|
-
rmSync(tmp, { force: true });
|
|
18147
|
-
} catch {
|
|
18148
|
-
}
|
|
18149
|
-
}
|
|
18150
|
-
tightenFile(file2);
|
|
18151
|
-
}
|
|
18152
|
-
|
|
18153
|
-
// ../../packages/persistence/src/migrations.ts
|
|
18154
|
-
function describeObject(object2) {
|
|
18155
|
-
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
18156
|
-
}
|
|
18157
|
-
function splitStatements(sql) {
|
|
18158
|
-
return sql.split(/-->\s*statement-breakpoint/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
18159
|
-
}
|
|
18160
|
-
function createdIndexName(statement) {
|
|
18161
|
-
const body = statement.replace(/^(?:\s*--[^\n]*\n?)+/, "").trimStart();
|
|
18162
|
-
return /^CREATE (?:UNIQUE )?INDEX (?:IF NOT EXISTS )?`([^`]+)`/.exec(body)?.[1];
|
|
18767
|
+
function createdIndexName(statement) {
|
|
18768
|
+
const body = statement.replace(/^(?:\s*--[^\n]*\n?)+/, "").trimStart();
|
|
18769
|
+
return /^CREATE (?:UNIQUE )?INDEX (?:IF NOT EXISTS )?`([^`]+)`/.exec(body)?.[1];
|
|
18163
18770
|
}
|
|
18164
18771
|
var LEGACY_DROP_MIGRATION_TAG = "0014_drop_legacy_events_findings";
|
|
18165
18772
|
function applyMigrations(db, file2) {
|
|
@@ -18265,9 +18872,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18265
18872
|
}
|
|
18266
18873
|
}
|
|
18267
18874
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18268
|
-
|
|
18269
|
-
|
|
18270
|
-
|
|
18875
|
+
reapStalePartials(file2);
|
|
18876
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18877
|
+
snapshotStore(db, backup);
|
|
18271
18878
|
return backup;
|
|
18272
18879
|
}
|
|
18273
18880
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18611,6 +19218,25 @@ function parseJsonObject(s) {
|
|
|
18611
19218
|
return void 0;
|
|
18612
19219
|
}
|
|
18613
19220
|
|
|
19221
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19222
|
+
function encodeKeysetCursor(payload) {
|
|
19223
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19224
|
+
}
|
|
19225
|
+
function decodeKeysetCursor(cursor) {
|
|
19226
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19227
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19228
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19229
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19230
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19231
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19232
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19233
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19234
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19235
|
+
return parsed;
|
|
19236
|
+
}
|
|
19237
|
+
return null;
|
|
19238
|
+
}
|
|
19239
|
+
|
|
18614
19240
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18615
19241
|
var DAY_MS = 864e5;
|
|
18616
19242
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18656,16 +19282,6 @@ function utcWindow(nowMs) {
|
|
|
18656
19282
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18657
19283
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18658
19284
|
}
|
|
18659
|
-
function encodeCursor(payload) {
|
|
18660
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18661
|
-
}
|
|
18662
|
-
function decodeCursor(cursor) {
|
|
18663
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18664
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18665
|
-
return parsed;
|
|
18666
|
-
}
|
|
18667
|
-
return null;
|
|
18668
|
-
}
|
|
18669
19285
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18670
19286
|
session: "session",
|
|
18671
19287
|
prompt: "prompt",
|
|
@@ -18810,7 +19426,7 @@ var SqliteActivityRepository = class {
|
|
|
18810
19426
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18811
19427
|
}
|
|
18812
19428
|
listSessions(query) {
|
|
18813
|
-
const cursor = query.cursor ?
|
|
19429
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18814
19430
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18815
19431
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18816
19432
|
const conditions = [SESSION_ROOT];
|
|
@@ -18884,7 +19500,7 @@ var SqliteActivityRepository = class {
|
|
|
18884
19500
|
)
|
|
18885
19501
|
);
|
|
18886
19502
|
const last = page[page.length - 1];
|
|
18887
|
-
const nextCursor = hasMore && last ?
|
|
19503
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
18888
19504
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
18889
19505
|
}
|
|
18890
19506
|
getSession(sessionId) {
|
|
@@ -19757,7 +20373,7 @@ var SqliteEventsRepository = class {
|
|
|
19757
20373
|
};
|
|
19758
20374
|
|
|
19759
20375
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19760
|
-
import { randomUUID } from "crypto";
|
|
20376
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19761
20377
|
|
|
19762
20378
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19763
20379
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19789,9 +20405,13 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19789
20405
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19790
20406
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19791
20407
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
20408
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
20409
|
+
AND conditions IS NULL
|
|
20410
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19792
20411
|
var SqliteExceptionsRepository = class {
|
|
19793
|
-
constructor(db) {
|
|
20412
|
+
constructor(db, now = () => Date.now()) {
|
|
19794
20413
|
this.db = db;
|
|
20414
|
+
this.now = now;
|
|
19795
20415
|
this.consumeStmt = db.prepare(
|
|
19796
20416
|
`UPDATE exceptions
|
|
19797
20417
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19809,6 +20429,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19809
20429
|
);
|
|
19810
20430
|
}
|
|
19811
20431
|
db;
|
|
20432
|
+
now;
|
|
19812
20433
|
consumeStmt;
|
|
19813
20434
|
insertBlockedStmt;
|
|
19814
20435
|
sweepBlockedStmt;
|
|
@@ -19835,8 +20456,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19835
20456
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19836
20457
|
);
|
|
19837
20458
|
}
|
|
19838
|
-
const id =
|
|
19839
|
-
const now =
|
|
20459
|
+
const id = randomUUID2();
|
|
20460
|
+
const now = this.now();
|
|
19840
20461
|
try {
|
|
19841
20462
|
this.insertExceptionRow(id, input, now);
|
|
19842
20463
|
} catch (err) {
|
|
@@ -19880,11 +20501,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19880
20501
|
this.db.prepare(
|
|
19881
20502
|
`INSERT INTO exceptions (
|
|
19882
20503
|
id, rule_id, category, value_fingerprint, key_version, masked_value,
|
|
19883
|
-
scope, expires_at, max_uses, use_count, last_used_at,
|
|
19884
|
-
conditions, created_by, created_via, created_at, updated_at
|
|
20504
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20505
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19885
20506
|
) VALUES (
|
|
19886
20507
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19887
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20508
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19888
20509
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19889
20510
|
)`
|
|
19890
20511
|
).run({
|
|
@@ -19894,6 +20515,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19894
20515
|
valueFingerprint: input.valueFingerprint,
|
|
19895
20516
|
keyVersion: input.keyVersion,
|
|
19896
20517
|
maskedValue: input.maskedValue,
|
|
20518
|
+
capability: input.capability ?? "suppress",
|
|
19897
20519
|
scope: input.scope,
|
|
19898
20520
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19899
20521
|
maxUses: input.maxUses,
|
|
@@ -19913,7 +20535,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19913
20535
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
19914
20536
|
const rows = allRows(
|
|
19915
20537
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
19916
|
-
opts?.includeTerminal ? {} : { now:
|
|
20538
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
19917
20539
|
);
|
|
19918
20540
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
19919
20541
|
return Promise.resolve(exceptions);
|
|
@@ -19948,7 +20570,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19948
20570
|
* already revoked.
|
|
19949
20571
|
*/
|
|
19950
20572
|
revoke(id, revokedBy, reason) {
|
|
19951
|
-
const now =
|
|
20573
|
+
const now = this.now();
|
|
19952
20574
|
const result = this.db.prepare(
|
|
19953
20575
|
`UPDATE exceptions
|
|
19954
20576
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -19962,7 +20584,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19962
20584
|
* callers must treat identically — means it does not and the detection is
|
|
19963
20585
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
19964
20586
|
*/
|
|
19965
|
-
consume(id, now =
|
|
20587
|
+
consume(id, now = this.now()) {
|
|
19966
20588
|
const result = this.consumeStmt.run({ id, now });
|
|
19967
20589
|
return Promise.resolve(Number(result.changes) === 1);
|
|
19968
20590
|
}
|
|
@@ -19971,7 +20593,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19971
20593
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
19972
20594
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
19973
20595
|
*/
|
|
19974
|
-
activeBundleEntries(keyVersion, now =
|
|
20596
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
19975
20597
|
const rows = allRows(
|
|
19976
20598
|
this.db.prepare(
|
|
19977
20599
|
`SELECT * FROM exceptions
|
|
@@ -19987,6 +20609,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19987
20609
|
ruleId: row.rule_id,
|
|
19988
20610
|
valueFingerprint: row.value_fingerprint,
|
|
19989
20611
|
keyVersion: row.key_version,
|
|
20612
|
+
capability: row.capability,
|
|
19990
20613
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
19991
20614
|
maxUses: row.max_uses,
|
|
19992
20615
|
useCount: row.use_count,
|
|
@@ -20002,7 +20625,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20002
20625
|
* than the retention window on every write, so the ledger self-limits.
|
|
20003
20626
|
*/
|
|
20004
20627
|
recordBlocked(entry) {
|
|
20005
|
-
const now =
|
|
20628
|
+
const now = this.now();
|
|
20006
20629
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20007
20630
|
this.insertBlockedStmt.run({
|
|
20008
20631
|
reference: entry.reference,
|
|
@@ -20025,7 +20648,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20025
20648
|
WHERE blocked_at > :cutoff
|
|
20026
20649
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20027
20650
|
),
|
|
20028
|
-
{ cutoff:
|
|
20651
|
+
{ cutoff: this.now() - windowMs }
|
|
20029
20652
|
);
|
|
20030
20653
|
return Promise.resolve(
|
|
20031
20654
|
rows.map((row) => ({
|
|
@@ -20041,6 +20664,36 @@ var SqliteExceptionsRepository = class {
|
|
|
20041
20664
|
}))
|
|
20042
20665
|
);
|
|
20043
20666
|
}
|
|
20667
|
+
/**
|
|
20668
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20669
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20670
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20671
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20672
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20673
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20674
|
+
*
|
|
20675
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20676
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20677
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20678
|
+
*/
|
|
20679
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20680
|
+
try {
|
|
20681
|
+
const at = now ?? this.now();
|
|
20682
|
+
const row = getRow(
|
|
20683
|
+
this.db.prepare(
|
|
20684
|
+
`SELECT id FROM exceptions
|
|
20685
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20686
|
+
AND key_version = :keyVersion
|
|
20687
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20688
|
+
LIMIT 1`
|
|
20689
|
+
),
|
|
20690
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20691
|
+
);
|
|
20692
|
+
return Promise.resolve(row ?? null);
|
|
20693
|
+
} catch (err) {
|
|
20694
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20695
|
+
}
|
|
20696
|
+
}
|
|
20044
20697
|
/**
|
|
20045
20698
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20046
20699
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20048,7 +20701,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20048
20701
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20049
20702
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20050
20703
|
*/
|
|
20051
|
-
sweepTerminal(retentionMs, now =
|
|
20704
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20052
20705
|
const result = this.db.prepare(
|
|
20053
20706
|
`DELETE FROM exceptions
|
|
20054
20707
|
WHERE updated_at < :cutoff
|
|
@@ -20068,6 +20721,7 @@ function parseExceptionRow(row) {
|
|
|
20068
20721
|
valueFingerprint: row.value_fingerprint,
|
|
20069
20722
|
keyVersion: row.key_version,
|
|
20070
20723
|
maskedValue: row.masked_value,
|
|
20724
|
+
capability: row.capability,
|
|
20071
20725
|
scope: row.scope,
|
|
20072
20726
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20073
20727
|
maxUses: row.max_uses,
|
|
@@ -20110,6 +20764,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20110
20764
|
|
|
20111
20765
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20112
20766
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20767
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20768
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20769
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20770
|
+
function compareLocationOrder(a, b) {
|
|
20771
|
+
return compareFindingGroupOrder(
|
|
20772
|
+
{
|
|
20773
|
+
severity: a.maxSeverity,
|
|
20774
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20775
|
+
id: ""
|
|
20776
|
+
},
|
|
20777
|
+
{
|
|
20778
|
+
severity: b.maxSeverity,
|
|
20779
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20780
|
+
id: ""
|
|
20781
|
+
}
|
|
20782
|
+
);
|
|
20783
|
+
}
|
|
20113
20784
|
var CONCAT_SEP = ",";
|
|
20114
20785
|
var TUPLE_SEP = "|";
|
|
20115
20786
|
function splitConcat(value) {
|
|
@@ -20122,6 +20793,33 @@ function deriveInstanceStatus(row) {
|
|
|
20122
20793
|
latestResolutionStatus: row.latest_status
|
|
20123
20794
|
});
|
|
20124
20795
|
}
|
|
20796
|
+
function encodeGroupCursor(group) {
|
|
20797
|
+
const payload = {
|
|
20798
|
+
sev: group.severity,
|
|
20799
|
+
t: group.latestDetectedAt,
|
|
20800
|
+
id: group.id
|
|
20801
|
+
};
|
|
20802
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20803
|
+
}
|
|
20804
|
+
function decodeGroupCursor(cursor) {
|
|
20805
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20806
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20807
|
+
return {
|
|
20808
|
+
severity: parsed.sev,
|
|
20809
|
+
latestDetectedAt: parsed.t,
|
|
20810
|
+
id: parsed.id
|
|
20811
|
+
};
|
|
20812
|
+
}
|
|
20813
|
+
return null;
|
|
20814
|
+
}
|
|
20815
|
+
function firstAfter(sorted, cursor) {
|
|
20816
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20817
|
+
return index === -1 ? sorted.length : index;
|
|
20818
|
+
}
|
|
20819
|
+
function findDeepLinked(sorted, page, id) {
|
|
20820
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20821
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20822
|
+
}
|
|
20125
20823
|
var DAY_MS3 = 864e5;
|
|
20126
20824
|
var SqliteFindingsRepository = class {
|
|
20127
20825
|
constructor(db) {
|
|
@@ -20231,8 +20929,13 @@ var SqliteFindingsRepository = class {
|
|
|
20231
20929
|
*/
|
|
20232
20930
|
listGroupedFindings(query) {
|
|
20233
20931
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20234
|
-
const
|
|
20235
|
-
const
|
|
20932
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20933
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20934
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20935
|
+
const sessionParams = {
|
|
20936
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20937
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20938
|
+
};
|
|
20236
20939
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20237
20940
|
predicate,
|
|
20238
20941
|
params: sessionParams
|
|
@@ -20240,7 +20943,8 @@ var SqliteFindingsRepository = class {
|
|
|
20240
20943
|
const rows = allRows(
|
|
20241
20944
|
this.db.prepare(
|
|
20242
20945
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20243
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20946
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20947
|
+
kind, finding_key, latest_status
|
|
20244
20948
|
FROM (
|
|
20245
20949
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20246
20950
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20250,6 +20954,7 @@ var SqliteFindingsRepository = class {
|
|
|
20250
20954
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20251
20955
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20252
20956
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
20957
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20253
20958
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20254
20959
|
latest.status AS latest_status,
|
|
20255
20960
|
ROW_NUMBER() OVER (
|
|
@@ -20281,6 +20986,8 @@ var SqliteFindingsRepository = class {
|
|
|
20281
20986
|
repo: r.repo ?? "",
|
|
20282
20987
|
file: r.file ?? "",
|
|
20283
20988
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
20989
|
+
eventId: r.event_id,
|
|
20990
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20284
20991
|
status: deriveInstanceStatus(r)
|
|
20285
20992
|
}));
|
|
20286
20993
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20304,18 +21011,23 @@ var SqliteFindingsRepository = class {
|
|
|
20304
21011
|
groups: sorted.length
|
|
20305
21012
|
};
|
|
20306
21013
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21014
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21015
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21016
|
+
const page = sorted.slice(start, start + limit);
|
|
21017
|
+
const lastOnPage = page.at(-1);
|
|
21018
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21019
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20307
21020
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20308
|
-
const
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
);
|
|
21021
|
+
const narrow = (g) => statusSet ? {
|
|
21022
|
+
...g,
|
|
21023
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21024
|
+
} : g;
|
|
21025
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20314
21026
|
return Promise.resolve({
|
|
20315
21027
|
totals,
|
|
20316
21028
|
facets,
|
|
20317
21029
|
items,
|
|
20318
|
-
nextCursor
|
|
21030
|
+
nextCursor,
|
|
20319
21031
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20320
21032
|
});
|
|
20321
21033
|
}
|
|
@@ -20347,6 +21059,266 @@ var SqliteFindingsRepository = class {
|
|
|
20347
21059
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20348
21060
|
* path repeating across tuples.)
|
|
20349
21061
|
*/
|
|
21062
|
+
/**
|
|
21063
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21064
|
+
* paged by keyset.
|
|
21065
|
+
*
|
|
21066
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21067
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21068
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21069
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21070
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21071
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21072
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21073
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21074
|
+
* none of the mappers names", which no IN-list can say.
|
|
21075
|
+
*
|
|
21076
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21077
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21078
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21079
|
+
* while the counting runs, and only the page itself is retained.
|
|
21080
|
+
*/
|
|
21081
|
+
listFindingInstances(query) {
|
|
21082
|
+
const opts = {
|
|
21083
|
+
severity: query.severity,
|
|
21084
|
+
subtype: query.subtype,
|
|
21085
|
+
providers: query.provider,
|
|
21086
|
+
actions: query.action,
|
|
21087
|
+
statuses: query.status,
|
|
21088
|
+
tools: query.tool,
|
|
21089
|
+
repo: query.repo,
|
|
21090
|
+
file: query.file,
|
|
21091
|
+
q: query.q
|
|
21092
|
+
};
|
|
21093
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21094
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21095
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21096
|
+
const items = [];
|
|
21097
|
+
let total = 0;
|
|
21098
|
+
let last;
|
|
21099
|
+
let hasMore = false;
|
|
21100
|
+
for (const row of this.scanFindingRows({
|
|
21101
|
+
sessionId: query.sessionId,
|
|
21102
|
+
from: query.from
|
|
21103
|
+
})) {
|
|
21104
|
+
accumulator.add(row);
|
|
21105
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21106
|
+
total += 1;
|
|
21107
|
+
if (items.length < limit) {
|
|
21108
|
+
items.push(toInstanceDetail(row));
|
|
21109
|
+
last = row;
|
|
21110
|
+
} else {
|
|
21111
|
+
hasMore = true;
|
|
21112
|
+
}
|
|
21113
|
+
}
|
|
21114
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21115
|
+
if (cursor !== null) {
|
|
21116
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21117
|
+
return Promise.resolve({
|
|
21118
|
+
totals: { findings: total },
|
|
21119
|
+
facets: accumulator.facets(),
|
|
21120
|
+
items: resumed.items,
|
|
21121
|
+
nextCursor: resumed.nextCursor
|
|
21122
|
+
});
|
|
21123
|
+
}
|
|
21124
|
+
return Promise.resolve({
|
|
21125
|
+
totals: { findings: total },
|
|
21126
|
+
facets: accumulator.facets(),
|
|
21127
|
+
items,
|
|
21128
|
+
nextCursor
|
|
21129
|
+
});
|
|
21130
|
+
}
|
|
21131
|
+
/**
|
|
21132
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21133
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21134
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21135
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21136
|
+
*/
|
|
21137
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21138
|
+
const items = [];
|
|
21139
|
+
let last;
|
|
21140
|
+
let hasMore = false;
|
|
21141
|
+
for (const row of this.scanFindingRows({
|
|
21142
|
+
sessionId: query.sessionId,
|
|
21143
|
+
from: query.from,
|
|
21144
|
+
after: cursor
|
|
21145
|
+
})) {
|
|
21146
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21147
|
+
if (items.length < limit) {
|
|
21148
|
+
items.push(toInstanceDetail(row));
|
|
21149
|
+
last = row;
|
|
21150
|
+
} else {
|
|
21151
|
+
hasMore = true;
|
|
21152
|
+
break;
|
|
21153
|
+
}
|
|
21154
|
+
}
|
|
21155
|
+
return {
|
|
21156
|
+
items,
|
|
21157
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21158
|
+
};
|
|
21159
|
+
}
|
|
21160
|
+
/**
|
|
21161
|
+
* The same findings folded by location: repository, then file within it.
|
|
21162
|
+
*
|
|
21163
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21164
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21165
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21166
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21167
|
+
* filter can name it.
|
|
21168
|
+
*/
|
|
21169
|
+
listFindingLocations(query) {
|
|
21170
|
+
const opts = {
|
|
21171
|
+
severity: query.severity,
|
|
21172
|
+
subtype: query.subtype,
|
|
21173
|
+
providers: query.provider,
|
|
21174
|
+
actions: query.action,
|
|
21175
|
+
statuses: query.status,
|
|
21176
|
+
tools: query.tool,
|
|
21177
|
+
q: query.q
|
|
21178
|
+
};
|
|
21179
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21180
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21181
|
+
let total = 0;
|
|
21182
|
+
for (const row of this.scanFindingRows({
|
|
21183
|
+
sessionId: query.sessionId,
|
|
21184
|
+
from: query.from
|
|
21185
|
+
})) {
|
|
21186
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21187
|
+
total += 1;
|
|
21188
|
+
let files = byRepo.get(row.repo);
|
|
21189
|
+
if (files === void 0) {
|
|
21190
|
+
files = /* @__PURE__ */ new Map();
|
|
21191
|
+
byRepo.set(row.repo, files);
|
|
21192
|
+
}
|
|
21193
|
+
let acc = files.get(row.file);
|
|
21194
|
+
if (acc === void 0) {
|
|
21195
|
+
acc = newLocationAccumulator();
|
|
21196
|
+
files.set(row.file, acc);
|
|
21197
|
+
}
|
|
21198
|
+
addToLocation(acc, row);
|
|
21199
|
+
}
|
|
21200
|
+
let fileCount = 0;
|
|
21201
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21202
|
+
fileCount += files.size;
|
|
21203
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21204
|
+
file: file2,
|
|
21205
|
+
instanceCount: acc.instanceCount,
|
|
21206
|
+
maxSeverity: acc.maxSeverity,
|
|
21207
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21208
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21209
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21210
|
+
})).sort(compareLocationOrder);
|
|
21211
|
+
const rollup = fileRows.reduce(
|
|
21212
|
+
(a, f) => ({
|
|
21213
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21214
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21215
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21216
|
+
}),
|
|
21217
|
+
{
|
|
21218
|
+
instanceCount: 0,
|
|
21219
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21220
|
+
latestDetectedAt: ""
|
|
21221
|
+
}
|
|
21222
|
+
);
|
|
21223
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21224
|
+
const folded = foldGroupStatus(statuses);
|
|
21225
|
+
return {
|
|
21226
|
+
repo,
|
|
21227
|
+
instanceCount: rollup.instanceCount,
|
|
21228
|
+
maxSeverity: rollup.maxSeverity,
|
|
21229
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21230
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21231
|
+
files: fileRows
|
|
21232
|
+
};
|
|
21233
|
+
});
|
|
21234
|
+
repos.sort(compareLocationOrder);
|
|
21235
|
+
return Promise.resolve({
|
|
21236
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21237
|
+
items: repos.slice(0, limit),
|
|
21238
|
+
hasMore: repos.length > limit
|
|
21239
|
+
});
|
|
21240
|
+
}
|
|
21241
|
+
/**
|
|
21242
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21243
|
+
*
|
|
21244
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21245
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21246
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21247
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21248
|
+
* rather than one unbounded result set.
|
|
21249
|
+
*
|
|
21250
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21251
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21252
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21253
|
+
* a window over the whole resolution table once per batch.
|
|
21254
|
+
*
|
|
21255
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21256
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21257
|
+
* dimension — see listFindingInstances.
|
|
21258
|
+
*/
|
|
21259
|
+
*scanFindingRows(scope) {
|
|
21260
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21261
|
+
const params = [];
|
|
21262
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21263
|
+
conditions.push("e.root_session_id = ?");
|
|
21264
|
+
params.push(scope.sessionId);
|
|
21265
|
+
}
|
|
21266
|
+
if (scope.from !== void 0) {
|
|
21267
|
+
conditions.push("e.started_at >= ?");
|
|
21268
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21269
|
+
}
|
|
21270
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21271
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21272
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21273
|
+
e.started_at AS occurred_at,
|
|
21274
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21275
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21276
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21277
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21278
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21279
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21280
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21281
|
+
FROM inspection_findings f
|
|
21282
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21283
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21284
|
+
WHERE ${conditions.join(" AND ")}
|
|
21285
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21286
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21287
|
+
LIMIT ?`;
|
|
21288
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21289
|
+
for (; ; ) {
|
|
21290
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21291
|
+
...params,
|
|
21292
|
+
after.startedAtMs,
|
|
21293
|
+
after.startedAtMs,
|
|
21294
|
+
after.id,
|
|
21295
|
+
SCAN_BATCH_ROWS
|
|
21296
|
+
]);
|
|
21297
|
+
for (const r of rows) {
|
|
21298
|
+
yield {
|
|
21299
|
+
id: r.id,
|
|
21300
|
+
ruleId: r.rule_id,
|
|
21301
|
+
category: r.category,
|
|
21302
|
+
severity: r.severity,
|
|
21303
|
+
maskedMatch: r.masked_match,
|
|
21304
|
+
actionTaken: r.action_taken,
|
|
21305
|
+
confidence: r.confidence,
|
|
21306
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21307
|
+
sourceTool: r.source_tool,
|
|
21308
|
+
repo: r.repo ?? "",
|
|
21309
|
+
file: r.file ?? "",
|
|
21310
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21311
|
+
eventId: r.event_id,
|
|
21312
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21313
|
+
status: deriveInstanceStatus(r)
|
|
21314
|
+
};
|
|
21315
|
+
}
|
|
21316
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21317
|
+
const lastRow = rows[rows.length - 1];
|
|
21318
|
+
if (lastRow === void 0) return;
|
|
21319
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21320
|
+
}
|
|
21321
|
+
}
|
|
20350
21322
|
groupAggregates(withSearchText, scope) {
|
|
20351
21323
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20352
21324
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20607,7 +21579,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20607
21579
|
};
|
|
20608
21580
|
|
|
20609
21581
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20610
|
-
import { createHash as createHash2, randomUUID as
|
|
21582
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20611
21583
|
|
|
20612
21584
|
// ../../packages/persistence/src/semver.ts
|
|
20613
21585
|
function parse3(version2) {
|
|
@@ -20758,7 +21730,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20758
21730
|
let behind = false;
|
|
20759
21731
|
for (const row of rows) {
|
|
20760
21732
|
const params = {
|
|
20761
|
-
id:
|
|
21733
|
+
id: randomUUID3(),
|
|
20762
21734
|
namespace: row.namespace,
|
|
20763
21735
|
packId: row.packId,
|
|
20764
21736
|
version: row.version,
|
|
@@ -20770,7 +21742,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20770
21742
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20771
21743
|
this.upsertAvailableStmt.run({
|
|
20772
21744
|
...params,
|
|
20773
|
-
id:
|
|
21745
|
+
id: randomUUID3(),
|
|
20774
21746
|
recordedBy: meta3?.recordedBy ?? null
|
|
20775
21747
|
});
|
|
20776
21748
|
} else {
|
|
@@ -21093,14 +22065,15 @@ var SqliteInventoryRepository = class {
|
|
|
21093
22065
|
};
|
|
21094
22066
|
|
|
21095
22067
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21096
|
-
import { randomUUID as
|
|
22068
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21097
22069
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21098
22070
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21099
22071
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21100
22072
|
var HARNESS_LABELS = {
|
|
21101
22073
|
claudecode: "Claude Code",
|
|
21102
22074
|
cursor: "Cursor",
|
|
21103
|
-
codex: "Codex"
|
|
22075
|
+
codex: "Codex",
|
|
22076
|
+
antigravity: "Antigravity"
|
|
21104
22077
|
};
|
|
21105
22078
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21106
22079
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21115,6 +22088,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21115
22088
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21116
22089
|
if (t.includes("cursor")) return "cursor";
|
|
21117
22090
|
if (t.includes("codex")) return "codex";
|
|
22091
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21118
22092
|
return null;
|
|
21119
22093
|
}
|
|
21120
22094
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21573,7 +22547,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21573
22547
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21574
22548
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21575
22549
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21576
|
-
).run({ id:
|
|
22550
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21577
22551
|
}
|
|
21578
22552
|
return true;
|
|
21579
22553
|
}
|
|
@@ -21594,7 +22568,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21594
22568
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21595
22569
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21596
22570
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21597
|
-
).run({ id:
|
|
22571
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21598
22572
|
}
|
|
21599
22573
|
this.configRowsCache = void 0;
|
|
21600
22574
|
return "ok";
|
|
@@ -21891,7 +22865,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21891
22865
|
};
|
|
21892
22866
|
|
|
21893
22867
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
21894
|
-
import { randomUUID as
|
|
22868
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
21895
22869
|
var SqlitePoliciesRepository = class {
|
|
21896
22870
|
constructor(db) {
|
|
21897
22871
|
this.db = db;
|
|
@@ -21926,7 +22900,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21926
22900
|
failOpenTransaction(this.db, () => {
|
|
21927
22901
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
21928
22902
|
stmt.run({
|
|
21929
|
-
id:
|
|
22903
|
+
id: randomUUID5(),
|
|
21930
22904
|
target: JSON.stringify({ category }),
|
|
21931
22905
|
action,
|
|
21932
22906
|
now: Date.now()
|
|
@@ -21946,7 +22920,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21946
22920
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
21947
22921
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
21948
22922
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
21949
|
-
).run({ id:
|
|
22923
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
21950
22924
|
}
|
|
21951
22925
|
// Caps every global per-category policy currently set to block/redact down
|
|
21952
22926
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22014,7 +22988,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22014
22988
|
};
|
|
22015
22989
|
|
|
22016
22990
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22017
|
-
import { randomUUID as
|
|
22991
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22018
22992
|
var SqliteProjectFilesRepository = class {
|
|
22019
22993
|
constructor(db) {
|
|
22020
22994
|
this.db = db;
|
|
@@ -22046,7 +23020,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22046
23020
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22047
23021
|
for (const file2 of scan2.files) {
|
|
22048
23022
|
this.upsertStmt.run({
|
|
22049
|
-
id:
|
|
23023
|
+
id: randomUUID6(),
|
|
22050
23024
|
projectId,
|
|
22051
23025
|
path: file2.path,
|
|
22052
23026
|
name: file2.name,
|
|
@@ -22060,7 +23034,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22060
23034
|
};
|
|
22061
23035
|
|
|
22062
23036
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22063
|
-
import { randomUUID as
|
|
23037
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22064
23038
|
var SqliteResolutionsRepository = class {
|
|
22065
23039
|
constructor(db, now = () => Date.now()) {
|
|
22066
23040
|
this.db = db;
|
|
@@ -22114,7 +23088,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22114
23088
|
*/
|
|
22115
23089
|
insertResolution(r) {
|
|
22116
23090
|
this.insertStmt.run({
|
|
22117
|
-
id:
|
|
23091
|
+
id: randomUUID7(),
|
|
22118
23092
|
findingKey: r.findingKey,
|
|
22119
23093
|
status: FindingStatus.parse(r.status),
|
|
22120
23094
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22173,13 +23147,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22173
23147
|
this.readStmt = db.prepare(
|
|
22174
23148
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22175
23149
|
);
|
|
23150
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23151
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23152
|
+
);
|
|
23153
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23154
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23155
|
+
);
|
|
22176
23156
|
}
|
|
22177
23157
|
db;
|
|
22178
23158
|
upsertStmt;
|
|
22179
23159
|
readStmt;
|
|
23160
|
+
countQuarantinedStmt;
|
|
23161
|
+
clearQuarantinedStmt;
|
|
22180
23162
|
getVerdict(ruleKey) {
|
|
22181
23163
|
return getRow(this.readStmt, { ruleKey });
|
|
22182
23164
|
}
|
|
23165
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23166
|
+
countQuarantined() {
|
|
23167
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23168
|
+
}
|
|
23169
|
+
/**
|
|
23170
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23171
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23172
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23173
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23174
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23175
|
+
*
|
|
23176
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23177
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23178
|
+
*
|
|
23179
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23180
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23181
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23182
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23183
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23184
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23185
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23186
|
+
* to restore are silently still disabled.
|
|
23187
|
+
*/
|
|
23188
|
+
clearQuarantined() {
|
|
23189
|
+
const before = this.countQuarantined();
|
|
23190
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23191
|
+
this.clearQuarantinedStmt.run();
|
|
23192
|
+
});
|
|
23193
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23194
|
+
}
|
|
22183
23195
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22184
23196
|
failOpenTransaction(this.db, () => {
|
|
22185
23197
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22233,6 +23245,419 @@ var SqliteScanLedgerRepository = class {
|
|
|
22233
23245
|
}
|
|
22234
23246
|
};
|
|
22235
23247
|
|
|
23248
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
23249
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23250
|
+
function pageLimit(requested, fallback) {
|
|
23251
|
+
if (requested === void 0) return fallback;
|
|
23252
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23253
|
+
}
|
|
23254
|
+
function encodeReuseCursor(payload) {
|
|
23255
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23256
|
+
}
|
|
23257
|
+
function decodeReuseCursor(cursor) {
|
|
23258
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23259
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23260
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23261
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23262
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23263
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23264
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23265
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23266
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23267
|
+
}
|
|
23268
|
+
return null;
|
|
23269
|
+
}
|
|
23270
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23271
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23272
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23273
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23274
|
+
function toSighting(row) {
|
|
23275
|
+
return {
|
|
23276
|
+
location: row.location,
|
|
23277
|
+
kind: row.kind,
|
|
23278
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23279
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23280
|
+
};
|
|
23281
|
+
}
|
|
23282
|
+
var SELECT_COLUMNS = `
|
|
23283
|
+
pointer_id AS pointerId,
|
|
23284
|
+
value_fingerprint AS valueFingerprint,
|
|
23285
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
23286
|
+
key_version AS keyVersion,
|
|
23287
|
+
format_version AS formatVersion,
|
|
23288
|
+
category,
|
|
23289
|
+
rule_id AS ruleId,
|
|
23290
|
+
masked_match AS maskedMatch,
|
|
23291
|
+
provider,
|
|
23292
|
+
ciphertext,
|
|
23293
|
+
nonce,
|
|
23294
|
+
auth_tag AS authTag,
|
|
23295
|
+
occurrence_count AS occurrenceCount,
|
|
23296
|
+
first_seen AS firstSeen,
|
|
23297
|
+
last_seen AS lastSeen`;
|
|
23298
|
+
function toRow(raw) {
|
|
23299
|
+
const { provider, ...rest } = raw;
|
|
23300
|
+
return provider === null ? rest : { ...rest, provider };
|
|
23301
|
+
}
|
|
23302
|
+
var SqliteSecretVaultRepository = class {
|
|
23303
|
+
constructor(db) {
|
|
23304
|
+
this.db = db;
|
|
23305
|
+
this.insertStmt = db.prepare(
|
|
23306
|
+
`INSERT INTO secret_vault (
|
|
23307
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
23308
|
+
format_version, category, rule_id, masked_match, provider,
|
|
23309
|
+
ciphertext, nonce, auth_tag,
|
|
23310
|
+
occurrence_count, first_seen, last_seen
|
|
23311
|
+
) VALUES (
|
|
23312
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
23313
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
23314
|
+
:ciphertext, :nonce, :authTag,
|
|
23315
|
+
1, :now, :now
|
|
23316
|
+
)`
|
|
23317
|
+
);
|
|
23318
|
+
this.bumpStmt = db.prepare(
|
|
23319
|
+
`UPDATE secret_vault
|
|
23320
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
23321
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
23322
|
+
);
|
|
23323
|
+
this.byPointerStmt = db.prepare(
|
|
23324
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
23325
|
+
);
|
|
23326
|
+
this.byFingerprintStmt = db.prepare(
|
|
23327
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
23328
|
+
);
|
|
23329
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
23330
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
23331
|
+
`UPDATE secret_vault
|
|
23332
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
23333
|
+
WHERE pointer_id = :pointerId`
|
|
23334
|
+
);
|
|
23335
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
23336
|
+
`UPDATE secret_vault
|
|
23337
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
23338
|
+
WHERE pointer_id = :pointerId`
|
|
23339
|
+
);
|
|
23340
|
+
this.derefStmt = db.prepare(
|
|
23341
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
23342
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
23343
|
+
);
|
|
23344
|
+
}
|
|
23345
|
+
db;
|
|
23346
|
+
insertStmt;
|
|
23347
|
+
bumpStmt;
|
|
23348
|
+
byPointerStmt;
|
|
23349
|
+
byFingerprintStmt;
|
|
23350
|
+
listStmt;
|
|
23351
|
+
replaceCiphertextStmt;
|
|
23352
|
+
refreshFingerprintStmt;
|
|
23353
|
+
derefStmt;
|
|
23354
|
+
/**
|
|
23355
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
23356
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
23357
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
23358
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
23359
|
+
* wire token. `minted` is true only when this call created the row.
|
|
23360
|
+
*
|
|
23361
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
23362
|
+
* writers cannot both decide they are minting.
|
|
23363
|
+
*/
|
|
23364
|
+
upsert(input, now) {
|
|
23365
|
+
let minted = false;
|
|
23366
|
+
withTransaction(
|
|
23367
|
+
this.db,
|
|
23368
|
+
() => {
|
|
23369
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
23370
|
+
valueFingerprint: input.valueFingerprint
|
|
23371
|
+
});
|
|
23372
|
+
if (existing === void 0) {
|
|
23373
|
+
this.insertStmt.run(
|
|
23374
|
+
bindParams({
|
|
23375
|
+
pointerId: input.pointerId,
|
|
23376
|
+
valueFingerprint: input.valueFingerprint,
|
|
23377
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
23378
|
+
keyVersion: input.keyVersion,
|
|
23379
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
23380
|
+
category: input.category,
|
|
23381
|
+
ruleId: input.ruleId,
|
|
23382
|
+
maskedMatch: input.maskedMatch,
|
|
23383
|
+
provider: input.provider,
|
|
23384
|
+
ciphertext: input.ciphertext,
|
|
23385
|
+
nonce: input.nonce,
|
|
23386
|
+
authTag: input.authTag,
|
|
23387
|
+
now
|
|
23388
|
+
})
|
|
23389
|
+
);
|
|
23390
|
+
minted = true;
|
|
23391
|
+
return;
|
|
23392
|
+
}
|
|
23393
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
23394
|
+
},
|
|
23395
|
+
"IMMEDIATE"
|
|
23396
|
+
);
|
|
23397
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
23398
|
+
valueFingerprint: input.valueFingerprint
|
|
23399
|
+
});
|
|
23400
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
23401
|
+
return { row: toRow(row), minted };
|
|
23402
|
+
}
|
|
23403
|
+
byPointerId(pointerId) {
|
|
23404
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
23405
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23406
|
+
}
|
|
23407
|
+
byValueFingerprint(fingerprint) {
|
|
23408
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
23409
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23410
|
+
}
|
|
23411
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
23412
|
+
recordDeref(entry) {
|
|
23413
|
+
this.derefStmt.run(
|
|
23414
|
+
bindParams({
|
|
23415
|
+
id: entry.id,
|
|
23416
|
+
pointerId: entry.pointerId,
|
|
23417
|
+
at: entry.at,
|
|
23418
|
+
target: entry.target,
|
|
23419
|
+
reason: entry.reason,
|
|
23420
|
+
outcome: entry.outcome,
|
|
23421
|
+
grantId: entry.grantId,
|
|
23422
|
+
pointerCount: entry.pointerCount ?? 1
|
|
23423
|
+
})
|
|
23424
|
+
);
|
|
23425
|
+
}
|
|
23426
|
+
listAll() {
|
|
23427
|
+
return allRows(this.listStmt).map(toRow);
|
|
23428
|
+
}
|
|
23429
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
23430
|
+
replaceCiphertext(pointerId, next) {
|
|
23431
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
23432
|
+
}
|
|
23433
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
23434
|
+
refreshFingerprint(pointerId, next) {
|
|
23435
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
23436
|
+
}
|
|
23437
|
+
/**
|
|
23438
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
23439
|
+
* audit is left alone on purpose — see the table note above.
|
|
23440
|
+
*/
|
|
23441
|
+
purgeAll() {
|
|
23442
|
+
let destroyed = 0;
|
|
23443
|
+
withTransaction(
|
|
23444
|
+
this.db,
|
|
23445
|
+
() => {
|
|
23446
|
+
destroyed = this.countEntries();
|
|
23447
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
23448
|
+
},
|
|
23449
|
+
"IMMEDIATE"
|
|
23450
|
+
);
|
|
23451
|
+
return destroyed;
|
|
23452
|
+
}
|
|
23453
|
+
/**
|
|
23454
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
23455
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
23456
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
23457
|
+
* so callers wrap this, not the other way around.
|
|
23458
|
+
*/
|
|
23459
|
+
recordSighting(entry, now) {
|
|
23460
|
+
this.db.prepare(
|
|
23461
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
23462
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
23463
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
23464
|
+
).run({
|
|
23465
|
+
id: randomUUID8(),
|
|
23466
|
+
pointerId: entry.pointerId,
|
|
23467
|
+
location: entry.location,
|
|
23468
|
+
kind: entry.kind,
|
|
23469
|
+
now
|
|
23470
|
+
});
|
|
23471
|
+
}
|
|
23472
|
+
/**
|
|
23473
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23474
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23475
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23476
|
+
*
|
|
23477
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23478
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23479
|
+
*/
|
|
23480
|
+
sightingsFor(pointerIds) {
|
|
23481
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23482
|
+
if (pointerIds.length === 0) return byPointer;
|
|
23483
|
+
const rows = allRows(
|
|
23484
|
+
this.db.prepare(
|
|
23485
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23486
|
+
FROM secret_vault_sighting
|
|
23487
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23488
|
+
ORDER BY last_seen DESC`
|
|
23489
|
+
),
|
|
23490
|
+
pointerIds
|
|
23491
|
+
);
|
|
23492
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23493
|
+
return byPointer;
|
|
23494
|
+
}
|
|
23495
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23496
|
+
toInventoryEntries(rows) {
|
|
23497
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
23498
|
+
return rows.map((r) => ({
|
|
23499
|
+
pointerId: r.pointer_id,
|
|
23500
|
+
category: r.category,
|
|
23501
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23502
|
+
maskedMatch: r.masked_match,
|
|
23503
|
+
occurrences: r.occurrence_count,
|
|
23504
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
23505
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23506
|
+
revealGrantId: r.grant_id,
|
|
23507
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
23508
|
+
}));
|
|
23509
|
+
}
|
|
23510
|
+
/**
|
|
23511
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23512
|
+
* value's descriptor data joined with its sightings and the active
|
|
23513
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23514
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23515
|
+
*
|
|
23516
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23517
|
+
* sees never depends on how far they have paged.
|
|
23518
|
+
*/
|
|
23519
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23520
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23521
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23522
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
23523
|
+
const rows = allRows(
|
|
23524
|
+
this.db.prepare(
|
|
23525
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23526
|
+
(SELECT e.id FROM exceptions e
|
|
23527
|
+
WHERE e.rule_id = v.rule_id
|
|
23528
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23529
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23530
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23531
|
+
LIMIT 1) AS grant_id
|
|
23532
|
+
FROM secret_vault v
|
|
23533
|
+
${where}
|
|
23534
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23535
|
+
LIMIT :limit`
|
|
23536
|
+
),
|
|
23537
|
+
bindParams({
|
|
23538
|
+
now,
|
|
23539
|
+
limit: limit + 1,
|
|
23540
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23541
|
+
})
|
|
23542
|
+
);
|
|
23543
|
+
const hasMore = rows.length > limit;
|
|
23544
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23545
|
+
const last = page[page.length - 1];
|
|
23546
|
+
return {
|
|
23547
|
+
totals: { values: this.countEntries() },
|
|
23548
|
+
items: this.toInventoryEntries(page),
|
|
23549
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23550
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23551
|
+
};
|
|
23552
|
+
}
|
|
23553
|
+
/**
|
|
23554
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23555
|
+
* than one location — most-reused first, one page at a time.
|
|
23556
|
+
*
|
|
23557
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23558
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23559
|
+
* under-report exactly the values a reader most needs to see.
|
|
23560
|
+
*/
|
|
23561
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23562
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23563
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23564
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23565
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23566
|
+
const rows = allRows(
|
|
23567
|
+
this.db.prepare(
|
|
23568
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23569
|
+
(SELECT e.id FROM exceptions e
|
|
23570
|
+
WHERE e.rule_id = v.rule_id
|
|
23571
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23572
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23573
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23574
|
+
LIMIT 1) AS grant_id
|
|
23575
|
+
FROM secret_vault v
|
|
23576
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23577
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23578
|
+
LIMIT :limit`
|
|
23579
|
+
),
|
|
23580
|
+
bindParams({
|
|
23581
|
+
now,
|
|
23582
|
+
limit: limit + 1,
|
|
23583
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23584
|
+
})
|
|
23585
|
+
);
|
|
23586
|
+
const hasMore = rows.length > limit;
|
|
23587
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23588
|
+
const last = page[page.length - 1];
|
|
23589
|
+
return {
|
|
23590
|
+
totals: { reused: this.countReused() },
|
|
23591
|
+
items: this.toInventoryEntries(page),
|
|
23592
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23593
|
+
};
|
|
23594
|
+
}
|
|
23595
|
+
/**
|
|
23596
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23597
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23598
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23599
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23600
|
+
*
|
|
23601
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23602
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23603
|
+
* the reader pages.
|
|
23604
|
+
*/
|
|
23605
|
+
listDerefs(query = {}) {
|
|
23606
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23607
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23608
|
+
const conditions = [];
|
|
23609
|
+
if (query.includeBatched !== true) {
|
|
23610
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23611
|
+
}
|
|
23612
|
+
if (cursor !== null) {
|
|
23613
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23614
|
+
}
|
|
23615
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
23616
|
+
const rows = allRows(
|
|
23617
|
+
this.db.prepare(
|
|
23618
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
23619
|
+
FROM secret_vault_deref ${where}
|
|
23620
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
23621
|
+
),
|
|
23622
|
+
bindParams({
|
|
23623
|
+
limit: limit + 1,
|
|
23624
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23625
|
+
})
|
|
23626
|
+
);
|
|
23627
|
+
const hasMore = rows.length > limit;
|
|
23628
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23629
|
+
const last = page[page.length - 1];
|
|
23630
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
23631
|
+
this.db,
|
|
23632
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
23633
|
+
);
|
|
23634
|
+
return {
|
|
23635
|
+
items: page.map((r) => ({
|
|
23636
|
+
id: r.id,
|
|
23637
|
+
pointerId: r.pointer_id,
|
|
23638
|
+
at: new Date(r.at).toISOString(),
|
|
23639
|
+
target: r.target,
|
|
23640
|
+
reason: r.reason,
|
|
23641
|
+
outcome: r.outcome,
|
|
23642
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
23643
|
+
pointerCount: r.pointer_count
|
|
23644
|
+
})),
|
|
23645
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
23646
|
+
hiddenBatched
|
|
23647
|
+
};
|
|
23648
|
+
}
|
|
23649
|
+
countEntries() {
|
|
23650
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
23651
|
+
}
|
|
23652
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23653
|
+
countReused() {
|
|
23654
|
+
return countScalar(
|
|
23655
|
+
this.db,
|
|
23656
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23657
|
+
);
|
|
23658
|
+
}
|
|
23659
|
+
};
|
|
23660
|
+
|
|
22236
23661
|
// ../../packages/persistence/src/repositories/security.ts
|
|
22237
23662
|
var DAY_MS4 = 864e5;
|
|
22238
23663
|
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
@@ -22245,7 +23670,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22245
23670
|
var SCAN_COVERAGE = [
|
|
22246
23671
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22247
23672
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22248
|
-
{ provider: "codex", coverage:
|
|
23673
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23674
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23675
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22249
23676
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22250
23677
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22251
23678
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -22578,7 +24005,7 @@ var SqliteSecurityRepository = class {
|
|
|
22578
24005
|
};
|
|
22579
24006
|
|
|
22580
24007
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22581
|
-
import { randomUUID as
|
|
24008
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
22582
24009
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22583
24010
|
var IN_CHUNK = 500;
|
|
22584
24011
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22834,7 +24261,7 @@ var SqliteSharesRepository = class {
|
|
|
22834
24261
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22835
24262
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22836
24263
|
).run({
|
|
22837
|
-
id:
|
|
24264
|
+
id: randomUUID9(),
|
|
22838
24265
|
destinationId,
|
|
22839
24266
|
host: dest.host,
|
|
22840
24267
|
decision,
|
|
@@ -22983,7 +24410,7 @@ var SqliteSharesRepository = class {
|
|
|
22983
24410
|
let destinationId = destIds.get(hit.host);
|
|
22984
24411
|
if (destinationId === void 0) {
|
|
22985
24412
|
destStmt.run({
|
|
22986
|
-
id:
|
|
24413
|
+
id: randomUUID9(),
|
|
22987
24414
|
kind: hit.kind,
|
|
22988
24415
|
name: hit.name,
|
|
22989
24416
|
host: hit.host,
|
|
@@ -22999,7 +24426,7 @@ var SqliteSharesRepository = class {
|
|
|
22999
24426
|
let endpointId = endpointIds.get(endpointKey);
|
|
23000
24427
|
if (endpointId === void 0) {
|
|
23001
24428
|
endpointStmt.run({
|
|
23002
|
-
id:
|
|
24429
|
+
id: randomUUID9(),
|
|
23003
24430
|
destinationId,
|
|
23004
24431
|
method: hit.method,
|
|
23005
24432
|
transport: hit.transport,
|
|
@@ -23012,7 +24439,7 @@ var SqliteSharesRepository = class {
|
|
|
23012
24439
|
endpointIds.set(endpointKey, endpointId);
|
|
23013
24440
|
}
|
|
23014
24441
|
siteStmt.run({
|
|
23015
|
-
id:
|
|
24442
|
+
id: randomUUID9(),
|
|
23016
24443
|
endpointId,
|
|
23017
24444
|
project: input.project,
|
|
23018
24445
|
projectKey: input.projectKey,
|
|
@@ -23377,6 +24804,9 @@ function purgeSampleData(db) {
|
|
|
23377
24804
|
}
|
|
23378
24805
|
|
|
23379
24806
|
// ../../packages/persistence/src/database.ts
|
|
24807
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24808
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24809
|
+
);
|
|
23380
24810
|
function linkHost(input, hostId) {
|
|
23381
24811
|
return hostId ? { ...input, hostId } : input;
|
|
23382
24812
|
}
|
|
@@ -23398,21 +24828,34 @@ function openWithPragmas(file2) {
|
|
|
23398
24828
|
}
|
|
23399
24829
|
return db;
|
|
23400
24830
|
}
|
|
23401
|
-
function backupLegacyStore(file2) {
|
|
23402
|
-
|
|
23403
|
-
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
24831
|
+
function backupLegacyStore(db, file2) {
|
|
24832
|
+
reapStalePartials(file2);
|
|
24833
|
+
const backup = backupPath(file2, "legacy");
|
|
24834
|
+
let snapshotted = false;
|
|
24835
|
+
let snapshotError;
|
|
24836
|
+
try {
|
|
24837
|
+
snapshotStore(db, backup);
|
|
24838
|
+
snapshotted = true;
|
|
24839
|
+
} catch (error51) {
|
|
24840
|
+
snapshotError = error51;
|
|
24841
|
+
} finally {
|
|
24842
|
+
db.close();
|
|
24843
|
+
}
|
|
24844
|
+
if (!snapshotted) {
|
|
24845
|
+
akaWarn(
|
|
24846
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24847
|
+
);
|
|
24848
|
+
moveStoreAside(file2, backup);
|
|
24849
|
+
return backup;
|
|
23407
24850
|
}
|
|
24851
|
+
discardStore(file2, backup);
|
|
23408
24852
|
return backup;
|
|
23409
24853
|
}
|
|
23410
24854
|
function openAndInitialize(file2) {
|
|
23411
24855
|
let db = openWithPragmas(file2);
|
|
23412
24856
|
try {
|
|
23413
24857
|
if (isForeignSqliteLineage(db)) {
|
|
23414
|
-
db
|
|
23415
|
-
const backup = backupLegacyStore(file2);
|
|
24858
|
+
const backup = backupLegacyStore(db, file2);
|
|
23416
24859
|
db = openWithPragmas(file2);
|
|
23417
24860
|
akaWarn(
|
|
23418
24861
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23428,6 +24871,7 @@ function openAndInitialize(file2) {
|
|
|
23428
24871
|
policies,
|
|
23429
24872
|
installedPacks,
|
|
23430
24873
|
scanLedger: new SqliteScanLedgerRepository(db),
|
|
24874
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23431
24875
|
exceptions: new SqliteExceptionsRepository(db),
|
|
23432
24876
|
resolutions: new SqliteResolutionsRepository(db),
|
|
23433
24877
|
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
@@ -23455,7 +24899,7 @@ function openAndInitialize(file2) {
|
|
|
23455
24899
|
}
|
|
23456
24900
|
function openLocalDatabase(dir) {
|
|
23457
24901
|
ensureDataDirSync(dir);
|
|
23458
|
-
const file2 =
|
|
24902
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23459
24903
|
const {
|
|
23460
24904
|
db,
|
|
23461
24905
|
events,
|
|
@@ -23463,6 +24907,7 @@ function openLocalDatabase(dir) {
|
|
|
23463
24907
|
policies,
|
|
23464
24908
|
installedPacks,
|
|
23465
24909
|
scanLedger,
|
|
24910
|
+
secretVault,
|
|
23466
24911
|
exceptions,
|
|
23467
24912
|
resolutions,
|
|
23468
24913
|
ruleProbeCache,
|
|
@@ -23571,7 +25016,7 @@ function openLocalDatabase(dir) {
|
|
|
23571
25016
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23572
25017
|
if (!definitionId) continue;
|
|
23573
25018
|
inspectionFindings.insertFinding({
|
|
23574
|
-
id:
|
|
25019
|
+
id: randomUUID10(),
|
|
23575
25020
|
auditEventId: record2.scanEvent.id,
|
|
23576
25021
|
inspectionDefinitionId: definitionId,
|
|
23577
25022
|
span: finding.span,
|
|
@@ -23648,6 +25093,7 @@ function openLocalDatabase(dir) {
|
|
|
23648
25093
|
policies,
|
|
23649
25094
|
installedPacks,
|
|
23650
25095
|
scanLedger,
|
|
25096
|
+
secretVault,
|
|
23651
25097
|
exceptions,
|
|
23652
25098
|
resolutions,
|
|
23653
25099
|
ruleProbeCache,
|
|
@@ -23676,35 +25122,251 @@ function openLocalDatabase(dir) {
|
|
|
23676
25122
|
transaction,
|
|
23677
25123
|
close: () => {
|
|
23678
25124
|
db.close();
|
|
23679
|
-
}
|
|
25125
|
+
},
|
|
25126
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25127
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
23680
25128
|
};
|
|
23681
25129
|
}
|
|
23682
25130
|
|
|
25131
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25132
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25133
|
+
import {
|
|
25134
|
+
closeSync,
|
|
25135
|
+
existsSync as existsSync2,
|
|
25136
|
+
openSync,
|
|
25137
|
+
readFileSync,
|
|
25138
|
+
rmSync as rmSync3,
|
|
25139
|
+
statSync as statSync2,
|
|
25140
|
+
writeFileSync as writeFileSync2
|
|
25141
|
+
} from "fs";
|
|
25142
|
+
import { hostname as hostname3 } from "os";
|
|
25143
|
+
var LOCK_SUFFIX = ".lock";
|
|
25144
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
25145
|
+
var DEFAULT_STALE_MS = 2e3;
|
|
25146
|
+
var RETRY_INTERVAL_MS = 5;
|
|
25147
|
+
var RETRYABLE_CREATE_ERRNOS = /* @__PURE__ */ new Set(["EEXIST", "EACCES", "EPERM", "EBUSY"]);
|
|
25148
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25149
|
+
function sleepSync(ms) {
|
|
25150
|
+
Atomics.wait(PARK, 0, 0, ms);
|
|
25151
|
+
}
|
|
25152
|
+
var FileLockError = class extends Error {
|
|
25153
|
+
reason;
|
|
25154
|
+
file;
|
|
25155
|
+
holderPid;
|
|
25156
|
+
constructor(reason, file2, detail, holderPid, options) {
|
|
25157
|
+
super(
|
|
25158
|
+
`cannot lock ${file2} for writing: ${detail}` + (holderPid === void 0 ? "" : ` (held by pid ${String(holderPid)})`),
|
|
25159
|
+
options
|
|
25160
|
+
);
|
|
25161
|
+
this.name = "FileLockError";
|
|
25162
|
+
this.reason = reason;
|
|
25163
|
+
this.file = file2;
|
|
25164
|
+
this.holderPid = holderPid;
|
|
25165
|
+
}
|
|
25166
|
+
};
|
|
25167
|
+
function lockPathFor(file2) {
|
|
25168
|
+
return `${file2}${LOCK_SUFFIX}`;
|
|
25169
|
+
}
|
|
25170
|
+
function readLockBody(lock) {
|
|
25171
|
+
let raw;
|
|
25172
|
+
try {
|
|
25173
|
+
raw = readFileSync(lock, "utf8");
|
|
25174
|
+
} catch {
|
|
25175
|
+
return null;
|
|
25176
|
+
}
|
|
25177
|
+
try {
|
|
25178
|
+
const parsed = JSON.parse(raw);
|
|
25179
|
+
const { pid, token, at, host } = parsed;
|
|
25180
|
+
if (typeof pid !== "number" || typeof token !== "string" || typeof at !== "number") return null;
|
|
25181
|
+
return { pid, token, at, host: typeof host === "string" ? host : "" };
|
|
25182
|
+
} catch {
|
|
25183
|
+
return null;
|
|
25184
|
+
}
|
|
25185
|
+
}
|
|
25186
|
+
function holderIsAlive(pid) {
|
|
25187
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
25188
|
+
try {
|
|
25189
|
+
process.kill(pid, 0);
|
|
25190
|
+
return true;
|
|
25191
|
+
} catch (err) {
|
|
25192
|
+
return err.code !== "ESRCH";
|
|
25193
|
+
}
|
|
25194
|
+
}
|
|
25195
|
+
function directoryAcceptsCreates(lock) {
|
|
25196
|
+
const probe = `${lock}.probe-${randomUUID11()}`;
|
|
25197
|
+
try {
|
|
25198
|
+
closeSync(openSync(probe, "wx", DATA_FILE_MODE));
|
|
25199
|
+
return true;
|
|
25200
|
+
} catch {
|
|
25201
|
+
return false;
|
|
25202
|
+
} finally {
|
|
25203
|
+
try {
|
|
25204
|
+
rmSync3(probe, { force: true });
|
|
25205
|
+
} catch {
|
|
25206
|
+
}
|
|
25207
|
+
}
|
|
25208
|
+
}
|
|
25209
|
+
function tryAcquire(lock, file2) {
|
|
25210
|
+
const token = randomUUID11();
|
|
25211
|
+
let fd;
|
|
25212
|
+
try {
|
|
25213
|
+
fd = openSync(lock, "wx", DATA_FILE_MODE);
|
|
25214
|
+
} catch (err) {
|
|
25215
|
+
const code = err.code ?? "";
|
|
25216
|
+
if (code === "EEXIST") return null;
|
|
25217
|
+
if (RETRYABLE_CREATE_ERRNOS.has(code) && (existsSync2(lock) || directoryAcceptsCreates(lock))) {
|
|
25218
|
+
return null;
|
|
25219
|
+
}
|
|
25220
|
+
throw new FileLockError(
|
|
25221
|
+
"unavailable",
|
|
25222
|
+
file2,
|
|
25223
|
+
err instanceof Error ? err.message : String(err),
|
|
25224
|
+
void 0,
|
|
25225
|
+
{ cause: err }
|
|
25226
|
+
);
|
|
25227
|
+
}
|
|
25228
|
+
const body = { pid: process.pid, token, at: Date.now(), host: hostname3() };
|
|
25229
|
+
try {
|
|
25230
|
+
writeFileSync2(fd, `${JSON.stringify(body)}
|
|
25231
|
+
`);
|
|
25232
|
+
} catch {
|
|
25233
|
+
try {
|
|
25234
|
+
closeSync(fd);
|
|
25235
|
+
} catch {
|
|
25236
|
+
}
|
|
25237
|
+
rmSync3(lock, { force: true });
|
|
25238
|
+
return null;
|
|
25239
|
+
}
|
|
25240
|
+
try {
|
|
25241
|
+
closeSync(fd);
|
|
25242
|
+
} catch {
|
|
25243
|
+
}
|
|
25244
|
+
return token;
|
|
25245
|
+
}
|
|
25246
|
+
function isAbandoned(body, lock, staleMs) {
|
|
25247
|
+
if (!body) {
|
|
25248
|
+
try {
|
|
25249
|
+
return Date.now() - statSync2(lock).mtimeMs >= staleMs;
|
|
25250
|
+
} catch {
|
|
25251
|
+
return false;
|
|
25252
|
+
}
|
|
25253
|
+
}
|
|
25254
|
+
if (Date.now() - body.at < staleMs) return false;
|
|
25255
|
+
if (body.host !== hostname3() || !holderIsAlive(body.pid)) return true;
|
|
25256
|
+
return Date.now() - body.at >= abandonWindow(staleMs);
|
|
25257
|
+
}
|
|
25258
|
+
function breakIfStale(lock, staleMs) {
|
|
25259
|
+
const breaker = `${lock}.break`;
|
|
25260
|
+
let fd;
|
|
25261
|
+
try {
|
|
25262
|
+
fd = openSync(breaker, "wx", DATA_FILE_MODE);
|
|
25263
|
+
} catch {
|
|
25264
|
+
reapAbandonedBreaker(breaker);
|
|
25265
|
+
return false;
|
|
25266
|
+
}
|
|
25267
|
+
try {
|
|
25268
|
+
closeSync(fd);
|
|
25269
|
+
} catch {
|
|
25270
|
+
}
|
|
25271
|
+
try {
|
|
25272
|
+
if (!existsSync2(lock) || !isAbandoned(readLockBody(lock), lock, staleMs)) return false;
|
|
25273
|
+
rmSync3(lock, { force: true });
|
|
25274
|
+
return true;
|
|
25275
|
+
} catch {
|
|
25276
|
+
return false;
|
|
25277
|
+
} finally {
|
|
25278
|
+
try {
|
|
25279
|
+
rmSync3(breaker, { force: true });
|
|
25280
|
+
} catch {
|
|
25281
|
+
}
|
|
25282
|
+
}
|
|
25283
|
+
}
|
|
25284
|
+
var BREAKER_ABANDONED_MS = 1e4;
|
|
25285
|
+
function reapAbandonedBreaker(breaker) {
|
|
25286
|
+
try {
|
|
25287
|
+
if (Date.now() - statSync2(breaker).mtimeMs >= BREAKER_ABANDONED_MS) {
|
|
25288
|
+
rmSync3(breaker, { force: true });
|
|
25289
|
+
}
|
|
25290
|
+
} catch {
|
|
25291
|
+
}
|
|
25292
|
+
}
|
|
25293
|
+
function abandonWindow(staleMs) {
|
|
25294
|
+
return Math.max(staleMs * 30, 6e4);
|
|
25295
|
+
}
|
|
25296
|
+
function release(lock, token) {
|
|
25297
|
+
try {
|
|
25298
|
+
if (readLockBody(lock)?.token !== token) return;
|
|
25299
|
+
rmSync3(lock, { force: true });
|
|
25300
|
+
} catch {
|
|
25301
|
+
}
|
|
25302
|
+
}
|
|
25303
|
+
function withFileLock(file2, fn, options = {}) {
|
|
25304
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
25305
|
+
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
25306
|
+
const lock = lockPathFor(file2);
|
|
25307
|
+
const deadline = Date.now() + timeoutMs;
|
|
25308
|
+
let token = tryAcquire(lock, file2);
|
|
25309
|
+
while (token === null) {
|
|
25310
|
+
if (breakIfStale(lock, staleMs)) {
|
|
25311
|
+
token = tryAcquire(lock, file2);
|
|
25312
|
+
continue;
|
|
25313
|
+
}
|
|
25314
|
+
if (Date.now() >= deadline) {
|
|
25315
|
+
throw new FileLockError(
|
|
25316
|
+
"timeout",
|
|
25317
|
+
file2,
|
|
25318
|
+
`still held after ${String(timeoutMs)}ms`,
|
|
25319
|
+
readLockBody(lock)?.pid
|
|
25320
|
+
);
|
|
25321
|
+
}
|
|
25322
|
+
sleepSync(RETRY_INTERVAL_MS);
|
|
25323
|
+
token = tryAcquire(lock, file2);
|
|
25324
|
+
}
|
|
25325
|
+
try {
|
|
25326
|
+
const result = fn();
|
|
25327
|
+
if (isThenable(result)) {
|
|
25328
|
+
void result.then(
|
|
25329
|
+
() => void 0,
|
|
25330
|
+
() => void 0
|
|
25331
|
+
);
|
|
25332
|
+
throw new TypeError(
|
|
25333
|
+
`withFileLock(${file2}) was given an async body; the lock is released as soon as it returns, so the awaited work would run unguarded. Pass a synchronous function.`
|
|
25334
|
+
);
|
|
25335
|
+
}
|
|
25336
|
+
return result;
|
|
25337
|
+
} finally {
|
|
25338
|
+
release(lock, token);
|
|
25339
|
+
}
|
|
25340
|
+
}
|
|
25341
|
+
function isThenable(value) {
|
|
25342
|
+
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
25343
|
+
}
|
|
25344
|
+
|
|
23683
25345
|
// ../../packages/persistence/src/finding-key.ts
|
|
23684
25346
|
import { createHash as createHash3 } from "crypto";
|
|
23685
25347
|
|
|
23686
25348
|
// ../../packages/persistence/src/fingerprint.ts
|
|
23687
25349
|
import { createHmac, randomBytes } from "crypto";
|
|
23688
|
-
import { existsSync as
|
|
23689
|
-
import { join as
|
|
25350
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25351
|
+
import { join as join3 } from "path";
|
|
23690
25352
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23691
25353
|
|
|
23692
25354
|
// ../../packages/persistence/src/local-layout.ts
|
|
23693
25355
|
import { renameSync as renameSync3 } from "fs";
|
|
23694
25356
|
import { mkdir } from "fs/promises";
|
|
23695
25357
|
import { homedir } from "os";
|
|
23696
|
-
import { join as
|
|
25358
|
+
import { join as join4 } from "path";
|
|
23697
25359
|
function defaultDataDir() {
|
|
23698
|
-
return
|
|
25360
|
+
return join4(homedir(), ".aka");
|
|
23699
25361
|
}
|
|
23700
25362
|
function settingsDir(base = defaultDataDir()) {
|
|
23701
|
-
return
|
|
25363
|
+
return join4(base, "settings");
|
|
23702
25364
|
}
|
|
23703
25365
|
function dataDir(base = defaultDataDir()) {
|
|
23704
|
-
return
|
|
25366
|
+
return join4(base, "data");
|
|
23705
25367
|
}
|
|
23706
25368
|
function dbPath(base = defaultDataDir()) {
|
|
23707
|
-
return
|
|
25369
|
+
return join4(dataDir(base), "aka.db");
|
|
23708
25370
|
}
|
|
23709
25371
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
23710
25372
|
ensureDataDirSync(dir);
|
|
@@ -23717,8 +25379,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23717
25379
|
for (const { name, dest } of moves) {
|
|
23718
25380
|
try {
|
|
23719
25381
|
ensureDataDirSync(dest);
|
|
23720
|
-
const moved =
|
|
23721
|
-
renameSync3(
|
|
25382
|
+
const moved = join4(dest, name);
|
|
25383
|
+
renameSync3(join4(base, name), moved);
|
|
23722
25384
|
tightenFile(moved);
|
|
23723
25385
|
} catch {
|
|
23724
25386
|
}
|
|
@@ -23726,10 +25388,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23726
25388
|
}
|
|
23727
25389
|
|
|
23728
25390
|
// ../../packages/persistence/src/settings.ts
|
|
23729
|
-
import { readFileSync as
|
|
23730
|
-
import { join as
|
|
25391
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25392
|
+
import { join as join5 } from "path";
|
|
25393
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
23731
25394
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
23732
|
-
const record2 = readJson(
|
|
25395
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
23733
25396
|
if (!record2) return defaultWorkspaceSettings();
|
|
23734
25397
|
try {
|
|
23735
25398
|
return WorkspaceSettings.parse(record2);
|
|
@@ -23739,46 +25402,75 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
23739
25402
|
}
|
|
23740
25403
|
function applyOnboarding(answers2, base = defaultDataDir()) {
|
|
23741
25404
|
const dir = settingsDir(base);
|
|
23742
|
-
const current = readWorkspaceSettings(base);
|
|
23743
|
-
const merged = WorkspaceSettings.parse({
|
|
23744
|
-
...current,
|
|
23745
|
-
...answers2,
|
|
23746
|
-
// First setup stamps the time; later edits keep the original completion mark.
|
|
23747
|
-
onboardedAt: answers2.onboardedAt ?? current.onboardedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
23748
|
-
});
|
|
23749
25405
|
ensureDataDirSync(dir);
|
|
23750
|
-
const file2 =
|
|
23751
|
-
|
|
25406
|
+
const file2 = join5(dir, SETTINGS_FILENAME);
|
|
25407
|
+
return withFileLock(file2, () => {
|
|
25408
|
+
const current = readWorkspaceSettings(base);
|
|
25409
|
+
const applied = typeof answers2 === "function" ? answers2(current) : answers2;
|
|
25410
|
+
const merged = WorkspaceSettings.parse({
|
|
25411
|
+
...current,
|
|
25412
|
+
...applied,
|
|
25413
|
+
// First setup stamps the time; later edits keep the original completion mark.
|
|
25414
|
+
onboardedAt: applied.onboardedAt ?? current.onboardedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
25415
|
+
});
|
|
25416
|
+
writeOwnerOnlyFileSync(file2, `${JSON.stringify(merged, null, 2)}
|
|
23752
25417
|
`);
|
|
23753
|
-
|
|
25418
|
+
return merged;
|
|
25419
|
+
});
|
|
23754
25420
|
}
|
|
23755
25421
|
function readJson(file2) {
|
|
23756
25422
|
let text;
|
|
23757
25423
|
try {
|
|
23758
|
-
text =
|
|
25424
|
+
text = readFileSync3(file2, "utf8");
|
|
23759
25425
|
} catch {
|
|
23760
25426
|
return null;
|
|
23761
25427
|
}
|
|
23762
25428
|
return parseJsonObject(text) ?? null;
|
|
23763
25429
|
}
|
|
23764
25430
|
|
|
25431
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
25432
|
+
import {
|
|
25433
|
+
createCipheriv,
|
|
25434
|
+
createDecipheriv,
|
|
25435
|
+
createHmac as createHmac2,
|
|
25436
|
+
hkdfSync,
|
|
25437
|
+
timingSafeEqual
|
|
25438
|
+
} from "crypto";
|
|
25439
|
+
|
|
25440
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25441
|
+
import { execFileSync } from "child_process";
|
|
25442
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
25443
|
+
import {
|
|
25444
|
+
chmodSync as chmodSync2,
|
|
25445
|
+
mkdirSync as mkdirSync2,
|
|
25446
|
+
readFileSync as readFileSync4,
|
|
25447
|
+
renameSync as renameSync4,
|
|
25448
|
+
rmSync as rmSync4,
|
|
25449
|
+
statSync as statSync3,
|
|
25450
|
+
writeFileSync as writeFileSync3
|
|
25451
|
+
} from "fs";
|
|
25452
|
+
import { join as join6 } from "path";
|
|
25453
|
+
|
|
25454
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
25455
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
25456
|
+
|
|
23765
25457
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
23766
|
-
import { existsSync as
|
|
23767
|
-
import { join as
|
|
25458
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25459
|
+
import { join as join7 } from "path";
|
|
23768
25460
|
var MARKER = "warn-era-capped";
|
|
23769
25461
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
23770
25462
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
23771
|
-
const marker =
|
|
23772
|
-
if (
|
|
25463
|
+
const marker = join7(dataDir2, MARKER);
|
|
25464
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
23773
25465
|
const capped = db.policies.capCategoryActions();
|
|
23774
|
-
|
|
25466
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
23775
25467
|
`, { mode: DATA_FILE_MODE });
|
|
23776
25468
|
return { capped };
|
|
23777
25469
|
}
|
|
23778
25470
|
|
|
23779
25471
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23780
|
-
import { existsSync as
|
|
23781
|
-
import { join as
|
|
25472
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25473
|
+
import { join as join8 } from "path";
|
|
23782
25474
|
|
|
23783
25475
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
23784
25476
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -23829,11 +25521,11 @@ function resolveProvider() {
|
|
|
23829
25521
|
}
|
|
23830
25522
|
|
|
23831
25523
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23832
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25524
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
23833
25525
|
try {
|
|
23834
25526
|
ensureLayoutDirSync(base);
|
|
23835
|
-
const settingsFile =
|
|
23836
|
-
if (
|
|
25527
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25528
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
23837
25529
|
} catch {
|
|
23838
25530
|
}
|
|
23839
25531
|
migrateLegacyLayout(base);
|
|
@@ -23844,21 +25536,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
23844
25536
|
dbPath: dbPath(base),
|
|
23845
25537
|
settingsDir: settingsDir(base),
|
|
23846
25538
|
onboarded: settings.onboardedAt != null,
|
|
23847
|
-
provider: resolveProviderSafe()
|
|
25539
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
23848
25540
|
};
|
|
23849
25541
|
}
|
|
23850
|
-
function resolveProviderSafe() {
|
|
25542
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
23851
25543
|
try {
|
|
23852
|
-
return
|
|
25544
|
+
return resolveProviderFn();
|
|
23853
25545
|
} catch {
|
|
23854
25546
|
return { provider: "anthropic" };
|
|
23855
25547
|
}
|
|
23856
25548
|
}
|
|
23857
25549
|
|
|
23858
25550
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23859
|
-
import { readdirSync, readFileSync as
|
|
25551
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
23860
25552
|
import { homedir as homedir2 } from "os";
|
|
23861
|
-
import { basename as
|
|
25553
|
+
import { basename as basename3, join as join10 } from "path";
|
|
23862
25554
|
|
|
23863
25555
|
// ../../packages/detections/src/egress/registry.ts
|
|
23864
25556
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -24569,22 +26261,27 @@ var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].m
|
|
|
24569
26261
|
);
|
|
24570
26262
|
|
|
24571
26263
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
24572
|
-
import { existsSync as
|
|
24573
|
-
import { basename, dirname, isAbsolute, join as
|
|
26264
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
26265
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
24574
26266
|
|
|
24575
26267
|
// ../../packages/plugin-sdk/src/events.ts
|
|
24576
|
-
import { createHash as createHash4, randomUUID as
|
|
26268
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
26269
|
+
|
|
26270
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
26271
|
+
import { existsSync as existsSync7 } from "fs";
|
|
26272
|
+
import { fileURLToPath } from "url";
|
|
26273
|
+
import { Worker } from "worker_threads";
|
|
24577
26274
|
|
|
24578
26275
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
24579
|
-
import { arch, hostname as
|
|
26276
|
+
import { arch, hostname as hostname4, platform, release as release2 } from "os";
|
|
24580
26277
|
|
|
24581
26278
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
24582
|
-
import { mkdirSync as
|
|
24583
|
-
import { join as
|
|
26279
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
26280
|
+
import { join as join11 } from "path";
|
|
24584
26281
|
|
|
24585
26282
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
24586
|
-
import { readdirSync as
|
|
24587
|
-
import { basename as
|
|
26283
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
26284
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
24588
26285
|
|
|
24589
26286
|
// ../../packages/plugin-sdk/src/posture.ts
|
|
24590
26287
|
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
@@ -24598,20 +26295,46 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
24598
26295
|
|
|
24599
26296
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
24600
26297
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
24601
|
-
import { existsSync as
|
|
24602
|
-
import { basename as
|
|
26298
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
26299
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
26300
|
+
|
|
26301
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
26302
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
26303
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
26304
|
+
return v;
|
|
26305
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
26306
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
26307
|
+
if (typeof v !== "string") return false;
|
|
26308
|
+
const normalized = v.trim().toLowerCase();
|
|
26309
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
26310
|
+
}, external_exports.boolean()).catch(false);
|
|
26311
|
+
var antigravityProviderEnvShape = {
|
|
26312
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
26313
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
26314
|
+
};
|
|
26315
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
26316
|
+
|
|
26317
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
26318
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
26319
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
26320
|
+
return v;
|
|
26321
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
26322
|
+
var codexProviderEnvShape = {
|
|
26323
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
26324
|
+
};
|
|
26325
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
24603
26326
|
|
|
24604
26327
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
24605
|
-
import { randomUUID as
|
|
26328
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
24606
26329
|
|
|
24607
26330
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
24608
26331
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
24609
26332
|
|
|
24610
26333
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
24611
|
-
import { mkdirSync as
|
|
24612
|
-
import { join as
|
|
26334
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
26335
|
+
import { join as join13 } from "path";
|
|
24613
26336
|
|
|
24614
|
-
// src/onboard-posture.ts
|
|
26337
|
+
// ../../packages/setup-wizard/src/onboard-posture.ts
|
|
24615
26338
|
function parsePosture(json2) {
|
|
24616
26339
|
const raw = JSON.parse(json2);
|
|
24617
26340
|
if (typeof raw !== "object" || raw === null) throw new Error("posture must be a JSON object");
|
|
@@ -24630,6 +26353,54 @@ function parsePosture(json2) {
|
|
|
24630
26353
|
return out;
|
|
24631
26354
|
}
|
|
24632
26355
|
|
|
26356
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
26357
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
26358
|
+
import { join as join14 } from "path";
|
|
26359
|
+
|
|
26360
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
26361
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
26362
|
+
import { tmpdir } from "os";
|
|
26363
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
26364
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
26365
|
+
ruleId: external_exports.string(),
|
|
26366
|
+
category: DetectionCategory,
|
|
26367
|
+
valueFingerprint: external_exports.string(),
|
|
26368
|
+
keyVersion: external_exports.number(),
|
|
26369
|
+
maskedValue: external_exports.string(),
|
|
26370
|
+
justification: external_exports.string()
|
|
26371
|
+
});
|
|
26372
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
26373
|
+
category: DetectionCategory,
|
|
26374
|
+
action: BuiltinPolicyId,
|
|
26375
|
+
genuineCount: external_exports.number(),
|
|
26376
|
+
fpCount: external_exports.number(),
|
|
26377
|
+
reasoning: external_exports.string()
|
|
26378
|
+
});
|
|
26379
|
+
var JoinEntrySchema = external_exports.object({
|
|
26380
|
+
id: external_exports.string(),
|
|
26381
|
+
ruleId: external_exports.string(),
|
|
26382
|
+
category: DetectionCategory,
|
|
26383
|
+
valueFingerprint: external_exports.string().optional(),
|
|
26384
|
+
keyVersion: external_exports.number().optional(),
|
|
26385
|
+
maskedMatch: external_exports.string(),
|
|
26386
|
+
maskedContext: external_exports.string()
|
|
26387
|
+
});
|
|
26388
|
+
var PLAN_FILE_VERSION = 3;
|
|
26389
|
+
var PersistedPlanSchema = external_exports.object({
|
|
26390
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
26391
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
26392
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
26393
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
26394
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
26395
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
26396
|
+
join: external_exports.array(JoinEntrySchema),
|
|
26397
|
+
notes: external_exports.string(),
|
|
26398
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
26399
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
26400
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
26401
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
26402
|
+
});
|
|
26403
|
+
|
|
24633
26404
|
// src/setup-show.ts
|
|
24634
26405
|
var SHOW_BEGIN = "<<<AKA_SHOW";
|
|
24635
26406
|
var SHOW_END = "AKA_SHOW>>>";
|
|
@@ -24676,9 +26447,9 @@ function show(body) {
|
|
|
24676
26447
|
}
|
|
24677
26448
|
|
|
24678
26449
|
// src/command-registry.ts
|
|
24679
|
-
import { readdirSync as
|
|
24680
|
-
import { fileURLToPath } from "url";
|
|
24681
|
-
var COMMANDS_DIR =
|
|
26450
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
26451
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
26452
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
24682
26453
|
|
|
24683
26454
|
// src/render.ts
|
|
24684
26455
|
var SEVERITY_GLYPH = {
|
|
@@ -24737,12 +26508,29 @@ if (process.argv.includes("--model-judge-consent")) {
|
|
|
24737
26508
|
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
24738
26509
|
};
|
|
24739
26510
|
}
|
|
26511
|
+
var rawVaultConsent = flags.get("vault-consent");
|
|
26512
|
+
var vaultConsentAction;
|
|
26513
|
+
if (rawVaultConsent !== void 0) {
|
|
26514
|
+
if (rawVaultConsent === "grant" || rawVaultConsent === "revoke") {
|
|
26515
|
+
vaultConsentAction = rawVaultConsent;
|
|
26516
|
+
} else {
|
|
26517
|
+
fail(`invalid --vault-consent "${rawVaultConsent}" (expected grant or revoke)`);
|
|
26518
|
+
}
|
|
26519
|
+
}
|
|
26520
|
+
if (vaultConsentAction === "grant") {
|
|
26521
|
+
answers.vaultConsent = {
|
|
26522
|
+
acknowledgedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26523
|
+
version: VAULT_CONSENT_VERSION
|
|
26524
|
+
};
|
|
26525
|
+
} else if (vaultConsentAction === "revoke") {
|
|
26526
|
+
answers.vaultConsent = void 0;
|
|
26527
|
+
}
|
|
24740
26528
|
var rawPosture = flags.get("posture");
|
|
24741
26529
|
var useFloor = process.argv.includes("--floor");
|
|
24742
26530
|
var recalibrate = process.argv.includes("--recalibrate");
|
|
24743
26531
|
if (useFloor && rawPosture !== void 0) fail("--floor and --posture are mutually exclusive");
|
|
24744
26532
|
if (Object.keys(answers).length === 0 && rawPosture === void 0 && !useFloor) {
|
|
24745
|
-
fail("nothing to save \u2014 pass --policy, --historical, --posture and/or --floor");
|
|
26533
|
+
fail("nothing to save \u2014 pass --policy, --historical, --vault-consent, --posture and/or --floor");
|
|
24746
26534
|
}
|
|
24747
26535
|
var wroteConsent = answers.modelJudgeConsent !== void 0;
|
|
24748
26536
|
var wrotePosture = answers.policy !== void 0 || answers.historicalAccess !== void 0;
|
|
@@ -24757,6 +26545,17 @@ if (Object.keys(answers).length > 0) {
|
|
|
24757
26545
|
show("Noted \u2014 I'll send findings to the model to rate them. You can revoke that anytime.")
|
|
24758
26546
|
);
|
|
24759
26547
|
}
|
|
26548
|
+
if (vaultConsentAction === "grant") {
|
|
26549
|
+
process.stdout.write(
|
|
26550
|
+
show("Okay \u2014 detected secrets will be kept recoverable in your local encrypted vault.")
|
|
26551
|
+
);
|
|
26552
|
+
} else if (vaultConsentAction === "revoke") {
|
|
26553
|
+
process.stdout.write(
|
|
26554
|
+
show(
|
|
26555
|
+
"Okay \u2014 new detections will no longer be vaulted. Anything already stored stays until you purge the vault."
|
|
26556
|
+
)
|
|
26557
|
+
);
|
|
26558
|
+
}
|
|
24760
26559
|
if (wrotePosture) {
|
|
24761
26560
|
try {
|
|
24762
26561
|
const dataDir2 = loadConfig().dataDir;
|