@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/firstrun.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" });
|
|
@@ -17068,6 +17234,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17068
17234
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17069
17235
|
}).meta({ id: "EgressWriteSummary" });
|
|
17070
17236
|
|
|
17237
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17238
|
+
var confirmation = external_exports.string().optional();
|
|
17239
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17240
|
+
reference: external_exports.string(),
|
|
17241
|
+
scope: external_exports.string(),
|
|
17242
|
+
reason: external_exports.string(),
|
|
17243
|
+
confirmation
|
|
17244
|
+
});
|
|
17245
|
+
var AddExceptionInput = external_exports.object({
|
|
17246
|
+
ruleId: external_exports.string(),
|
|
17247
|
+
value: external_exports.string(),
|
|
17248
|
+
scope: external_exports.string(),
|
|
17249
|
+
reason: external_exports.string(),
|
|
17250
|
+
confirmation
|
|
17251
|
+
});
|
|
17252
|
+
var GrantRevealInput = external_exports.object({
|
|
17253
|
+
pointer: external_exports.string(),
|
|
17254
|
+
scope: external_exports.string(),
|
|
17255
|
+
justification: external_exports.string(),
|
|
17256
|
+
confirmation
|
|
17257
|
+
});
|
|
17258
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17259
|
+
id: external_exports.string(),
|
|
17260
|
+
reason: external_exports.string()
|
|
17261
|
+
});
|
|
17262
|
+
var RotateKeyInput = external_exports.object({
|
|
17263
|
+
confirmation: external_exports.string()
|
|
17264
|
+
});
|
|
17265
|
+
|
|
17071
17266
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17072
17267
|
function toApiAction(dbVal) {
|
|
17073
17268
|
const map2 = {
|
|
@@ -17123,6 +17318,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17123
17318
|
repo: r.repo,
|
|
17124
17319
|
file: r.file,
|
|
17125
17320
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17321
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17322
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17126
17323
|
action: toApiAction(effectiveDbAction),
|
|
17127
17324
|
detectedAt: r.occurredAt,
|
|
17128
17325
|
confidence: r.confidence,
|
|
@@ -17254,14 +17451,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17254
17451
|
}
|
|
17255
17452
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17256
17453
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17454
|
+
function compareFindingGroupOrder(a, b) {
|
|
17455
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17456
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17457
|
+
const severityDiff = rankA - rankB;
|
|
17458
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17459
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17460
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17461
|
+
return a.id.localeCompare(b.id);
|
|
17462
|
+
}
|
|
17257
17463
|
function sortFindingGroups(groups) {
|
|
17258
|
-
return [...groups].sort(
|
|
17259
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17260
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17261
|
-
const severityDiff = rankA - rankB;
|
|
17262
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17263
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17264
|
-
});
|
|
17464
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17265
17465
|
}
|
|
17266
17466
|
function computeFindingFacets(allGroups, opts) {
|
|
17267
17467
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17317,15 +17517,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17317
17517
|
for (const g of forStatus) {
|
|
17318
17518
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17319
17519
|
}
|
|
17320
|
-
const
|
|
17520
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17521
|
+
return {
|
|
17522
|
+
severity: toItems2(severityMap),
|
|
17523
|
+
provider: toItems2(providerMap),
|
|
17524
|
+
action: toItems2(actionMap),
|
|
17525
|
+
subtype: toItems2(subtypeMap),
|
|
17526
|
+
status: toItems2(statusMap)
|
|
17527
|
+
};
|
|
17528
|
+
}
|
|
17529
|
+
|
|
17530
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17531
|
+
function rowHaystack(row) {
|
|
17532
|
+
return [
|
|
17533
|
+
row.ruleId,
|
|
17534
|
+
row.category,
|
|
17535
|
+
row.maskedMatch,
|
|
17536
|
+
row.repo,
|
|
17537
|
+
row.file,
|
|
17538
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17539
|
+
row.id
|
|
17540
|
+
].join(" ").toLowerCase();
|
|
17541
|
+
}
|
|
17542
|
+
function matchesDimension(row, opts, dimension) {
|
|
17543
|
+
switch (dimension) {
|
|
17544
|
+
case "severity":
|
|
17545
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17546
|
+
case "subtype":
|
|
17547
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17548
|
+
case "providers":
|
|
17549
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17550
|
+
case "actions":
|
|
17551
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17552
|
+
case "statuses":
|
|
17553
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17554
|
+
case "tools":
|
|
17555
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17556
|
+
case "repo":
|
|
17557
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17558
|
+
case "file":
|
|
17559
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17560
|
+
case "q":
|
|
17561
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17562
|
+
}
|
|
17563
|
+
}
|
|
17564
|
+
var DIMENSIONS = [
|
|
17565
|
+
"severity",
|
|
17566
|
+
"subtype",
|
|
17567
|
+
"providers",
|
|
17568
|
+
"actions",
|
|
17569
|
+
"statuses",
|
|
17570
|
+
"tools",
|
|
17571
|
+
"repo",
|
|
17572
|
+
"file",
|
|
17573
|
+
"q"
|
|
17574
|
+
];
|
|
17575
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17576
|
+
for (const dimension of DIMENSIONS) {
|
|
17577
|
+
if (dimension === except) continue;
|
|
17578
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17579
|
+
}
|
|
17580
|
+
return true;
|
|
17581
|
+
}
|
|
17582
|
+
function toItems(counts) {
|
|
17583
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17584
|
+
}
|
|
17585
|
+
function bump(counts, value) {
|
|
17586
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17587
|
+
}
|
|
17588
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17589
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17590
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17591
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17592
|
+
const action = /* @__PURE__ */ new Map();
|
|
17593
|
+
const status = /* @__PURE__ */ new Map();
|
|
17594
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17595
|
+
return {
|
|
17596
|
+
add(row) {
|
|
17597
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17598
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17599
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17600
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17601
|
+
}
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17603
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17604
|
+
bump(status, row.status);
|
|
17605
|
+
}
|
|
17606
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17607
|
+
bump(tool, row.toolName);
|
|
17608
|
+
}
|
|
17609
|
+
},
|
|
17610
|
+
facets: () => ({
|
|
17611
|
+
severity: toItems(severity),
|
|
17612
|
+
subtype: toItems(subtype),
|
|
17613
|
+
provider: toItems(provider),
|
|
17614
|
+
action: toItems(action),
|
|
17615
|
+
status: toItems(status),
|
|
17616
|
+
tool: toItems(tool)
|
|
17617
|
+
})
|
|
17618
|
+
};
|
|
17619
|
+
}
|
|
17620
|
+
function toInstanceDetail(row) {
|
|
17621
|
+
const category = toApiCategory(row.category);
|
|
17622
|
+
return {
|
|
17623
|
+
id: row.id,
|
|
17624
|
+
provider: toApiProvider(row.sourceTool),
|
|
17625
|
+
repo: row.repo,
|
|
17626
|
+
file: row.file,
|
|
17627
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17628
|
+
eventId: row.eventId,
|
|
17629
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17630
|
+
action: toApiAction(row.actionTaken),
|
|
17631
|
+
detectedAt: row.occurredAt,
|
|
17632
|
+
confidence: row.confidence,
|
|
17633
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17634
|
+
groupId: row.ruleId,
|
|
17635
|
+
category,
|
|
17636
|
+
subtype: row.ruleId,
|
|
17637
|
+
severity: row.severity,
|
|
17638
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17639
|
+
detection: { id: row.ruleId, name: null },
|
|
17640
|
+
policy: { id: `category:${category}`, name: category }
|
|
17641
|
+
};
|
|
17642
|
+
}
|
|
17643
|
+
var SEVERITY_ORDER2 = {
|
|
17644
|
+
critical: 0,
|
|
17645
|
+
high: 1,
|
|
17646
|
+
medium: 2,
|
|
17647
|
+
low: 3
|
|
17648
|
+
};
|
|
17649
|
+
function newLocationAccumulator() {
|
|
17321
17650
|
return {
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17651
|
+
instanceCount: 0,
|
|
17652
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17653
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17654
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17655
|
+
maxSeverity: "low",
|
|
17656
|
+
latestDetectedAt: "",
|
|
17657
|
+
statuses: [],
|
|
17658
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17327
17659
|
};
|
|
17328
17660
|
}
|
|
17661
|
+
function addToLocation(acc, row) {
|
|
17662
|
+
acc.instanceCount += 1;
|
|
17663
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17664
|
+
if (rank < acc.maxSeverityRank) {
|
|
17665
|
+
acc.maxSeverityRank = rank;
|
|
17666
|
+
acc.maxSeverity = row.severity;
|
|
17667
|
+
}
|
|
17668
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17669
|
+
acc.statuses.push(row.status);
|
|
17670
|
+
acc.ruleIds.add(row.ruleId);
|
|
17671
|
+
}
|
|
17329
17672
|
|
|
17330
17673
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17331
17674
|
var InstalledPack = external_exports.object({
|
|
@@ -17357,8 +17700,161 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17357
17700
|
message: "At least one field must be provided"
|
|
17358
17701
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17359
17702
|
|
|
17703
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17704
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17705
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17706
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17707
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17708
|
+
);
|
|
17709
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17710
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17711
|
+
var ParsedPointer = external_exports.object({
|
|
17712
|
+
category: DetectionCategory,
|
|
17713
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17714
|
+
pointerId: external_exports.string(),
|
|
17715
|
+
tag: external_exports.string()
|
|
17716
|
+
});
|
|
17717
|
+
var VaultEntry = external_exports.object({
|
|
17718
|
+
pointerId: external_exports.string(),
|
|
17719
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17720
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17721
|
+
// independently of the vault encryption key below.
|
|
17722
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17723
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17724
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17725
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17726
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17727
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17728
|
+
// value always produces exactly one wire token.
|
|
17729
|
+
category: DetectionCategory,
|
|
17730
|
+
ruleId: external_exports.string(),
|
|
17731
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17732
|
+
maskedMatch: external_exports.string(),
|
|
17733
|
+
provider: external_exports.string().optional(),
|
|
17734
|
+
ciphertext: external_exports.string(),
|
|
17735
|
+
nonce: external_exports.string(),
|
|
17736
|
+
authTag: external_exports.string(),
|
|
17737
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17738
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17739
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17740
|
+
firstSeen: external_exports.string(),
|
|
17741
|
+
lastSeen: external_exports.string()
|
|
17742
|
+
});
|
|
17743
|
+
var PointerDescriptor = external_exports.object({
|
|
17744
|
+
category: DetectionCategory,
|
|
17745
|
+
provider: external_exports.string().optional(),
|
|
17746
|
+
maskedMatch: external_exports.string(),
|
|
17747
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17748
|
+
firstSeen: external_exports.string(),
|
|
17749
|
+
lastSeen: external_exports.string()
|
|
17750
|
+
});
|
|
17751
|
+
var PointerIdentity = external_exports.object({
|
|
17752
|
+
ruleId: external_exports.string(),
|
|
17753
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17754
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17755
|
+
});
|
|
17756
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17757
|
+
var VaultDerefReason = external_exports.enum([
|
|
17758
|
+
"display",
|
|
17759
|
+
"explicit-reveal",
|
|
17760
|
+
"view-render",
|
|
17761
|
+
"model-input",
|
|
17762
|
+
"remediation",
|
|
17763
|
+
"purge"
|
|
17764
|
+
]);
|
|
17765
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17766
|
+
var VaultDeref = external_exports.object({
|
|
17767
|
+
id: external_exports.guid(),
|
|
17768
|
+
pointerId: external_exports.string(),
|
|
17769
|
+
at: external_exports.string(),
|
|
17770
|
+
target: DetokenizeTarget,
|
|
17771
|
+
reason: VaultDerefReason,
|
|
17772
|
+
outcome: VaultDerefOutcome,
|
|
17773
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17774
|
+
grantId: external_exports.string().optional(),
|
|
17775
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17776
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17777
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17778
|
+
});
|
|
17779
|
+
var VaultSightingKind = external_exports.enum([
|
|
17780
|
+
"prompt",
|
|
17781
|
+
"tool-input",
|
|
17782
|
+
"tool-output",
|
|
17783
|
+
"file",
|
|
17784
|
+
"transcript"
|
|
17785
|
+
]);
|
|
17786
|
+
var VaultSighting = external_exports.object({
|
|
17787
|
+
location: external_exports.string(),
|
|
17788
|
+
kind: VaultSightingKind,
|
|
17789
|
+
firstSeen: external_exports.string(),
|
|
17790
|
+
lastSeen: external_exports.string()
|
|
17791
|
+
});
|
|
17792
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17793
|
+
pointerId: external_exports.string(),
|
|
17794
|
+
category: DetectionCategory,
|
|
17795
|
+
provider: external_exports.string().optional(),
|
|
17796
|
+
maskedMatch: external_exports.string(),
|
|
17797
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17798
|
+
firstSeen: external_exports.string(),
|
|
17799
|
+
lastSeen: external_exports.string(),
|
|
17800
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17801
|
+
// the inventory badges it, the row links to revocation.
|
|
17802
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17803
|
+
sightings: external_exports.array(VaultSighting)
|
|
17804
|
+
});
|
|
17805
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17806
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17807
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17808
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17809
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17810
|
+
// Opaque; names the last row of the page just served.
|
|
17811
|
+
cursor: external_exports.string().optional()
|
|
17812
|
+
});
|
|
17813
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17814
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17815
|
+
// independent, so paging never changes what the count claims.
|
|
17816
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17817
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17818
|
+
// `null` once the last page is reached.
|
|
17819
|
+
nextCursor: external_exports.string().nullable()
|
|
17820
|
+
});
|
|
17821
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17822
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17823
|
+
cursor: external_exports.string().optional()
|
|
17824
|
+
});
|
|
17825
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17826
|
+
// Reused values across the whole store — the number the section's claim
|
|
17827
|
+
// ("values detected in more than one place") is about.
|
|
17828
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17829
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17830
|
+
nextCursor: external_exports.string().nullable()
|
|
17831
|
+
});
|
|
17832
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17833
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17834
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17835
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17836
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17837
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17838
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17839
|
+
cursor: external_exports.string().optional()
|
|
17840
|
+
});
|
|
17841
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17842
|
+
items: external_exports.array(VaultDeref),
|
|
17843
|
+
nextCursor: external_exports.string().nullable(),
|
|
17844
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17845
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17846
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17847
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17848
|
+
});
|
|
17849
|
+
var VaultKeyCustody = external_exports.string();
|
|
17850
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17851
|
+
var VaultConsent = external_exports.object({
|
|
17852
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17853
|
+
version: external_exports.number().int().positive()
|
|
17854
|
+
});
|
|
17855
|
+
|
|
17360
17856
|
// ../../packages/schema/src/zod/local.ts
|
|
17361
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17857
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17362
17858
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17363
17859
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17364
17860
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
@@ -17380,6 +17876,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17380
17876
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17381
17877
|
// Shares writes.
|
|
17382
17878
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17879
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17880
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17881
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17882
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17883
|
+
// purging the vault is the eraser.
|
|
17884
|
+
vaultConsent: VaultConsent.optional(),
|
|
17885
|
+
// Where the vault master key lives.
|
|
17886
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17887
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17888
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17383
17889
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17384
17890
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17385
17891
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -17712,7 +18218,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17712
18218
|
// Omit for both kinds.
|
|
17713
18219
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17714
18220
|
});
|
|
17715
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18221
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17716
18222
|
var ScanCoverageProvider = external_exports.object({
|
|
17717
18223
|
provider: Provider,
|
|
17718
18224
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -17965,6 +18471,138 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
17965
18471
|
);
|
|
17966
18472
|
}
|
|
17967
18473
|
|
|
18474
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18475
|
+
import { randomUUID } from "crypto";
|
|
18476
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18477
|
+
import { basename, dirname, join } from "path";
|
|
18478
|
+
|
|
18479
|
+
// ../../packages/persistence/src/paths.ts
|
|
18480
|
+
import {
|
|
18481
|
+
chmodSync,
|
|
18482
|
+
linkSync,
|
|
18483
|
+
lstatSync,
|
|
18484
|
+
mkdirSync,
|
|
18485
|
+
renameSync,
|
|
18486
|
+
rmSync,
|
|
18487
|
+
writeFileSync
|
|
18488
|
+
} from "fs";
|
|
18489
|
+
import { threadId } from "worker_threads";
|
|
18490
|
+
var DATA_DIR_MODE = 448;
|
|
18491
|
+
var DATA_FILE_MODE = 384;
|
|
18492
|
+
var DB_FILENAME = "aka.db";
|
|
18493
|
+
function isSymlink(path) {
|
|
18494
|
+
try {
|
|
18495
|
+
return lstatSync(path).isSymbolicLink();
|
|
18496
|
+
} catch {
|
|
18497
|
+
return false;
|
|
18498
|
+
}
|
|
18499
|
+
}
|
|
18500
|
+
function chmodBestEffort(path, mode) {
|
|
18501
|
+
if (isSymlink(path)) return;
|
|
18502
|
+
try {
|
|
18503
|
+
chmodSync(path, mode);
|
|
18504
|
+
} catch {
|
|
18505
|
+
}
|
|
18506
|
+
}
|
|
18507
|
+
function tightenDir(dir) {
|
|
18508
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18509
|
+
}
|
|
18510
|
+
function ensureDataDirSync(dir) {
|
|
18511
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18512
|
+
tightenDir(dir);
|
|
18513
|
+
}
|
|
18514
|
+
function dbSidecars(file2) {
|
|
18515
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18516
|
+
}
|
|
18517
|
+
function tightenFile(file2) {
|
|
18518
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18519
|
+
}
|
|
18520
|
+
function tightenPerms(file2) {
|
|
18521
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18522
|
+
}
|
|
18523
|
+
|
|
18524
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18525
|
+
function backupPath(file2, tag) {
|
|
18526
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18527
|
+
}
|
|
18528
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18529
|
+
function reapStalePartials(file2) {
|
|
18530
|
+
const dir = dirname(file2);
|
|
18531
|
+
const prefix = `${basename(file2)}.`;
|
|
18532
|
+
let entries;
|
|
18533
|
+
try {
|
|
18534
|
+
entries = readdirSync(dir);
|
|
18535
|
+
} catch {
|
|
18536
|
+
return;
|
|
18537
|
+
}
|
|
18538
|
+
for (const name of entries) {
|
|
18539
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18540
|
+
const partial2 = join(dir, name);
|
|
18541
|
+
try {
|
|
18542
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18543
|
+
rmSync2(partial2, { force: true });
|
|
18544
|
+
}
|
|
18545
|
+
} catch {
|
|
18546
|
+
}
|
|
18547
|
+
}
|
|
18548
|
+
}
|
|
18549
|
+
function snapshotStore(db, backup) {
|
|
18550
|
+
const partial2 = `${backup}.partial`;
|
|
18551
|
+
try {
|
|
18552
|
+
rmSync2(partial2, { force: true });
|
|
18553
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18554
|
+
tightenFile(partial2);
|
|
18555
|
+
renameSync2(partial2, backup);
|
|
18556
|
+
} catch (error51) {
|
|
18557
|
+
try {
|
|
18558
|
+
rmSync2(partial2, { force: true });
|
|
18559
|
+
} catch {
|
|
18560
|
+
}
|
|
18561
|
+
throw error51;
|
|
18562
|
+
}
|
|
18563
|
+
}
|
|
18564
|
+
function moveStoreAside(file2, backup) {
|
|
18565
|
+
const undo = [];
|
|
18566
|
+
renameSync2(file2, backup);
|
|
18567
|
+
undo.push([backup, file2]);
|
|
18568
|
+
try {
|
|
18569
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18570
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18571
|
+
try {
|
|
18572
|
+
renameSync2(sidecar, moved);
|
|
18573
|
+
undo.push([moved, sidecar]);
|
|
18574
|
+
} catch {
|
|
18575
|
+
rmSync2(sidecar, { force: true });
|
|
18576
|
+
}
|
|
18577
|
+
}
|
|
18578
|
+
} catch (error51) {
|
|
18579
|
+
for (const [from, to] of undo.reverse()) {
|
|
18580
|
+
try {
|
|
18581
|
+
renameSync2(from, to);
|
|
18582
|
+
} catch {
|
|
18583
|
+
}
|
|
18584
|
+
}
|
|
18585
|
+
throw error51;
|
|
18586
|
+
}
|
|
18587
|
+
tightenPerms(backup);
|
|
18588
|
+
}
|
|
18589
|
+
function discardStore(file2, backup) {
|
|
18590
|
+
try {
|
|
18591
|
+
rmSync2(file2, { force: true });
|
|
18592
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18593
|
+
rmSync2(sidecar, { force: true });
|
|
18594
|
+
}
|
|
18595
|
+
} catch (error51) {
|
|
18596
|
+
if (existsSync(file2)) {
|
|
18597
|
+
try {
|
|
18598
|
+
rmSync2(backup, { force: true });
|
|
18599
|
+
} catch {
|
|
18600
|
+
}
|
|
18601
|
+
}
|
|
18602
|
+
throw error51;
|
|
18603
|
+
}
|
|
18604
|
+
}
|
|
18605
|
+
|
|
17968
18606
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
17969
18607
|
function escapeLikePattern(s) {
|
|
17970
18608
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18106,38 +18744,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18106
18744
|
return out;
|
|
18107
18745
|
}
|
|
18108
18746
|
|
|
18109
|
-
// ../../packages/persistence/src/paths.ts
|
|
18110
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18111
|
-
var DATA_DIR_MODE = 448;
|
|
18112
|
-
var DATA_FILE_MODE = 384;
|
|
18113
|
-
var DB_FILENAME = "aka.db";
|
|
18114
|
-
function chmodBestEffort(path, mode) {
|
|
18115
|
-
try {
|
|
18116
|
-
chmodSync(path, mode);
|
|
18117
|
-
} catch {
|
|
18118
|
-
}
|
|
18119
|
-
}
|
|
18120
|
-
function tightenDir(dir) {
|
|
18121
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18122
|
-
}
|
|
18123
|
-
function ensureDataDirSync(dir) {
|
|
18124
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18125
|
-
tightenDir(dir);
|
|
18126
|
-
}
|
|
18127
|
-
function dbSidecars(file2) {
|
|
18128
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18129
|
-
}
|
|
18130
|
-
function tightenFile(file2) {
|
|
18131
|
-
try {
|
|
18132
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18133
|
-
} catch {
|
|
18134
|
-
}
|
|
18135
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18136
|
-
}
|
|
18137
|
-
function tightenPerms(file2) {
|
|
18138
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18139
|
-
}
|
|
18140
|
-
|
|
18141
18747
|
// ../../packages/persistence/src/migrations.ts
|
|
18142
18748
|
function describeObject(object2) {
|
|
18143
18749
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18253,9 +18859,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18253
18859
|
}
|
|
18254
18860
|
}
|
|
18255
18861
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18862
|
+
reapStalePartials(file2);
|
|
18863
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18864
|
+
snapshotStore(db, backup);
|
|
18259
18865
|
return backup;
|
|
18260
18866
|
}
|
|
18261
18867
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18599,6 +19205,25 @@ function parseJsonObject(s) {
|
|
|
18599
19205
|
return void 0;
|
|
18600
19206
|
}
|
|
18601
19207
|
|
|
19208
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19209
|
+
function encodeKeysetCursor(payload) {
|
|
19210
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19211
|
+
}
|
|
19212
|
+
function decodeKeysetCursor(cursor) {
|
|
19213
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19214
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19215
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19216
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19217
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19218
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19219
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19220
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19221
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19222
|
+
return parsed;
|
|
19223
|
+
}
|
|
19224
|
+
return null;
|
|
19225
|
+
}
|
|
19226
|
+
|
|
18602
19227
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18603
19228
|
var DAY_MS = 864e5;
|
|
18604
19229
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18644,16 +19269,6 @@ function utcWindow(nowMs) {
|
|
|
18644
19269
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18645
19270
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18646
19271
|
}
|
|
18647
|
-
function encodeCursor(payload) {
|
|
18648
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18649
|
-
}
|
|
18650
|
-
function decodeCursor(cursor) {
|
|
18651
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18652
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18653
|
-
return parsed;
|
|
18654
|
-
}
|
|
18655
|
-
return null;
|
|
18656
|
-
}
|
|
18657
19272
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18658
19273
|
session: "session",
|
|
18659
19274
|
prompt: "prompt",
|
|
@@ -18798,7 +19413,7 @@ var SqliteActivityRepository = class {
|
|
|
18798
19413
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18799
19414
|
}
|
|
18800
19415
|
listSessions(query) {
|
|
18801
|
-
const cursor = query.cursor ?
|
|
19416
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18802
19417
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18803
19418
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18804
19419
|
const conditions = [SESSION_ROOT];
|
|
@@ -18872,7 +19487,7 @@ var SqliteActivityRepository = class {
|
|
|
18872
19487
|
)
|
|
18873
19488
|
);
|
|
18874
19489
|
const last = page[page.length - 1];
|
|
18875
|
-
const nextCursor = hasMore && last ?
|
|
19490
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
18876
19491
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
18877
19492
|
}
|
|
18878
19493
|
getSession(sessionId) {
|
|
@@ -19745,7 +20360,7 @@ var SqliteEventsRepository = class {
|
|
|
19745
20360
|
};
|
|
19746
20361
|
|
|
19747
20362
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19748
|
-
import { randomUUID } from "crypto";
|
|
20363
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19749
20364
|
|
|
19750
20365
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19751
20366
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19777,9 +20392,13 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19777
20392
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19778
20393
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19779
20394
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
20395
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
20396
|
+
AND conditions IS NULL
|
|
20397
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19780
20398
|
var SqliteExceptionsRepository = class {
|
|
19781
|
-
constructor(db) {
|
|
20399
|
+
constructor(db, now = () => Date.now()) {
|
|
19782
20400
|
this.db = db;
|
|
20401
|
+
this.now = now;
|
|
19783
20402
|
this.consumeStmt = db.prepare(
|
|
19784
20403
|
`UPDATE exceptions
|
|
19785
20404
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19797,6 +20416,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19797
20416
|
);
|
|
19798
20417
|
}
|
|
19799
20418
|
db;
|
|
20419
|
+
now;
|
|
19800
20420
|
consumeStmt;
|
|
19801
20421
|
insertBlockedStmt;
|
|
19802
20422
|
sweepBlockedStmt;
|
|
@@ -19823,8 +20443,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19823
20443
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19824
20444
|
);
|
|
19825
20445
|
}
|
|
19826
|
-
const id =
|
|
19827
|
-
const now =
|
|
20446
|
+
const id = randomUUID2();
|
|
20447
|
+
const now = this.now();
|
|
19828
20448
|
try {
|
|
19829
20449
|
this.insertExceptionRow(id, input, now);
|
|
19830
20450
|
} catch (err) {
|
|
@@ -19868,11 +20488,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19868
20488
|
this.db.prepare(
|
|
19869
20489
|
`INSERT INTO exceptions (
|
|
19870
20490
|
id, rule_id, category, value_fingerprint, key_version, masked_value,
|
|
19871
|
-
scope, expires_at, max_uses, use_count, last_used_at,
|
|
19872
|
-
conditions, created_by, created_via, created_at, updated_at
|
|
20491
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20492
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19873
20493
|
) VALUES (
|
|
19874
20494
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19875
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20495
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19876
20496
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19877
20497
|
)`
|
|
19878
20498
|
).run({
|
|
@@ -19882,6 +20502,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19882
20502
|
valueFingerprint: input.valueFingerprint,
|
|
19883
20503
|
keyVersion: input.keyVersion,
|
|
19884
20504
|
maskedValue: input.maskedValue,
|
|
20505
|
+
capability: input.capability ?? "suppress",
|
|
19885
20506
|
scope: input.scope,
|
|
19886
20507
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19887
20508
|
maxUses: input.maxUses,
|
|
@@ -19901,7 +20522,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19901
20522
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
19902
20523
|
const rows = allRows(
|
|
19903
20524
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
19904
|
-
opts?.includeTerminal ? {} : { now:
|
|
20525
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
19905
20526
|
);
|
|
19906
20527
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
19907
20528
|
return Promise.resolve(exceptions);
|
|
@@ -19936,7 +20557,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19936
20557
|
* already revoked.
|
|
19937
20558
|
*/
|
|
19938
20559
|
revoke(id, revokedBy, reason) {
|
|
19939
|
-
const now =
|
|
20560
|
+
const now = this.now();
|
|
19940
20561
|
const result = this.db.prepare(
|
|
19941
20562
|
`UPDATE exceptions
|
|
19942
20563
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -19950,7 +20571,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19950
20571
|
* callers must treat identically — means it does not and the detection is
|
|
19951
20572
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
19952
20573
|
*/
|
|
19953
|
-
consume(id, now =
|
|
20574
|
+
consume(id, now = this.now()) {
|
|
19954
20575
|
const result = this.consumeStmt.run({ id, now });
|
|
19955
20576
|
return Promise.resolve(Number(result.changes) === 1);
|
|
19956
20577
|
}
|
|
@@ -19959,7 +20580,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19959
20580
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
19960
20581
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
19961
20582
|
*/
|
|
19962
|
-
activeBundleEntries(keyVersion, now =
|
|
20583
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
19963
20584
|
const rows = allRows(
|
|
19964
20585
|
this.db.prepare(
|
|
19965
20586
|
`SELECT * FROM exceptions
|
|
@@ -19975,6 +20596,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19975
20596
|
ruleId: row.rule_id,
|
|
19976
20597
|
valueFingerprint: row.value_fingerprint,
|
|
19977
20598
|
keyVersion: row.key_version,
|
|
20599
|
+
capability: row.capability,
|
|
19978
20600
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
19979
20601
|
maxUses: row.max_uses,
|
|
19980
20602
|
useCount: row.use_count,
|
|
@@ -19990,7 +20612,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19990
20612
|
* than the retention window on every write, so the ledger self-limits.
|
|
19991
20613
|
*/
|
|
19992
20614
|
recordBlocked(entry) {
|
|
19993
|
-
const now =
|
|
20615
|
+
const now = this.now();
|
|
19994
20616
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
19995
20617
|
this.insertBlockedStmt.run({
|
|
19996
20618
|
reference: entry.reference,
|
|
@@ -20013,7 +20635,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20013
20635
|
WHERE blocked_at > :cutoff
|
|
20014
20636
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20015
20637
|
),
|
|
20016
|
-
{ cutoff:
|
|
20638
|
+
{ cutoff: this.now() - windowMs }
|
|
20017
20639
|
);
|
|
20018
20640
|
return Promise.resolve(
|
|
20019
20641
|
rows.map((row) => ({
|
|
@@ -20029,6 +20651,36 @@ var SqliteExceptionsRepository = class {
|
|
|
20029
20651
|
}))
|
|
20030
20652
|
);
|
|
20031
20653
|
}
|
|
20654
|
+
/**
|
|
20655
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20656
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20657
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20658
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20659
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20660
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20661
|
+
*
|
|
20662
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20663
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20664
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20665
|
+
*/
|
|
20666
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20667
|
+
try {
|
|
20668
|
+
const at = now ?? this.now();
|
|
20669
|
+
const row = getRow(
|
|
20670
|
+
this.db.prepare(
|
|
20671
|
+
`SELECT id FROM exceptions
|
|
20672
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20673
|
+
AND key_version = :keyVersion
|
|
20674
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20675
|
+
LIMIT 1`
|
|
20676
|
+
),
|
|
20677
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20678
|
+
);
|
|
20679
|
+
return Promise.resolve(row ?? null);
|
|
20680
|
+
} catch (err) {
|
|
20681
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20032
20684
|
/**
|
|
20033
20685
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20034
20686
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20036,7 +20688,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20036
20688
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20037
20689
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20038
20690
|
*/
|
|
20039
|
-
sweepTerminal(retentionMs, now =
|
|
20691
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20040
20692
|
const result = this.db.prepare(
|
|
20041
20693
|
`DELETE FROM exceptions
|
|
20042
20694
|
WHERE updated_at < :cutoff
|
|
@@ -20056,6 +20708,7 @@ function parseExceptionRow(row) {
|
|
|
20056
20708
|
valueFingerprint: row.value_fingerprint,
|
|
20057
20709
|
keyVersion: row.key_version,
|
|
20058
20710
|
maskedValue: row.masked_value,
|
|
20711
|
+
capability: row.capability,
|
|
20059
20712
|
scope: row.scope,
|
|
20060
20713
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20061
20714
|
maxUses: row.max_uses,
|
|
@@ -20098,6 +20751,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20098
20751
|
|
|
20099
20752
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20100
20753
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20754
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20755
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20756
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20757
|
+
function compareLocationOrder(a, b) {
|
|
20758
|
+
return compareFindingGroupOrder(
|
|
20759
|
+
{
|
|
20760
|
+
severity: a.maxSeverity,
|
|
20761
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20762
|
+
id: ""
|
|
20763
|
+
},
|
|
20764
|
+
{
|
|
20765
|
+
severity: b.maxSeverity,
|
|
20766
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20767
|
+
id: ""
|
|
20768
|
+
}
|
|
20769
|
+
);
|
|
20770
|
+
}
|
|
20101
20771
|
var CONCAT_SEP = ",";
|
|
20102
20772
|
var TUPLE_SEP = "|";
|
|
20103
20773
|
function splitConcat(value) {
|
|
@@ -20110,6 +20780,33 @@ function deriveInstanceStatus(row) {
|
|
|
20110
20780
|
latestResolutionStatus: row.latest_status
|
|
20111
20781
|
});
|
|
20112
20782
|
}
|
|
20783
|
+
function encodeGroupCursor(group) {
|
|
20784
|
+
const payload = {
|
|
20785
|
+
sev: group.severity,
|
|
20786
|
+
t: group.latestDetectedAt,
|
|
20787
|
+
id: group.id
|
|
20788
|
+
};
|
|
20789
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20790
|
+
}
|
|
20791
|
+
function decodeGroupCursor(cursor) {
|
|
20792
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20793
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20794
|
+
return {
|
|
20795
|
+
severity: parsed.sev,
|
|
20796
|
+
latestDetectedAt: parsed.t,
|
|
20797
|
+
id: parsed.id
|
|
20798
|
+
};
|
|
20799
|
+
}
|
|
20800
|
+
return null;
|
|
20801
|
+
}
|
|
20802
|
+
function firstAfter(sorted, cursor) {
|
|
20803
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20804
|
+
return index === -1 ? sorted.length : index;
|
|
20805
|
+
}
|
|
20806
|
+
function findDeepLinked(sorted, page, id) {
|
|
20807
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20808
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20809
|
+
}
|
|
20113
20810
|
var DAY_MS3 = 864e5;
|
|
20114
20811
|
var SqliteFindingsRepository = class {
|
|
20115
20812
|
constructor(db) {
|
|
@@ -20219,8 +20916,13 @@ var SqliteFindingsRepository = class {
|
|
|
20219
20916
|
*/
|
|
20220
20917
|
listGroupedFindings(query) {
|
|
20221
20918
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20222
|
-
const
|
|
20223
|
-
const
|
|
20919
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20920
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20921
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20922
|
+
const sessionParams = {
|
|
20923
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20924
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20925
|
+
};
|
|
20224
20926
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20225
20927
|
predicate,
|
|
20226
20928
|
params: sessionParams
|
|
@@ -20228,7 +20930,8 @@ var SqliteFindingsRepository = class {
|
|
|
20228
20930
|
const rows = allRows(
|
|
20229
20931
|
this.db.prepare(
|
|
20230
20932
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20231
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20933
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20934
|
+
kind, finding_key, latest_status
|
|
20232
20935
|
FROM (
|
|
20233
20936
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20234
20937
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20238,6 +20941,7 @@ var SqliteFindingsRepository = class {
|
|
|
20238
20941
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20239
20942
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20240
20943
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
20944
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20241
20945
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20242
20946
|
latest.status AS latest_status,
|
|
20243
20947
|
ROW_NUMBER() OVER (
|
|
@@ -20269,6 +20973,8 @@ var SqliteFindingsRepository = class {
|
|
|
20269
20973
|
repo: r.repo ?? "",
|
|
20270
20974
|
file: r.file ?? "",
|
|
20271
20975
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
20976
|
+
eventId: r.event_id,
|
|
20977
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20272
20978
|
status: deriveInstanceStatus(r)
|
|
20273
20979
|
}));
|
|
20274
20980
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20292,18 +20998,23 @@ var SqliteFindingsRepository = class {
|
|
|
20292
20998
|
groups: sorted.length
|
|
20293
20999
|
};
|
|
20294
21000
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21001
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21002
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21003
|
+
const page = sorted.slice(start, start + limit);
|
|
21004
|
+
const lastOnPage = page.at(-1);
|
|
21005
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21006
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20295
21007
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20296
|
-
const
|
|
20297
|
-
|
|
20298
|
-
|
|
20299
|
-
|
|
20300
|
-
|
|
20301
|
-
);
|
|
21008
|
+
const narrow = (g) => statusSet ? {
|
|
21009
|
+
...g,
|
|
21010
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21011
|
+
} : g;
|
|
21012
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20302
21013
|
return Promise.resolve({
|
|
20303
21014
|
totals,
|
|
20304
21015
|
facets,
|
|
20305
21016
|
items,
|
|
20306
|
-
nextCursor
|
|
21017
|
+
nextCursor,
|
|
20307
21018
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20308
21019
|
});
|
|
20309
21020
|
}
|
|
@@ -20335,6 +21046,266 @@ var SqliteFindingsRepository = class {
|
|
|
20335
21046
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20336
21047
|
* path repeating across tuples.)
|
|
20337
21048
|
*/
|
|
21049
|
+
/**
|
|
21050
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21051
|
+
* paged by keyset.
|
|
21052
|
+
*
|
|
21053
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21054
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21055
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21056
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21057
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21058
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21059
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21060
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21061
|
+
* none of the mappers names", which no IN-list can say.
|
|
21062
|
+
*
|
|
21063
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21064
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21065
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21066
|
+
* while the counting runs, and only the page itself is retained.
|
|
21067
|
+
*/
|
|
21068
|
+
listFindingInstances(query) {
|
|
21069
|
+
const opts = {
|
|
21070
|
+
severity: query.severity,
|
|
21071
|
+
subtype: query.subtype,
|
|
21072
|
+
providers: query.provider,
|
|
21073
|
+
actions: query.action,
|
|
21074
|
+
statuses: query.status,
|
|
21075
|
+
tools: query.tool,
|
|
21076
|
+
repo: query.repo,
|
|
21077
|
+
file: query.file,
|
|
21078
|
+
q: query.q
|
|
21079
|
+
};
|
|
21080
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21081
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21082
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21083
|
+
const items = [];
|
|
21084
|
+
let total = 0;
|
|
21085
|
+
let last;
|
|
21086
|
+
let hasMore = false;
|
|
21087
|
+
for (const row of this.scanFindingRows({
|
|
21088
|
+
sessionId: query.sessionId,
|
|
21089
|
+
from: query.from
|
|
21090
|
+
})) {
|
|
21091
|
+
accumulator.add(row);
|
|
21092
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21093
|
+
total += 1;
|
|
21094
|
+
if (items.length < limit) {
|
|
21095
|
+
items.push(toInstanceDetail(row));
|
|
21096
|
+
last = row;
|
|
21097
|
+
} else {
|
|
21098
|
+
hasMore = true;
|
|
21099
|
+
}
|
|
21100
|
+
}
|
|
21101
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21102
|
+
if (cursor !== null) {
|
|
21103
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21104
|
+
return Promise.resolve({
|
|
21105
|
+
totals: { findings: total },
|
|
21106
|
+
facets: accumulator.facets(),
|
|
21107
|
+
items: resumed.items,
|
|
21108
|
+
nextCursor: resumed.nextCursor
|
|
21109
|
+
});
|
|
21110
|
+
}
|
|
21111
|
+
return Promise.resolve({
|
|
21112
|
+
totals: { findings: total },
|
|
21113
|
+
facets: accumulator.facets(),
|
|
21114
|
+
items,
|
|
21115
|
+
nextCursor
|
|
21116
|
+
});
|
|
21117
|
+
}
|
|
21118
|
+
/**
|
|
21119
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21120
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21121
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21122
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21123
|
+
*/
|
|
21124
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21125
|
+
const items = [];
|
|
21126
|
+
let last;
|
|
21127
|
+
let hasMore = false;
|
|
21128
|
+
for (const row of this.scanFindingRows({
|
|
21129
|
+
sessionId: query.sessionId,
|
|
21130
|
+
from: query.from,
|
|
21131
|
+
after: cursor
|
|
21132
|
+
})) {
|
|
21133
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21134
|
+
if (items.length < limit) {
|
|
21135
|
+
items.push(toInstanceDetail(row));
|
|
21136
|
+
last = row;
|
|
21137
|
+
} else {
|
|
21138
|
+
hasMore = true;
|
|
21139
|
+
break;
|
|
21140
|
+
}
|
|
21141
|
+
}
|
|
21142
|
+
return {
|
|
21143
|
+
items,
|
|
21144
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21145
|
+
};
|
|
21146
|
+
}
|
|
21147
|
+
/**
|
|
21148
|
+
* The same findings folded by location: repository, then file within it.
|
|
21149
|
+
*
|
|
21150
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21151
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21152
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21153
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21154
|
+
* filter can name it.
|
|
21155
|
+
*/
|
|
21156
|
+
listFindingLocations(query) {
|
|
21157
|
+
const opts = {
|
|
21158
|
+
severity: query.severity,
|
|
21159
|
+
subtype: query.subtype,
|
|
21160
|
+
providers: query.provider,
|
|
21161
|
+
actions: query.action,
|
|
21162
|
+
statuses: query.status,
|
|
21163
|
+
tools: query.tool,
|
|
21164
|
+
q: query.q
|
|
21165
|
+
};
|
|
21166
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21167
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21168
|
+
let total = 0;
|
|
21169
|
+
for (const row of this.scanFindingRows({
|
|
21170
|
+
sessionId: query.sessionId,
|
|
21171
|
+
from: query.from
|
|
21172
|
+
})) {
|
|
21173
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21174
|
+
total += 1;
|
|
21175
|
+
let files = byRepo.get(row.repo);
|
|
21176
|
+
if (files === void 0) {
|
|
21177
|
+
files = /* @__PURE__ */ new Map();
|
|
21178
|
+
byRepo.set(row.repo, files);
|
|
21179
|
+
}
|
|
21180
|
+
let acc = files.get(row.file);
|
|
21181
|
+
if (acc === void 0) {
|
|
21182
|
+
acc = newLocationAccumulator();
|
|
21183
|
+
files.set(row.file, acc);
|
|
21184
|
+
}
|
|
21185
|
+
addToLocation(acc, row);
|
|
21186
|
+
}
|
|
21187
|
+
let fileCount = 0;
|
|
21188
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21189
|
+
fileCount += files.size;
|
|
21190
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21191
|
+
file: file2,
|
|
21192
|
+
instanceCount: acc.instanceCount,
|
|
21193
|
+
maxSeverity: acc.maxSeverity,
|
|
21194
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21195
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21196
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21197
|
+
})).sort(compareLocationOrder);
|
|
21198
|
+
const rollup = fileRows.reduce(
|
|
21199
|
+
(a, f) => ({
|
|
21200
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21201
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21202
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21203
|
+
}),
|
|
21204
|
+
{
|
|
21205
|
+
instanceCount: 0,
|
|
21206
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21207
|
+
latestDetectedAt: ""
|
|
21208
|
+
}
|
|
21209
|
+
);
|
|
21210
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21211
|
+
const folded = foldGroupStatus(statuses);
|
|
21212
|
+
return {
|
|
21213
|
+
repo,
|
|
21214
|
+
instanceCount: rollup.instanceCount,
|
|
21215
|
+
maxSeverity: rollup.maxSeverity,
|
|
21216
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21217
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21218
|
+
files: fileRows
|
|
21219
|
+
};
|
|
21220
|
+
});
|
|
21221
|
+
repos.sort(compareLocationOrder);
|
|
21222
|
+
return Promise.resolve({
|
|
21223
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21224
|
+
items: repos.slice(0, limit),
|
|
21225
|
+
hasMore: repos.length > limit
|
|
21226
|
+
});
|
|
21227
|
+
}
|
|
21228
|
+
/**
|
|
21229
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21230
|
+
*
|
|
21231
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21232
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21233
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21234
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21235
|
+
* rather than one unbounded result set.
|
|
21236
|
+
*
|
|
21237
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21238
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21239
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21240
|
+
* a window over the whole resolution table once per batch.
|
|
21241
|
+
*
|
|
21242
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21243
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21244
|
+
* dimension — see listFindingInstances.
|
|
21245
|
+
*/
|
|
21246
|
+
*scanFindingRows(scope) {
|
|
21247
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21248
|
+
const params = [];
|
|
21249
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21250
|
+
conditions.push("e.root_session_id = ?");
|
|
21251
|
+
params.push(scope.sessionId);
|
|
21252
|
+
}
|
|
21253
|
+
if (scope.from !== void 0) {
|
|
21254
|
+
conditions.push("e.started_at >= ?");
|
|
21255
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21256
|
+
}
|
|
21257
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21258
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21259
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21260
|
+
e.started_at AS occurred_at,
|
|
21261
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21262
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21263
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21264
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21265
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21266
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21267
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21268
|
+
FROM inspection_findings f
|
|
21269
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21270
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21271
|
+
WHERE ${conditions.join(" AND ")}
|
|
21272
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21273
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21274
|
+
LIMIT ?`;
|
|
21275
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21276
|
+
for (; ; ) {
|
|
21277
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21278
|
+
...params,
|
|
21279
|
+
after.startedAtMs,
|
|
21280
|
+
after.startedAtMs,
|
|
21281
|
+
after.id,
|
|
21282
|
+
SCAN_BATCH_ROWS
|
|
21283
|
+
]);
|
|
21284
|
+
for (const r of rows) {
|
|
21285
|
+
yield {
|
|
21286
|
+
id: r.id,
|
|
21287
|
+
ruleId: r.rule_id,
|
|
21288
|
+
category: r.category,
|
|
21289
|
+
severity: r.severity,
|
|
21290
|
+
maskedMatch: r.masked_match,
|
|
21291
|
+
actionTaken: r.action_taken,
|
|
21292
|
+
confidence: r.confidence,
|
|
21293
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21294
|
+
sourceTool: r.source_tool,
|
|
21295
|
+
repo: r.repo ?? "",
|
|
21296
|
+
file: r.file ?? "",
|
|
21297
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21298
|
+
eventId: r.event_id,
|
|
21299
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21300
|
+
status: deriveInstanceStatus(r)
|
|
21301
|
+
};
|
|
21302
|
+
}
|
|
21303
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21304
|
+
const lastRow = rows[rows.length - 1];
|
|
21305
|
+
if (lastRow === void 0) return;
|
|
21306
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21307
|
+
}
|
|
21308
|
+
}
|
|
20338
21309
|
groupAggregates(withSearchText, scope) {
|
|
20339
21310
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20340
21311
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20595,7 +21566,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20595
21566
|
};
|
|
20596
21567
|
|
|
20597
21568
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20598
|
-
import { createHash as createHash2, randomUUID as
|
|
21569
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20599
21570
|
|
|
20600
21571
|
// ../../packages/persistence/src/semver.ts
|
|
20601
21572
|
function parse3(version2) {
|
|
@@ -20746,7 +21717,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20746
21717
|
let behind = false;
|
|
20747
21718
|
for (const row of rows) {
|
|
20748
21719
|
const params = {
|
|
20749
|
-
id:
|
|
21720
|
+
id: randomUUID3(),
|
|
20750
21721
|
namespace: row.namespace,
|
|
20751
21722
|
packId: row.packId,
|
|
20752
21723
|
version: row.version,
|
|
@@ -20758,7 +21729,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20758
21729
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20759
21730
|
this.upsertAvailableStmt.run({
|
|
20760
21731
|
...params,
|
|
20761
|
-
id:
|
|
21732
|
+
id: randomUUID3(),
|
|
20762
21733
|
recordedBy: meta3?.recordedBy ?? null
|
|
20763
21734
|
});
|
|
20764
21735
|
} else {
|
|
@@ -21081,14 +22052,15 @@ var SqliteInventoryRepository = class {
|
|
|
21081
22052
|
};
|
|
21082
22053
|
|
|
21083
22054
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21084
|
-
import { randomUUID as
|
|
22055
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21085
22056
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21086
22057
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21087
22058
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21088
22059
|
var HARNESS_LABELS = {
|
|
21089
22060
|
claudecode: "Claude Code",
|
|
21090
22061
|
cursor: "Cursor",
|
|
21091
|
-
codex: "Codex"
|
|
22062
|
+
codex: "Codex",
|
|
22063
|
+
antigravity: "Antigravity"
|
|
21092
22064
|
};
|
|
21093
22065
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21094
22066
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21103,6 +22075,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21103
22075
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21104
22076
|
if (t.includes("cursor")) return "cursor";
|
|
21105
22077
|
if (t.includes("codex")) return "codex";
|
|
22078
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21106
22079
|
return null;
|
|
21107
22080
|
}
|
|
21108
22081
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21561,7 +22534,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21561
22534
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21562
22535
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21563
22536
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21564
|
-
).run({ id:
|
|
22537
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21565
22538
|
}
|
|
21566
22539
|
return true;
|
|
21567
22540
|
}
|
|
@@ -21582,7 +22555,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21582
22555
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21583
22556
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21584
22557
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21585
|
-
).run({ id:
|
|
22558
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21586
22559
|
}
|
|
21587
22560
|
this.configRowsCache = void 0;
|
|
21588
22561
|
return "ok";
|
|
@@ -21879,7 +22852,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21879
22852
|
};
|
|
21880
22853
|
|
|
21881
22854
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
21882
|
-
import { randomUUID as
|
|
22855
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
21883
22856
|
var SqlitePoliciesRepository = class {
|
|
21884
22857
|
constructor(db) {
|
|
21885
22858
|
this.db = db;
|
|
@@ -21914,7 +22887,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21914
22887
|
failOpenTransaction(this.db, () => {
|
|
21915
22888
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
21916
22889
|
stmt.run({
|
|
21917
|
-
id:
|
|
22890
|
+
id: randomUUID5(),
|
|
21918
22891
|
target: JSON.stringify({ category }),
|
|
21919
22892
|
action,
|
|
21920
22893
|
now: Date.now()
|
|
@@ -21934,7 +22907,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21934
22907
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
21935
22908
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
21936
22909
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
21937
|
-
).run({ id:
|
|
22910
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
21938
22911
|
}
|
|
21939
22912
|
// Caps every global per-category policy currently set to block/redact down
|
|
21940
22913
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22002,7 +22975,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22002
22975
|
};
|
|
22003
22976
|
|
|
22004
22977
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22005
|
-
import { randomUUID as
|
|
22978
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22006
22979
|
var SqliteProjectFilesRepository = class {
|
|
22007
22980
|
constructor(db) {
|
|
22008
22981
|
this.db = db;
|
|
@@ -22034,7 +23007,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22034
23007
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22035
23008
|
for (const file2 of scan2.files) {
|
|
22036
23009
|
this.upsertStmt.run({
|
|
22037
|
-
id:
|
|
23010
|
+
id: randomUUID6(),
|
|
22038
23011
|
projectId,
|
|
22039
23012
|
path: file2.path,
|
|
22040
23013
|
name: file2.name,
|
|
@@ -22048,7 +23021,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22048
23021
|
};
|
|
22049
23022
|
|
|
22050
23023
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22051
|
-
import { randomUUID as
|
|
23024
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22052
23025
|
var SqliteResolutionsRepository = class {
|
|
22053
23026
|
constructor(db, now = () => Date.now()) {
|
|
22054
23027
|
this.db = db;
|
|
@@ -22102,7 +23075,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22102
23075
|
*/
|
|
22103
23076
|
insertResolution(r) {
|
|
22104
23077
|
this.insertStmt.run({
|
|
22105
|
-
id:
|
|
23078
|
+
id: randomUUID7(),
|
|
22106
23079
|
findingKey: r.findingKey,
|
|
22107
23080
|
status: FindingStatus.parse(r.status),
|
|
22108
23081
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22161,13 +23134,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22161
23134
|
this.readStmt = db.prepare(
|
|
22162
23135
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22163
23136
|
);
|
|
23137
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23138
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23139
|
+
);
|
|
23140
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23141
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23142
|
+
);
|
|
22164
23143
|
}
|
|
22165
23144
|
db;
|
|
22166
23145
|
upsertStmt;
|
|
22167
23146
|
readStmt;
|
|
23147
|
+
countQuarantinedStmt;
|
|
23148
|
+
clearQuarantinedStmt;
|
|
22168
23149
|
getVerdict(ruleKey) {
|
|
22169
23150
|
return getRow(this.readStmt, { ruleKey });
|
|
22170
23151
|
}
|
|
23152
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23153
|
+
countQuarantined() {
|
|
23154
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23155
|
+
}
|
|
23156
|
+
/**
|
|
23157
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23158
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23159
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23160
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23161
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23162
|
+
*
|
|
23163
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23164
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23165
|
+
*
|
|
23166
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23167
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23168
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23169
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23170
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23171
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23172
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23173
|
+
* to restore are silently still disabled.
|
|
23174
|
+
*/
|
|
23175
|
+
clearQuarantined() {
|
|
23176
|
+
const before = this.countQuarantined();
|
|
23177
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23178
|
+
this.clearQuarantinedStmt.run();
|
|
23179
|
+
});
|
|
23180
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23181
|
+
}
|
|
22171
23182
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22172
23183
|
failOpenTransaction(this.db, () => {
|
|
22173
23184
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22221,6 +23232,419 @@ var SqliteScanLedgerRepository = class {
|
|
|
22221
23232
|
}
|
|
22222
23233
|
};
|
|
22223
23234
|
|
|
23235
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
23236
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23237
|
+
function pageLimit(requested, fallback) {
|
|
23238
|
+
if (requested === void 0) return fallback;
|
|
23239
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23240
|
+
}
|
|
23241
|
+
function encodeReuseCursor(payload) {
|
|
23242
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23243
|
+
}
|
|
23244
|
+
function decodeReuseCursor(cursor) {
|
|
23245
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23246
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23247
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23248
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23249
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23250
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23251
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23252
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23253
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23254
|
+
}
|
|
23255
|
+
return null;
|
|
23256
|
+
}
|
|
23257
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23258
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23259
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23260
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23261
|
+
function toSighting(row) {
|
|
23262
|
+
return {
|
|
23263
|
+
location: row.location,
|
|
23264
|
+
kind: row.kind,
|
|
23265
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23266
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23267
|
+
};
|
|
23268
|
+
}
|
|
23269
|
+
var SELECT_COLUMNS = `
|
|
23270
|
+
pointer_id AS pointerId,
|
|
23271
|
+
value_fingerprint AS valueFingerprint,
|
|
23272
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
23273
|
+
key_version AS keyVersion,
|
|
23274
|
+
format_version AS formatVersion,
|
|
23275
|
+
category,
|
|
23276
|
+
rule_id AS ruleId,
|
|
23277
|
+
masked_match AS maskedMatch,
|
|
23278
|
+
provider,
|
|
23279
|
+
ciphertext,
|
|
23280
|
+
nonce,
|
|
23281
|
+
auth_tag AS authTag,
|
|
23282
|
+
occurrence_count AS occurrenceCount,
|
|
23283
|
+
first_seen AS firstSeen,
|
|
23284
|
+
last_seen AS lastSeen`;
|
|
23285
|
+
function toRow(raw) {
|
|
23286
|
+
const { provider, ...rest } = raw;
|
|
23287
|
+
return provider === null ? rest : { ...rest, provider };
|
|
23288
|
+
}
|
|
23289
|
+
var SqliteSecretVaultRepository = class {
|
|
23290
|
+
constructor(db) {
|
|
23291
|
+
this.db = db;
|
|
23292
|
+
this.insertStmt = db.prepare(
|
|
23293
|
+
`INSERT INTO secret_vault (
|
|
23294
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
23295
|
+
format_version, category, rule_id, masked_match, provider,
|
|
23296
|
+
ciphertext, nonce, auth_tag,
|
|
23297
|
+
occurrence_count, first_seen, last_seen
|
|
23298
|
+
) VALUES (
|
|
23299
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
23300
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
23301
|
+
:ciphertext, :nonce, :authTag,
|
|
23302
|
+
1, :now, :now
|
|
23303
|
+
)`
|
|
23304
|
+
);
|
|
23305
|
+
this.bumpStmt = db.prepare(
|
|
23306
|
+
`UPDATE secret_vault
|
|
23307
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
23308
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
23309
|
+
);
|
|
23310
|
+
this.byPointerStmt = db.prepare(
|
|
23311
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
23312
|
+
);
|
|
23313
|
+
this.byFingerprintStmt = db.prepare(
|
|
23314
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
23315
|
+
);
|
|
23316
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
23317
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
23318
|
+
`UPDATE secret_vault
|
|
23319
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
23320
|
+
WHERE pointer_id = :pointerId`
|
|
23321
|
+
);
|
|
23322
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
23323
|
+
`UPDATE secret_vault
|
|
23324
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
23325
|
+
WHERE pointer_id = :pointerId`
|
|
23326
|
+
);
|
|
23327
|
+
this.derefStmt = db.prepare(
|
|
23328
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
23329
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
23330
|
+
);
|
|
23331
|
+
}
|
|
23332
|
+
db;
|
|
23333
|
+
insertStmt;
|
|
23334
|
+
bumpStmt;
|
|
23335
|
+
byPointerStmt;
|
|
23336
|
+
byFingerprintStmt;
|
|
23337
|
+
listStmt;
|
|
23338
|
+
replaceCiphertextStmt;
|
|
23339
|
+
refreshFingerprintStmt;
|
|
23340
|
+
derefStmt;
|
|
23341
|
+
/**
|
|
23342
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
23343
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
23344
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
23345
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
23346
|
+
* wire token. `minted` is true only when this call created the row.
|
|
23347
|
+
*
|
|
23348
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
23349
|
+
* writers cannot both decide they are minting.
|
|
23350
|
+
*/
|
|
23351
|
+
upsert(input, now) {
|
|
23352
|
+
let minted = false;
|
|
23353
|
+
withTransaction(
|
|
23354
|
+
this.db,
|
|
23355
|
+
() => {
|
|
23356
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
23357
|
+
valueFingerprint: input.valueFingerprint
|
|
23358
|
+
});
|
|
23359
|
+
if (existing === void 0) {
|
|
23360
|
+
this.insertStmt.run(
|
|
23361
|
+
bindParams({
|
|
23362
|
+
pointerId: input.pointerId,
|
|
23363
|
+
valueFingerprint: input.valueFingerprint,
|
|
23364
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
23365
|
+
keyVersion: input.keyVersion,
|
|
23366
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
23367
|
+
category: input.category,
|
|
23368
|
+
ruleId: input.ruleId,
|
|
23369
|
+
maskedMatch: input.maskedMatch,
|
|
23370
|
+
provider: input.provider,
|
|
23371
|
+
ciphertext: input.ciphertext,
|
|
23372
|
+
nonce: input.nonce,
|
|
23373
|
+
authTag: input.authTag,
|
|
23374
|
+
now
|
|
23375
|
+
})
|
|
23376
|
+
);
|
|
23377
|
+
minted = true;
|
|
23378
|
+
return;
|
|
23379
|
+
}
|
|
23380
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
23381
|
+
},
|
|
23382
|
+
"IMMEDIATE"
|
|
23383
|
+
);
|
|
23384
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
23385
|
+
valueFingerprint: input.valueFingerprint
|
|
23386
|
+
});
|
|
23387
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
23388
|
+
return { row: toRow(row), minted };
|
|
23389
|
+
}
|
|
23390
|
+
byPointerId(pointerId) {
|
|
23391
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
23392
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23393
|
+
}
|
|
23394
|
+
byValueFingerprint(fingerprint) {
|
|
23395
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
23396
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23397
|
+
}
|
|
23398
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
23399
|
+
recordDeref(entry) {
|
|
23400
|
+
this.derefStmt.run(
|
|
23401
|
+
bindParams({
|
|
23402
|
+
id: entry.id,
|
|
23403
|
+
pointerId: entry.pointerId,
|
|
23404
|
+
at: entry.at,
|
|
23405
|
+
target: entry.target,
|
|
23406
|
+
reason: entry.reason,
|
|
23407
|
+
outcome: entry.outcome,
|
|
23408
|
+
grantId: entry.grantId,
|
|
23409
|
+
pointerCount: entry.pointerCount ?? 1
|
|
23410
|
+
})
|
|
23411
|
+
);
|
|
23412
|
+
}
|
|
23413
|
+
listAll() {
|
|
23414
|
+
return allRows(this.listStmt).map(toRow);
|
|
23415
|
+
}
|
|
23416
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
23417
|
+
replaceCiphertext(pointerId, next) {
|
|
23418
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
23419
|
+
}
|
|
23420
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
23421
|
+
refreshFingerprint(pointerId, next) {
|
|
23422
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
23423
|
+
}
|
|
23424
|
+
/**
|
|
23425
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
23426
|
+
* audit is left alone on purpose — see the table note above.
|
|
23427
|
+
*/
|
|
23428
|
+
purgeAll() {
|
|
23429
|
+
let destroyed = 0;
|
|
23430
|
+
withTransaction(
|
|
23431
|
+
this.db,
|
|
23432
|
+
() => {
|
|
23433
|
+
destroyed = this.countEntries();
|
|
23434
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
23435
|
+
},
|
|
23436
|
+
"IMMEDIATE"
|
|
23437
|
+
);
|
|
23438
|
+
return destroyed;
|
|
23439
|
+
}
|
|
23440
|
+
/**
|
|
23441
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
23442
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
23443
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
23444
|
+
* so callers wrap this, not the other way around.
|
|
23445
|
+
*/
|
|
23446
|
+
recordSighting(entry, now) {
|
|
23447
|
+
this.db.prepare(
|
|
23448
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
23449
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
23450
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
23451
|
+
).run({
|
|
23452
|
+
id: randomUUID8(),
|
|
23453
|
+
pointerId: entry.pointerId,
|
|
23454
|
+
location: entry.location,
|
|
23455
|
+
kind: entry.kind,
|
|
23456
|
+
now
|
|
23457
|
+
});
|
|
23458
|
+
}
|
|
23459
|
+
/**
|
|
23460
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23461
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23462
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23463
|
+
*
|
|
23464
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23465
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23466
|
+
*/
|
|
23467
|
+
sightingsFor(pointerIds) {
|
|
23468
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23469
|
+
if (pointerIds.length === 0) return byPointer;
|
|
23470
|
+
const rows = allRows(
|
|
23471
|
+
this.db.prepare(
|
|
23472
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23473
|
+
FROM secret_vault_sighting
|
|
23474
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23475
|
+
ORDER BY last_seen DESC`
|
|
23476
|
+
),
|
|
23477
|
+
pointerIds
|
|
23478
|
+
);
|
|
23479
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23480
|
+
return byPointer;
|
|
23481
|
+
}
|
|
23482
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23483
|
+
toInventoryEntries(rows) {
|
|
23484
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
23485
|
+
return rows.map((r) => ({
|
|
23486
|
+
pointerId: r.pointer_id,
|
|
23487
|
+
category: r.category,
|
|
23488
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23489
|
+
maskedMatch: r.masked_match,
|
|
23490
|
+
occurrences: r.occurrence_count,
|
|
23491
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
23492
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23493
|
+
revealGrantId: r.grant_id,
|
|
23494
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
23495
|
+
}));
|
|
23496
|
+
}
|
|
23497
|
+
/**
|
|
23498
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23499
|
+
* value's descriptor data joined with its sightings and the active
|
|
23500
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23501
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23502
|
+
*
|
|
23503
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23504
|
+
* sees never depends on how far they have paged.
|
|
23505
|
+
*/
|
|
23506
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23507
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23508
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23509
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
23510
|
+
const rows = allRows(
|
|
23511
|
+
this.db.prepare(
|
|
23512
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23513
|
+
(SELECT e.id FROM exceptions e
|
|
23514
|
+
WHERE e.rule_id = v.rule_id
|
|
23515
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23516
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23517
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23518
|
+
LIMIT 1) AS grant_id
|
|
23519
|
+
FROM secret_vault v
|
|
23520
|
+
${where}
|
|
23521
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23522
|
+
LIMIT :limit`
|
|
23523
|
+
),
|
|
23524
|
+
bindParams({
|
|
23525
|
+
now,
|
|
23526
|
+
limit: limit + 1,
|
|
23527
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23528
|
+
})
|
|
23529
|
+
);
|
|
23530
|
+
const hasMore = rows.length > limit;
|
|
23531
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23532
|
+
const last = page[page.length - 1];
|
|
23533
|
+
return {
|
|
23534
|
+
totals: { values: this.countEntries() },
|
|
23535
|
+
items: this.toInventoryEntries(page),
|
|
23536
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23537
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23538
|
+
};
|
|
23539
|
+
}
|
|
23540
|
+
/**
|
|
23541
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23542
|
+
* than one location — most-reused first, one page at a time.
|
|
23543
|
+
*
|
|
23544
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23545
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23546
|
+
* under-report exactly the values a reader most needs to see.
|
|
23547
|
+
*/
|
|
23548
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23549
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23550
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23551
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23552
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23553
|
+
const rows = allRows(
|
|
23554
|
+
this.db.prepare(
|
|
23555
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23556
|
+
(SELECT e.id FROM exceptions e
|
|
23557
|
+
WHERE e.rule_id = v.rule_id
|
|
23558
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23559
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23560
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23561
|
+
LIMIT 1) AS grant_id
|
|
23562
|
+
FROM secret_vault v
|
|
23563
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23564
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23565
|
+
LIMIT :limit`
|
|
23566
|
+
),
|
|
23567
|
+
bindParams({
|
|
23568
|
+
now,
|
|
23569
|
+
limit: limit + 1,
|
|
23570
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23571
|
+
})
|
|
23572
|
+
);
|
|
23573
|
+
const hasMore = rows.length > limit;
|
|
23574
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23575
|
+
const last = page[page.length - 1];
|
|
23576
|
+
return {
|
|
23577
|
+
totals: { reused: this.countReused() },
|
|
23578
|
+
items: this.toInventoryEntries(page),
|
|
23579
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23580
|
+
};
|
|
23581
|
+
}
|
|
23582
|
+
/**
|
|
23583
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23584
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23585
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23586
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23587
|
+
*
|
|
23588
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23589
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23590
|
+
* the reader pages.
|
|
23591
|
+
*/
|
|
23592
|
+
listDerefs(query = {}) {
|
|
23593
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23594
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23595
|
+
const conditions = [];
|
|
23596
|
+
if (query.includeBatched !== true) {
|
|
23597
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23598
|
+
}
|
|
23599
|
+
if (cursor !== null) {
|
|
23600
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23601
|
+
}
|
|
23602
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
23603
|
+
const rows = allRows(
|
|
23604
|
+
this.db.prepare(
|
|
23605
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
23606
|
+
FROM secret_vault_deref ${where}
|
|
23607
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
23608
|
+
),
|
|
23609
|
+
bindParams({
|
|
23610
|
+
limit: limit + 1,
|
|
23611
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23612
|
+
})
|
|
23613
|
+
);
|
|
23614
|
+
const hasMore = rows.length > limit;
|
|
23615
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23616
|
+
const last = page[page.length - 1];
|
|
23617
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
23618
|
+
this.db,
|
|
23619
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
23620
|
+
);
|
|
23621
|
+
return {
|
|
23622
|
+
items: page.map((r) => ({
|
|
23623
|
+
id: r.id,
|
|
23624
|
+
pointerId: r.pointer_id,
|
|
23625
|
+
at: new Date(r.at).toISOString(),
|
|
23626
|
+
target: r.target,
|
|
23627
|
+
reason: r.reason,
|
|
23628
|
+
outcome: r.outcome,
|
|
23629
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
23630
|
+
pointerCount: r.pointer_count
|
|
23631
|
+
})),
|
|
23632
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
23633
|
+
hiddenBatched
|
|
23634
|
+
};
|
|
23635
|
+
}
|
|
23636
|
+
countEntries() {
|
|
23637
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
23638
|
+
}
|
|
23639
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23640
|
+
countReused() {
|
|
23641
|
+
return countScalar(
|
|
23642
|
+
this.db,
|
|
23643
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23644
|
+
);
|
|
23645
|
+
}
|
|
23646
|
+
};
|
|
23647
|
+
|
|
22224
23648
|
// ../../packages/persistence/src/repositories/security.ts
|
|
22225
23649
|
var DAY_MS4 = 864e5;
|
|
22226
23650
|
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
@@ -22233,7 +23657,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22233
23657
|
var SCAN_COVERAGE = [
|
|
22234
23658
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22235
23659
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22236
|
-
{ provider: "codex", coverage:
|
|
23660
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23661
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23662
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22237
23663
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22238
23664
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22239
23665
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -22566,7 +23992,7 @@ var SqliteSecurityRepository = class {
|
|
|
22566
23992
|
};
|
|
22567
23993
|
|
|
22568
23994
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22569
|
-
import { randomUUID as
|
|
23995
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
22570
23996
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22571
23997
|
var IN_CHUNK = 500;
|
|
22572
23998
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22822,7 +24248,7 @@ var SqliteSharesRepository = class {
|
|
|
22822
24248
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22823
24249
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22824
24250
|
).run({
|
|
22825
|
-
id:
|
|
24251
|
+
id: randomUUID9(),
|
|
22826
24252
|
destinationId,
|
|
22827
24253
|
host: dest.host,
|
|
22828
24254
|
decision,
|
|
@@ -22971,7 +24397,7 @@ var SqliteSharesRepository = class {
|
|
|
22971
24397
|
let destinationId = destIds.get(hit.host);
|
|
22972
24398
|
if (destinationId === void 0) {
|
|
22973
24399
|
destStmt.run({
|
|
22974
|
-
id:
|
|
24400
|
+
id: randomUUID9(),
|
|
22975
24401
|
kind: hit.kind,
|
|
22976
24402
|
name: hit.name,
|
|
22977
24403
|
host: hit.host,
|
|
@@ -22987,7 +24413,7 @@ var SqliteSharesRepository = class {
|
|
|
22987
24413
|
let endpointId = endpointIds.get(endpointKey);
|
|
22988
24414
|
if (endpointId === void 0) {
|
|
22989
24415
|
endpointStmt.run({
|
|
22990
|
-
id:
|
|
24416
|
+
id: randomUUID9(),
|
|
22991
24417
|
destinationId,
|
|
22992
24418
|
method: hit.method,
|
|
22993
24419
|
transport: hit.transport,
|
|
@@ -23000,7 +24426,7 @@ var SqliteSharesRepository = class {
|
|
|
23000
24426
|
endpointIds.set(endpointKey, endpointId);
|
|
23001
24427
|
}
|
|
23002
24428
|
siteStmt.run({
|
|
23003
|
-
id:
|
|
24429
|
+
id: randomUUID9(),
|
|
23004
24430
|
endpointId,
|
|
23005
24431
|
project: input.project,
|
|
23006
24432
|
projectKey: input.projectKey,
|
|
@@ -23365,6 +24791,9 @@ function purgeSampleData(db) {
|
|
|
23365
24791
|
}
|
|
23366
24792
|
|
|
23367
24793
|
// ../../packages/persistence/src/database.ts
|
|
24794
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24795
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24796
|
+
);
|
|
23368
24797
|
function linkHost(input, hostId) {
|
|
23369
24798
|
return hostId ? { ...input, hostId } : input;
|
|
23370
24799
|
}
|
|
@@ -23386,21 +24815,34 @@ function openWithPragmas(file2) {
|
|
|
23386
24815
|
}
|
|
23387
24816
|
return db;
|
|
23388
24817
|
}
|
|
23389
|
-
function backupLegacyStore(file2) {
|
|
23390
|
-
|
|
23391
|
-
|
|
23392
|
-
|
|
23393
|
-
|
|
23394
|
-
|
|
24818
|
+
function backupLegacyStore(db, file2) {
|
|
24819
|
+
reapStalePartials(file2);
|
|
24820
|
+
const backup = backupPath(file2, "legacy");
|
|
24821
|
+
let snapshotted = false;
|
|
24822
|
+
let snapshotError;
|
|
24823
|
+
try {
|
|
24824
|
+
snapshotStore(db, backup);
|
|
24825
|
+
snapshotted = true;
|
|
24826
|
+
} catch (error51) {
|
|
24827
|
+
snapshotError = error51;
|
|
24828
|
+
} finally {
|
|
24829
|
+
db.close();
|
|
23395
24830
|
}
|
|
24831
|
+
if (!snapshotted) {
|
|
24832
|
+
akaWarn(
|
|
24833
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24834
|
+
);
|
|
24835
|
+
moveStoreAside(file2, backup);
|
|
24836
|
+
return backup;
|
|
24837
|
+
}
|
|
24838
|
+
discardStore(file2, backup);
|
|
23396
24839
|
return backup;
|
|
23397
24840
|
}
|
|
23398
24841
|
function openAndInitialize(file2) {
|
|
23399
24842
|
let db = openWithPragmas(file2);
|
|
23400
24843
|
try {
|
|
23401
24844
|
if (isForeignSqliteLineage(db)) {
|
|
23402
|
-
db
|
|
23403
|
-
const backup = backupLegacyStore(file2);
|
|
24845
|
+
const backup = backupLegacyStore(db, file2);
|
|
23404
24846
|
db = openWithPragmas(file2);
|
|
23405
24847
|
akaWarn(
|
|
23406
24848
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23416,6 +24858,7 @@ function openAndInitialize(file2) {
|
|
|
23416
24858
|
policies,
|
|
23417
24859
|
installedPacks,
|
|
23418
24860
|
scanLedger: new SqliteScanLedgerRepository(db),
|
|
24861
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23419
24862
|
exceptions: new SqliteExceptionsRepository(db),
|
|
23420
24863
|
resolutions: new SqliteResolutionsRepository(db),
|
|
23421
24864
|
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
@@ -23443,7 +24886,7 @@ function openAndInitialize(file2) {
|
|
|
23443
24886
|
}
|
|
23444
24887
|
function openLocalDatabase(dir) {
|
|
23445
24888
|
ensureDataDirSync(dir);
|
|
23446
|
-
const file2 =
|
|
24889
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23447
24890
|
const {
|
|
23448
24891
|
db,
|
|
23449
24892
|
events,
|
|
@@ -23451,6 +24894,7 @@ function openLocalDatabase(dir) {
|
|
|
23451
24894
|
policies,
|
|
23452
24895
|
installedPacks,
|
|
23453
24896
|
scanLedger,
|
|
24897
|
+
secretVault,
|
|
23454
24898
|
exceptions,
|
|
23455
24899
|
resolutions,
|
|
23456
24900
|
ruleProbeCache,
|
|
@@ -23559,7 +25003,7 @@ function openLocalDatabase(dir) {
|
|
|
23559
25003
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23560
25004
|
if (!definitionId) continue;
|
|
23561
25005
|
inspectionFindings.insertFinding({
|
|
23562
|
-
id:
|
|
25006
|
+
id: randomUUID10(),
|
|
23563
25007
|
auditEventId: record2.scanEvent.id,
|
|
23564
25008
|
inspectionDefinitionId: definitionId,
|
|
23565
25009
|
span: finding.span,
|
|
@@ -23636,6 +25080,7 @@ function openLocalDatabase(dir) {
|
|
|
23636
25080
|
policies,
|
|
23637
25081
|
installedPacks,
|
|
23638
25082
|
scanLedger,
|
|
25083
|
+
secretVault,
|
|
23639
25084
|
exceptions,
|
|
23640
25085
|
resolutions,
|
|
23641
25086
|
ruleProbeCache,
|
|
@@ -23664,22 +25109,38 @@ function openLocalDatabase(dir) {
|
|
|
23664
25109
|
transaction,
|
|
23665
25110
|
close: () => {
|
|
23666
25111
|
db.close();
|
|
23667
|
-
}
|
|
25112
|
+
},
|
|
25113
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25114
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
23668
25115
|
};
|
|
23669
25116
|
}
|
|
23670
25117
|
|
|
25118
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25119
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25120
|
+
import {
|
|
25121
|
+
closeSync,
|
|
25122
|
+
existsSync as existsSync2,
|
|
25123
|
+
openSync,
|
|
25124
|
+
readFileSync,
|
|
25125
|
+
rmSync as rmSync3,
|
|
25126
|
+
statSync as statSync2,
|
|
25127
|
+
writeFileSync as writeFileSync2
|
|
25128
|
+
} from "fs";
|
|
25129
|
+
import { hostname as hostname3 } from "os";
|
|
25130
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25131
|
+
|
|
23671
25132
|
// ../../packages/persistence/src/finding-key.ts
|
|
23672
25133
|
import { createHash as createHash3 } from "crypto";
|
|
23673
25134
|
|
|
23674
25135
|
// ../../packages/persistence/src/fingerprint.ts
|
|
23675
25136
|
import { createHmac, randomBytes } from "crypto";
|
|
23676
|
-
import { existsSync as
|
|
23677
|
-
import { join as
|
|
25137
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25138
|
+
import { join as join3 } from "path";
|
|
23678
25139
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23679
|
-
var
|
|
25140
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
23680
25141
|
var KEY_MATERIAL_BYTES = 32;
|
|
23681
25142
|
function keyFilePath(dataDir2) {
|
|
23682
|
-
return
|
|
25143
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
23683
25144
|
}
|
|
23684
25145
|
function parseKeyFile(raw) {
|
|
23685
25146
|
const parsed = JSON.parse(raw);
|
|
@@ -23702,7 +25163,7 @@ function parseKeyFile(raw) {
|
|
|
23702
25163
|
function readFingerprintKey(dataDir2) {
|
|
23703
25164
|
let raw;
|
|
23704
25165
|
try {
|
|
23705
|
-
raw =
|
|
25166
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
23706
25167
|
} catch (err) {
|
|
23707
25168
|
if (err.code === "ENOENT") return null;
|
|
23708
25169
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -23714,18 +25175,18 @@ function readFingerprintKey(dataDir2) {
|
|
|
23714
25175
|
import { renameSync as renameSync3 } from "fs";
|
|
23715
25176
|
import { mkdir } from "fs/promises";
|
|
23716
25177
|
import { homedir } from "os";
|
|
23717
|
-
import { join as
|
|
25178
|
+
import { join as join4 } from "path";
|
|
23718
25179
|
function defaultDataDir() {
|
|
23719
|
-
return
|
|
25180
|
+
return join4(homedir(), ".aka");
|
|
23720
25181
|
}
|
|
23721
25182
|
function settingsDir(base = defaultDataDir()) {
|
|
23722
|
-
return
|
|
25183
|
+
return join4(base, "settings");
|
|
23723
25184
|
}
|
|
23724
25185
|
function dataDir(base = defaultDataDir()) {
|
|
23725
|
-
return
|
|
25186
|
+
return join4(base, "data");
|
|
23726
25187
|
}
|
|
23727
25188
|
function dbPath(base = defaultDataDir()) {
|
|
23728
|
-
return
|
|
25189
|
+
return join4(dataDir(base), "aka.db");
|
|
23729
25190
|
}
|
|
23730
25191
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
23731
25192
|
ensureDataDirSync(dir);
|
|
@@ -23738,8 +25199,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23738
25199
|
for (const { name, dest } of moves) {
|
|
23739
25200
|
try {
|
|
23740
25201
|
ensureDataDirSync(dest);
|
|
23741
|
-
const moved =
|
|
23742
|
-
renameSync3(
|
|
25202
|
+
const moved = join4(dest, name);
|
|
25203
|
+
renameSync3(join4(base, name), moved);
|
|
23743
25204
|
tightenFile(moved);
|
|
23744
25205
|
} catch {
|
|
23745
25206
|
}
|
|
@@ -23747,10 +25208,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23747
25208
|
}
|
|
23748
25209
|
|
|
23749
25210
|
// ../../packages/persistence/src/settings.ts
|
|
23750
|
-
import { readFileSync as
|
|
23751
|
-
import { join as
|
|
25211
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25212
|
+
import { join as join5 } from "path";
|
|
25213
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
23752
25214
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
23753
|
-
const record2 = readJson(
|
|
25215
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
23754
25216
|
if (!record2) return defaultWorkspaceSettings();
|
|
23755
25217
|
try {
|
|
23756
25218
|
return WorkspaceSettings.parse(record2);
|
|
@@ -23761,30 +25223,56 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
23761
25223
|
function readJson(file2) {
|
|
23762
25224
|
let text;
|
|
23763
25225
|
try {
|
|
23764
|
-
text =
|
|
25226
|
+
text = readFileSync3(file2, "utf8");
|
|
23765
25227
|
} catch {
|
|
23766
25228
|
return null;
|
|
23767
25229
|
}
|
|
23768
25230
|
return parseJsonObject(text) ?? null;
|
|
23769
25231
|
}
|
|
23770
25232
|
|
|
25233
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
25234
|
+
import {
|
|
25235
|
+
createCipheriv,
|
|
25236
|
+
createDecipheriv,
|
|
25237
|
+
createHmac as createHmac2,
|
|
25238
|
+
hkdfSync,
|
|
25239
|
+
timingSafeEqual
|
|
25240
|
+
} from "crypto";
|
|
25241
|
+
|
|
25242
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25243
|
+
import { execFileSync } from "child_process";
|
|
25244
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
25245
|
+
import {
|
|
25246
|
+
chmodSync as chmodSync2,
|
|
25247
|
+
mkdirSync as mkdirSync2,
|
|
25248
|
+
readFileSync as readFileSync4,
|
|
25249
|
+
renameSync as renameSync4,
|
|
25250
|
+
rmSync as rmSync4,
|
|
25251
|
+
statSync as statSync3,
|
|
25252
|
+
writeFileSync as writeFileSync3
|
|
25253
|
+
} from "fs";
|
|
25254
|
+
import { join as join6 } from "path";
|
|
25255
|
+
|
|
25256
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
25257
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
25258
|
+
|
|
23771
25259
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
23772
|
-
import { existsSync as
|
|
23773
|
-
import { join as
|
|
25260
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25261
|
+
import { join as join7 } from "path";
|
|
23774
25262
|
var MARKER = "warn-era-capped";
|
|
23775
25263
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
23776
25264
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
23777
|
-
const marker =
|
|
23778
|
-
if (
|
|
25265
|
+
const marker = join7(dataDir2, MARKER);
|
|
25266
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
23779
25267
|
const capped = db.policies.capCategoryActions();
|
|
23780
|
-
|
|
25268
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
23781
25269
|
`, { mode: DATA_FILE_MODE });
|
|
23782
25270
|
return { capped };
|
|
23783
25271
|
}
|
|
23784
25272
|
|
|
23785
25273
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23786
|
-
import { existsSync as
|
|
23787
|
-
import { join as
|
|
25274
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25275
|
+
import { join as join8 } from "path";
|
|
23788
25276
|
|
|
23789
25277
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
23790
25278
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -23835,11 +25323,11 @@ function resolveProvider() {
|
|
|
23835
25323
|
}
|
|
23836
25324
|
|
|
23837
25325
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23838
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25326
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
23839
25327
|
try {
|
|
23840
25328
|
ensureLayoutDirSync(base);
|
|
23841
|
-
const settingsFile =
|
|
23842
|
-
if (
|
|
25329
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25330
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
23843
25331
|
} catch {
|
|
23844
25332
|
}
|
|
23845
25333
|
migrateLegacyLayout(base);
|
|
@@ -23850,21 +25338,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
23850
25338
|
dbPath: dbPath(base),
|
|
23851
25339
|
settingsDir: settingsDir(base),
|
|
23852
25340
|
onboarded: settings.onboardedAt != null,
|
|
23853
|
-
provider: resolveProviderSafe()
|
|
25341
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
23854
25342
|
};
|
|
23855
25343
|
}
|
|
23856
|
-
function resolveProviderSafe() {
|
|
25344
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
23857
25345
|
try {
|
|
23858
|
-
return
|
|
25346
|
+
return resolveProviderFn();
|
|
23859
25347
|
} catch {
|
|
23860
25348
|
return { provider: "anthropic" };
|
|
23861
25349
|
}
|
|
23862
25350
|
}
|
|
23863
25351
|
|
|
23864
25352
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23865
|
-
import { readdirSync, readFileSync as
|
|
25353
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
23866
25354
|
import { homedir as homedir2 } from "os";
|
|
23867
|
-
import { basename as
|
|
25355
|
+
import { basename as basename3, join as join10 } from "path";
|
|
23868
25356
|
|
|
23869
25357
|
// ../../packages/detections/src/egress/registry.ts
|
|
23870
25358
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -26228,7 +27716,7 @@ var gcp_service_account_default = {
|
|
|
26228
27716
|
severity: "critical",
|
|
26229
27717
|
matcher: {
|
|
26230
27718
|
type: "regex",
|
|
26231
|
-
pattern: "
|
|
27719
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
26232
27720
|
flags: "g"
|
|
26233
27721
|
},
|
|
26234
27722
|
examples: [
|
|
@@ -26600,40 +28088,71 @@ function bundledDetections() {
|
|
|
26600
28088
|
}
|
|
26601
28089
|
|
|
26602
28090
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
26603
|
-
import { existsSync as
|
|
26604
|
-
import { basename, dirname, isAbsolute, join as
|
|
28091
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
28092
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
26605
28093
|
|
|
26606
28094
|
// ../../packages/plugin-sdk/src/events.ts
|
|
26607
|
-
import { createHash as createHash4, randomUUID as
|
|
28095
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28096
|
+
|
|
28097
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
28098
|
+
import { existsSync as existsSync7 } from "fs";
|
|
28099
|
+
import { fileURLToPath } from "url";
|
|
28100
|
+
import { Worker } from "worker_threads";
|
|
26608
28101
|
|
|
26609
28102
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
26610
|
-
import { arch, hostname as
|
|
28103
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
26611
28104
|
|
|
26612
28105
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
26613
|
-
import { mkdirSync as
|
|
26614
|
-
import { join as
|
|
28106
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
28107
|
+
import { join as join11 } from "path";
|
|
26615
28108
|
|
|
26616
28109
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
26617
|
-
import { readdirSync as
|
|
26618
|
-
import { basename as
|
|
28110
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
28111
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
26619
28112
|
|
|
26620
28113
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
26621
28114
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
26622
|
-
import { existsSync as
|
|
26623
|
-
import { basename as
|
|
28115
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
28116
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
28117
|
+
|
|
28118
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
28119
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
28120
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28121
|
+
return v;
|
|
28122
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28123
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
28124
|
+
if (typeof v !== "string") return false;
|
|
28125
|
+
const normalized = v.trim().toLowerCase();
|
|
28126
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
28127
|
+
}, external_exports.boolean()).catch(false);
|
|
28128
|
+
var antigravityProviderEnvShape = {
|
|
28129
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
28130
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
28131
|
+
};
|
|
28132
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
28133
|
+
|
|
28134
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
28135
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
28136
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28137
|
+
return v;
|
|
28138
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28139
|
+
var codexProviderEnvShape = {
|
|
28140
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
28141
|
+
};
|
|
28142
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
26624
28143
|
|
|
26625
28144
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
26626
|
-
import { randomUUID as
|
|
28145
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
26627
28146
|
|
|
26628
28147
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
26629
28148
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
26630
28149
|
|
|
26631
28150
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
26632
|
-
import { mkdirSync as
|
|
26633
|
-
import { join as
|
|
28151
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
28152
|
+
import { join as join13 } from "path";
|
|
26634
28153
|
|
|
26635
28154
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
26636
|
-
import { randomUUID as
|
|
28155
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
26637
28156
|
|
|
26638
28157
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
26639
28158
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -26795,7 +28314,7 @@ var StandaloneDataGateway = class {
|
|
|
26795
28314
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
26796
28315
|
const installed = this.installedScanRules();
|
|
26797
28316
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
26798
|
-
id:
|
|
28317
|
+
id: randomUUID15(),
|
|
26799
28318
|
scope: "global",
|
|
26800
28319
|
target: { ruleId },
|
|
26801
28320
|
action,
|
|
@@ -26948,16 +28467,16 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
26948
28467
|
}
|
|
26949
28468
|
|
|
26950
28469
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
26951
|
-
import { randomUUID as
|
|
28470
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
26952
28471
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
26953
28472
|
|
|
26954
28473
|
// src/command-registry.ts
|
|
26955
|
-
import { readdirSync as
|
|
26956
|
-
import { fileURLToPath } from "url";
|
|
28474
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
28475
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
26957
28476
|
var COMMAND_NAMESPACE = "aka";
|
|
26958
|
-
var COMMANDS_DIR =
|
|
28477
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
26959
28478
|
function readRegisteredCommands() {
|
|
26960
|
-
return
|
|
28479
|
+
return readdirSync5(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
26961
28480
|
}
|
|
26962
28481
|
function selectRegisteredCommands(curated, registry2) {
|
|
26963
28482
|
const registered = new Set(registry2);
|
|
@@ -27063,6 +28582,54 @@ function show(body) {
|
|
|
27063
28582
|
return showBlock(body);
|
|
27064
28583
|
}
|
|
27065
28584
|
|
|
28585
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
28586
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
28587
|
+
import { join as join14 } from "path";
|
|
28588
|
+
|
|
28589
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
28590
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
28591
|
+
import { tmpdir } from "os";
|
|
28592
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
28593
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
28594
|
+
ruleId: external_exports.string(),
|
|
28595
|
+
category: DetectionCategory,
|
|
28596
|
+
valueFingerprint: external_exports.string(),
|
|
28597
|
+
keyVersion: external_exports.number(),
|
|
28598
|
+
maskedValue: external_exports.string(),
|
|
28599
|
+
justification: external_exports.string()
|
|
28600
|
+
});
|
|
28601
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
28602
|
+
category: DetectionCategory,
|
|
28603
|
+
action: BuiltinPolicyId,
|
|
28604
|
+
genuineCount: external_exports.number(),
|
|
28605
|
+
fpCount: external_exports.number(),
|
|
28606
|
+
reasoning: external_exports.string()
|
|
28607
|
+
});
|
|
28608
|
+
var JoinEntrySchema = external_exports.object({
|
|
28609
|
+
id: external_exports.string(),
|
|
28610
|
+
ruleId: external_exports.string(),
|
|
28611
|
+
category: DetectionCategory,
|
|
28612
|
+
valueFingerprint: external_exports.string().optional(),
|
|
28613
|
+
keyVersion: external_exports.number().optional(),
|
|
28614
|
+
maskedMatch: external_exports.string(),
|
|
28615
|
+
maskedContext: external_exports.string()
|
|
28616
|
+
});
|
|
28617
|
+
var PLAN_FILE_VERSION = 3;
|
|
28618
|
+
var PersistedPlanSchema = external_exports.object({
|
|
28619
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
28620
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
28621
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
28622
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
28623
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
28624
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
28625
|
+
join: external_exports.array(JoinEntrySchema),
|
|
28626
|
+
notes: external_exports.string(),
|
|
28627
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
28628
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
28629
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
28630
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
28631
|
+
});
|
|
28632
|
+
|
|
27066
28633
|
// src/render.ts
|
|
27067
28634
|
var STORE_UNAVAILABLE_NOTE = "I couldn't check my records just now \u2014 we can check again soon. Your Claude session keeps going, and I'll fill in as you work.";
|
|
27068
28635
|
var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
@@ -27272,10 +28839,10 @@ async function runFirstRunFailOpen(deps) {
|
|
|
27272
28839
|
}
|
|
27273
28840
|
|
|
27274
28841
|
// src/posture.ts
|
|
27275
|
-
async function readPostureBlock(
|
|
28842
|
+
async function readPostureBlock(open2) {
|
|
27276
28843
|
let db;
|
|
27277
28844
|
try {
|
|
27278
|
-
db =
|
|
28845
|
+
db = open2();
|
|
27279
28846
|
const policies = await db.policies.readPolicies();
|
|
27280
28847
|
return renderPosture(
|
|
27281
28848
|
policies.map((p) => ({
|