@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/query.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
|
|
|
@@ -15355,7 +15378,17 @@ var Finding = external_exports.object({
|
|
|
15355
15378
|
}).meta({ id: "Finding" });
|
|
15356
15379
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15357
15380
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15358
|
-
var FindingProvider = external_exports.enum([
|
|
15381
|
+
var FindingProvider = external_exports.enum([
|
|
15382
|
+
"claudecode",
|
|
15383
|
+
"claudedesktop",
|
|
15384
|
+
"cursor",
|
|
15385
|
+
"copilot",
|
|
15386
|
+
"chatgpt",
|
|
15387
|
+
"claudeai",
|
|
15388
|
+
"codex",
|
|
15389
|
+
"antigravity",
|
|
15390
|
+
"api"
|
|
15391
|
+
]).meta({ id: "FindingProvider" });
|
|
15359
15392
|
var FindingCategory = external_exports.enum([
|
|
15360
15393
|
"secret",
|
|
15361
15394
|
"pii",
|
|
@@ -15409,7 +15442,16 @@ var FindingInstance = external_exports.object({
|
|
|
15409
15442
|
confidence: external_exports.number().min(0).max(1),
|
|
15410
15443
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15411
15444
|
// that predate the resolution feature stay valid.
|
|
15412
|
-
status: FindingStatus.optional()
|
|
15445
|
+
status: FindingStatus.optional(),
|
|
15446
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15447
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15448
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15449
|
+
// MOST RECENT detection event, not the first.
|
|
15450
|
+
eventId: external_exports.string().optional(),
|
|
15451
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15452
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15453
|
+
// outside a session.
|
|
15454
|
+
sessionId: external_exports.string().optional()
|
|
15413
15455
|
}).meta({ id: "FindingInstance" });
|
|
15414
15456
|
var FindingGroup = external_exports.object({
|
|
15415
15457
|
id: external_exports.string(),
|
|
@@ -15453,7 +15495,11 @@ var FindingFacets = external_exports.object({
|
|
|
15453
15495
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15454
15496
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15455
15497
|
// counted under no value.
|
|
15456
|
-
status: external_exports.array(FindingFacetItem)
|
|
15498
|
+
status: external_exports.array(FindingFacetItem),
|
|
15499
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15500
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15501
|
+
// because a group spans tools.
|
|
15502
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15457
15503
|
}).meta({ id: "FindingFacets" });
|
|
15458
15504
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15459
15505
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15471,6 +15517,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15471
15517
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15472
15518
|
// session → findings drilldown). Findings without a session never match.
|
|
15473
15519
|
sessionId: external_exports.string().optional(),
|
|
15520
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15521
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15522
|
+
// means all time — this list has no default window.
|
|
15523
|
+
from: external_exports.iso.datetime().optional(),
|
|
15524
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15525
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15526
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15527
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15528
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15529
|
+
includeId: external_exports.string().optional(),
|
|
15474
15530
|
groupBy: external_exports.literal("type").optional(),
|
|
15475
15531
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15476
15532
|
cursor: external_exports.string().optional()
|
|
@@ -15515,15 +15571,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15515
15571
|
detection: FindingDetectionRef,
|
|
15516
15572
|
policy: FindingPolicyRef
|
|
15517
15573
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15574
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15575
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15576
|
+
severity: external_exports.array(Severity).optional(),
|
|
15577
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15578
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15579
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15580
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15581
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15582
|
+
// the grouped query's group-level fold.
|
|
15583
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15584
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15585
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15586
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15587
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15588
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15589
|
+
repo: external_exports.string().optional(),
|
|
15590
|
+
file: external_exports.string().optional(),
|
|
15591
|
+
q: external_exports.string().optional(),
|
|
15592
|
+
sessionId: external_exports.string().optional(),
|
|
15593
|
+
from: external_exports.iso.datetime().optional(),
|
|
15594
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15595
|
+
cursor: external_exports.string().optional()
|
|
15596
|
+
});
|
|
15597
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15598
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15599
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15600
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15601
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15602
|
+
// dimension still excludes its own filter.
|
|
15603
|
+
facets: FindingFacets,
|
|
15604
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15605
|
+
nextCursor: external_exports.string().nullable()
|
|
15606
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15607
|
+
var FindingLocationFile = external_exports.object({
|
|
15608
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15609
|
+
// with no file attribution).
|
|
15610
|
+
file: external_exports.string(),
|
|
15611
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15612
|
+
maxSeverity: Severity,
|
|
15613
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15614
|
+
// Folded from the instances' derived statuses with the same
|
|
15615
|
+
// open-dominates precedence a group uses.
|
|
15616
|
+
status: FindingStatus.optional(),
|
|
15617
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15618
|
+
// chips, and the count is what conveys scale.
|
|
15619
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15620
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15621
|
+
var FindingLocationRepo = external_exports.object({
|
|
15622
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15623
|
+
repo: external_exports.string(),
|
|
15624
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15625
|
+
maxSeverity: Severity,
|
|
15626
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15627
|
+
status: FindingStatus.optional(),
|
|
15628
|
+
files: external_exports.array(FindingLocationFile)
|
|
15629
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15630
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15631
|
+
severity: external_exports.array(Severity).optional(),
|
|
15632
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15633
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15634
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15635
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15636
|
+
// instances that match, and folds its status from those.
|
|
15637
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15638
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15639
|
+
q: external_exports.string().optional(),
|
|
15640
|
+
sessionId: external_exports.string().optional(),
|
|
15641
|
+
from: external_exports.iso.datetime().optional(),
|
|
15642
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15643
|
+
});
|
|
15644
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15645
|
+
totals: external_exports.object({
|
|
15646
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15647
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15648
|
+
files: external_exports.number().int().nonnegative()
|
|
15649
|
+
}),
|
|
15650
|
+
/** Sorted by max severity, then most recent. */
|
|
15651
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15652
|
+
/** Whether `limit` truncated the repo list. */
|
|
15653
|
+
hasMore: external_exports.boolean()
|
|
15654
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15518
15655
|
|
|
15519
15656
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15520
|
-
var Harness = external_exports.enum([
|
|
15657
|
+
var Harness = external_exports.enum([
|
|
15658
|
+
"claudecode",
|
|
15659
|
+
"cursor",
|
|
15660
|
+
"copilot",
|
|
15661
|
+
"codex",
|
|
15662
|
+
"antigravity",
|
|
15663
|
+
"windsurf",
|
|
15664
|
+
"claudedesktop",
|
|
15665
|
+
"chatgpt",
|
|
15666
|
+
"claudeai",
|
|
15667
|
+
"api"
|
|
15668
|
+
]).meta({ id: "Harness" });
|
|
15521
15669
|
var TOOL_TO_HARNESS = {
|
|
15522
15670
|
"claude-code": "claudecode",
|
|
15523
15671
|
"claude-desktop": "claudedesktop",
|
|
15524
15672
|
"github-copilot": "copilot",
|
|
15525
15673
|
cursor: "cursor",
|
|
15526
|
-
chatgpt: "chatgpt"
|
|
15674
|
+
chatgpt: "chatgpt",
|
|
15675
|
+
codex: "codex",
|
|
15676
|
+
antigravity: "antigravity",
|
|
15677
|
+
"claude-ai": "claudeai"
|
|
15527
15678
|
};
|
|
15528
15679
|
|
|
15529
15680
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15981,7 +16132,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15981
16132
|
// ../../packages/schema/src/zod/event.ts
|
|
15982
16133
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15983
16134
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15984
|
-
var SourceTool = external_exports.enum([
|
|
16135
|
+
var SourceTool = external_exports.enum([
|
|
16136
|
+
"claude-code",
|
|
16137
|
+
"claude-desktop",
|
|
16138
|
+
"cursor",
|
|
16139
|
+
"chatgpt",
|
|
16140
|
+
"claude-ai",
|
|
16141
|
+
"github-copilot",
|
|
16142
|
+
"codex",
|
|
16143
|
+
"antigravity",
|
|
16144
|
+
"cli",
|
|
16145
|
+
"unknown"
|
|
16146
|
+
]).meta({ id: "SourceTool" });
|
|
15985
16147
|
var EventMetadata = external_exports.object({
|
|
15986
16148
|
sessionId: external_exports.string().optional(),
|
|
15987
16149
|
repo: external_exports.string().optional(),
|
|
@@ -16052,7 +16214,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
16052
16214
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16053
16215
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16054
16216
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16055
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16217
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16056
16218
|
var AccessCounts = external_exports.object({
|
|
16057
16219
|
open: external_exports.number().int().nonnegative(),
|
|
16058
16220
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16274,6 +16436,7 @@ var ExceptionConditions = external_exports.object({
|
|
|
16274
16436
|
sourceTool: external_exports.string().optional(),
|
|
16275
16437
|
provider: external_exports.string().optional()
|
|
16276
16438
|
}).strict();
|
|
16439
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16277
16440
|
var DetectionException = external_exports.object({
|
|
16278
16441
|
id: external_exports.guid(),
|
|
16279
16442
|
ruleId: external_exports.string(),
|
|
@@ -16290,6 +16453,7 @@ var DetectionException = external_exports.object({
|
|
|
16290
16453
|
keyVersion: external_exports.number().int().positive(),
|
|
16291
16454
|
// maskMatch() preview of the approved value — never the raw value.
|
|
16292
16455
|
maskedValue: external_exports.string(),
|
|
16456
|
+
capability: ExceptionCapability.default("suppress"),
|
|
16293
16457
|
scope: ExceptionScope,
|
|
16294
16458
|
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16295
16459
|
maxUses: external_exports.number().int().positive().nullable(),
|
|
@@ -16313,11 +16477,13 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16313
16477
|
ruleId: true,
|
|
16314
16478
|
valueFingerprint: true,
|
|
16315
16479
|
keyVersion: true,
|
|
16480
|
+
capability: true,
|
|
16316
16481
|
expiresAt: true,
|
|
16317
16482
|
maxUses: true,
|
|
16318
16483
|
useCount: true,
|
|
16319
16484
|
conditions: true
|
|
16320
16485
|
});
|
|
16486
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16321
16487
|
|
|
16322
16488
|
// ../../packages/schema/src/zod/rule.ts
|
|
16323
16489
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17128,6 +17294,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17128
17294
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17129
17295
|
}).meta({ id: "EgressWriteSummary" });
|
|
17130
17296
|
|
|
17297
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17298
|
+
var confirmation = external_exports.string().optional();
|
|
17299
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17300
|
+
reference: external_exports.string(),
|
|
17301
|
+
scope: external_exports.string(),
|
|
17302
|
+
reason: external_exports.string(),
|
|
17303
|
+
confirmation
|
|
17304
|
+
});
|
|
17305
|
+
var AddExceptionInput = external_exports.object({
|
|
17306
|
+
ruleId: external_exports.string(),
|
|
17307
|
+
value: external_exports.string(),
|
|
17308
|
+
scope: external_exports.string(),
|
|
17309
|
+
reason: external_exports.string(),
|
|
17310
|
+
confirmation
|
|
17311
|
+
});
|
|
17312
|
+
var GrantRevealInput = external_exports.object({
|
|
17313
|
+
pointer: external_exports.string(),
|
|
17314
|
+
scope: external_exports.string(),
|
|
17315
|
+
justification: external_exports.string(),
|
|
17316
|
+
confirmation
|
|
17317
|
+
});
|
|
17318
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17319
|
+
id: external_exports.string(),
|
|
17320
|
+
reason: external_exports.string()
|
|
17321
|
+
});
|
|
17322
|
+
var RotateKeyInput = external_exports.object({
|
|
17323
|
+
confirmation: external_exports.string()
|
|
17324
|
+
});
|
|
17325
|
+
|
|
17131
17326
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17132
17327
|
function toApiAction(dbVal) {
|
|
17133
17328
|
const map2 = {
|
|
@@ -17183,6 +17378,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17183
17378
|
repo: r.repo,
|
|
17184
17379
|
file: r.file,
|
|
17185
17380
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17381
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17382
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17186
17383
|
action: toApiAction(effectiveDbAction),
|
|
17187
17384
|
detectedAt: r.occurredAt,
|
|
17188
17385
|
confidence: r.confidence,
|
|
@@ -17314,14 +17511,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17314
17511
|
}
|
|
17315
17512
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17316
17513
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17514
|
+
function compareFindingGroupOrder(a, b) {
|
|
17515
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17516
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17517
|
+
const severityDiff = rankA - rankB;
|
|
17518
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17519
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17520
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17521
|
+
return a.id.localeCompare(b.id);
|
|
17522
|
+
}
|
|
17317
17523
|
function sortFindingGroups(groups) {
|
|
17318
|
-
return [...groups].sort(
|
|
17319
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17320
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17321
|
-
const severityDiff = rankA - rankB;
|
|
17322
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17323
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17324
|
-
});
|
|
17524
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17325
17525
|
}
|
|
17326
17526
|
function computeFindingFacets(allGroups, opts) {
|
|
17327
17527
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17377,15 +17577,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17377
17577
|
for (const g of forStatus) {
|
|
17378
17578
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17379
17579
|
}
|
|
17380
|
-
const
|
|
17580
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17581
|
+
return {
|
|
17582
|
+
severity: toItems2(severityMap),
|
|
17583
|
+
provider: toItems2(providerMap),
|
|
17584
|
+
action: toItems2(actionMap),
|
|
17585
|
+
subtype: toItems2(subtypeMap),
|
|
17586
|
+
status: toItems2(statusMap)
|
|
17587
|
+
};
|
|
17588
|
+
}
|
|
17589
|
+
|
|
17590
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17591
|
+
function rowHaystack(row) {
|
|
17592
|
+
return [
|
|
17593
|
+
row.ruleId,
|
|
17594
|
+
row.category,
|
|
17595
|
+
row.maskedMatch,
|
|
17596
|
+
row.repo,
|
|
17597
|
+
row.file,
|
|
17598
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17599
|
+
row.id
|
|
17600
|
+
].join(" ").toLowerCase();
|
|
17601
|
+
}
|
|
17602
|
+
function matchesDimension(row, opts, dimension) {
|
|
17603
|
+
switch (dimension) {
|
|
17604
|
+
case "severity":
|
|
17605
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17606
|
+
case "subtype":
|
|
17607
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17608
|
+
case "providers":
|
|
17609
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17610
|
+
case "actions":
|
|
17611
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17612
|
+
case "statuses":
|
|
17613
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17614
|
+
case "tools":
|
|
17615
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17616
|
+
case "repo":
|
|
17617
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17618
|
+
case "file":
|
|
17619
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17620
|
+
case "q":
|
|
17621
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17622
|
+
}
|
|
17623
|
+
}
|
|
17624
|
+
var DIMENSIONS = [
|
|
17625
|
+
"severity",
|
|
17626
|
+
"subtype",
|
|
17627
|
+
"providers",
|
|
17628
|
+
"actions",
|
|
17629
|
+
"statuses",
|
|
17630
|
+
"tools",
|
|
17631
|
+
"repo",
|
|
17632
|
+
"file",
|
|
17633
|
+
"q"
|
|
17634
|
+
];
|
|
17635
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17636
|
+
for (const dimension of DIMENSIONS) {
|
|
17637
|
+
if (dimension === except) continue;
|
|
17638
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17639
|
+
}
|
|
17640
|
+
return true;
|
|
17641
|
+
}
|
|
17642
|
+
function toItems(counts) {
|
|
17643
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17644
|
+
}
|
|
17645
|
+
function bump(counts, value) {
|
|
17646
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17647
|
+
}
|
|
17648
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17649
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17650
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17651
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17652
|
+
const action = /* @__PURE__ */ new Map();
|
|
17653
|
+
const status = /* @__PURE__ */ new Map();
|
|
17654
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17655
|
+
return {
|
|
17656
|
+
add(row) {
|
|
17657
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17658
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17659
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17660
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17661
|
+
}
|
|
17662
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17663
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17664
|
+
bump(status, row.status);
|
|
17665
|
+
}
|
|
17666
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17667
|
+
bump(tool, row.toolName);
|
|
17668
|
+
}
|
|
17669
|
+
},
|
|
17670
|
+
facets: () => ({
|
|
17671
|
+
severity: toItems(severity),
|
|
17672
|
+
subtype: toItems(subtype),
|
|
17673
|
+
provider: toItems(provider),
|
|
17674
|
+
action: toItems(action),
|
|
17675
|
+
status: toItems(status),
|
|
17676
|
+
tool: toItems(tool)
|
|
17677
|
+
})
|
|
17678
|
+
};
|
|
17679
|
+
}
|
|
17680
|
+
function toInstanceDetail(row) {
|
|
17681
|
+
const category = toApiCategory(row.category);
|
|
17682
|
+
return {
|
|
17683
|
+
id: row.id,
|
|
17684
|
+
provider: toApiProvider(row.sourceTool),
|
|
17685
|
+
repo: row.repo,
|
|
17686
|
+
file: row.file,
|
|
17687
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17688
|
+
eventId: row.eventId,
|
|
17689
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17690
|
+
action: toApiAction(row.actionTaken),
|
|
17691
|
+
detectedAt: row.occurredAt,
|
|
17692
|
+
confidence: row.confidence,
|
|
17693
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17694
|
+
groupId: row.ruleId,
|
|
17695
|
+
category,
|
|
17696
|
+
subtype: row.ruleId,
|
|
17697
|
+
severity: row.severity,
|
|
17698
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17699
|
+
detection: { id: row.ruleId, name: null },
|
|
17700
|
+
policy: { id: `category:${category}`, name: category }
|
|
17701
|
+
};
|
|
17702
|
+
}
|
|
17703
|
+
var SEVERITY_ORDER2 = {
|
|
17704
|
+
critical: 0,
|
|
17705
|
+
high: 1,
|
|
17706
|
+
medium: 2,
|
|
17707
|
+
low: 3
|
|
17708
|
+
};
|
|
17709
|
+
function newLocationAccumulator() {
|
|
17381
17710
|
return {
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17711
|
+
instanceCount: 0,
|
|
17712
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17713
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17714
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17715
|
+
maxSeverity: "low",
|
|
17716
|
+
latestDetectedAt: "",
|
|
17717
|
+
statuses: [],
|
|
17718
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17387
17719
|
};
|
|
17388
17720
|
}
|
|
17721
|
+
function addToLocation(acc, row) {
|
|
17722
|
+
acc.instanceCount += 1;
|
|
17723
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17724
|
+
if (rank < acc.maxSeverityRank) {
|
|
17725
|
+
acc.maxSeverityRank = rank;
|
|
17726
|
+
acc.maxSeverity = row.severity;
|
|
17727
|
+
}
|
|
17728
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17729
|
+
acc.statuses.push(row.status);
|
|
17730
|
+
acc.ruleIds.add(row.ruleId);
|
|
17731
|
+
}
|
|
17389
17732
|
|
|
17390
17733
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17391
17734
|
var InstalledPack = external_exports.object({
|
|
@@ -17417,8 +17760,161 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17417
17760
|
message: "At least one field must be provided"
|
|
17418
17761
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17419
17762
|
|
|
17763
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17764
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17765
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17766
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17767
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17768
|
+
);
|
|
17769
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17770
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17771
|
+
var ParsedPointer = external_exports.object({
|
|
17772
|
+
category: DetectionCategory,
|
|
17773
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17774
|
+
pointerId: external_exports.string(),
|
|
17775
|
+
tag: external_exports.string()
|
|
17776
|
+
});
|
|
17777
|
+
var VaultEntry = external_exports.object({
|
|
17778
|
+
pointerId: external_exports.string(),
|
|
17779
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17780
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17781
|
+
// independently of the vault encryption key below.
|
|
17782
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17783
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17784
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17785
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17786
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17787
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17788
|
+
// value always produces exactly one wire token.
|
|
17789
|
+
category: DetectionCategory,
|
|
17790
|
+
ruleId: external_exports.string(),
|
|
17791
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17792
|
+
maskedMatch: external_exports.string(),
|
|
17793
|
+
provider: external_exports.string().optional(),
|
|
17794
|
+
ciphertext: external_exports.string(),
|
|
17795
|
+
nonce: external_exports.string(),
|
|
17796
|
+
authTag: external_exports.string(),
|
|
17797
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17798
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17799
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17800
|
+
firstSeen: external_exports.string(),
|
|
17801
|
+
lastSeen: external_exports.string()
|
|
17802
|
+
});
|
|
17803
|
+
var PointerDescriptor = external_exports.object({
|
|
17804
|
+
category: DetectionCategory,
|
|
17805
|
+
provider: external_exports.string().optional(),
|
|
17806
|
+
maskedMatch: external_exports.string(),
|
|
17807
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17808
|
+
firstSeen: external_exports.string(),
|
|
17809
|
+
lastSeen: external_exports.string()
|
|
17810
|
+
});
|
|
17811
|
+
var PointerIdentity = external_exports.object({
|
|
17812
|
+
ruleId: external_exports.string(),
|
|
17813
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17814
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17815
|
+
});
|
|
17816
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17817
|
+
var VaultDerefReason = external_exports.enum([
|
|
17818
|
+
"display",
|
|
17819
|
+
"explicit-reveal",
|
|
17820
|
+
"view-render",
|
|
17821
|
+
"model-input",
|
|
17822
|
+
"remediation",
|
|
17823
|
+
"purge"
|
|
17824
|
+
]);
|
|
17825
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17826
|
+
var VaultDeref = external_exports.object({
|
|
17827
|
+
id: external_exports.guid(),
|
|
17828
|
+
pointerId: external_exports.string(),
|
|
17829
|
+
at: external_exports.string(),
|
|
17830
|
+
target: DetokenizeTarget,
|
|
17831
|
+
reason: VaultDerefReason,
|
|
17832
|
+
outcome: VaultDerefOutcome,
|
|
17833
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17834
|
+
grantId: external_exports.string().optional(),
|
|
17835
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17836
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17837
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17838
|
+
});
|
|
17839
|
+
var VaultSightingKind = external_exports.enum([
|
|
17840
|
+
"prompt",
|
|
17841
|
+
"tool-input",
|
|
17842
|
+
"tool-output",
|
|
17843
|
+
"file",
|
|
17844
|
+
"transcript"
|
|
17845
|
+
]);
|
|
17846
|
+
var VaultSighting = external_exports.object({
|
|
17847
|
+
location: external_exports.string(),
|
|
17848
|
+
kind: VaultSightingKind,
|
|
17849
|
+
firstSeen: external_exports.string(),
|
|
17850
|
+
lastSeen: external_exports.string()
|
|
17851
|
+
});
|
|
17852
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17853
|
+
pointerId: external_exports.string(),
|
|
17854
|
+
category: DetectionCategory,
|
|
17855
|
+
provider: external_exports.string().optional(),
|
|
17856
|
+
maskedMatch: external_exports.string(),
|
|
17857
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17858
|
+
firstSeen: external_exports.string(),
|
|
17859
|
+
lastSeen: external_exports.string(),
|
|
17860
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17861
|
+
// the inventory badges it, the row links to revocation.
|
|
17862
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17863
|
+
sightings: external_exports.array(VaultSighting)
|
|
17864
|
+
});
|
|
17865
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17866
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17867
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17868
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17869
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17870
|
+
// Opaque; names the last row of the page just served.
|
|
17871
|
+
cursor: external_exports.string().optional()
|
|
17872
|
+
});
|
|
17873
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17874
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17875
|
+
// independent, so paging never changes what the count claims.
|
|
17876
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17877
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17878
|
+
// `null` once the last page is reached.
|
|
17879
|
+
nextCursor: external_exports.string().nullable()
|
|
17880
|
+
});
|
|
17881
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17882
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17883
|
+
cursor: external_exports.string().optional()
|
|
17884
|
+
});
|
|
17885
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17886
|
+
// Reused values across the whole store — the number the section's claim
|
|
17887
|
+
// ("values detected in more than one place") is about.
|
|
17888
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17889
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17890
|
+
nextCursor: external_exports.string().nullable()
|
|
17891
|
+
});
|
|
17892
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17893
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17894
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17895
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17896
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17897
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17898
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17899
|
+
cursor: external_exports.string().optional()
|
|
17900
|
+
});
|
|
17901
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17902
|
+
items: external_exports.array(VaultDeref),
|
|
17903
|
+
nextCursor: external_exports.string().nullable(),
|
|
17904
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17905
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17906
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17907
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17908
|
+
});
|
|
17909
|
+
var VaultKeyCustody = external_exports.string();
|
|
17910
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17911
|
+
var VaultConsent = external_exports.object({
|
|
17912
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17913
|
+
version: external_exports.number().int().positive()
|
|
17914
|
+
});
|
|
17915
|
+
|
|
17420
17916
|
// ../../packages/schema/src/zod/local.ts
|
|
17421
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17917
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17422
17918
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17423
17919
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17424
17920
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
@@ -17440,6 +17936,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17440
17936
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17441
17937
|
// Shares writes.
|
|
17442
17938
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17939
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17940
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17941
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17942
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17943
|
+
// purging the vault is the eraser.
|
|
17944
|
+
vaultConsent: VaultConsent.optional(),
|
|
17945
|
+
// Where the vault master key lives.
|
|
17946
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17947
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17948
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17443
17949
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17444
17950
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17445
17951
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -17772,7 +18278,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17772
18278
|
// Omit for both kinds.
|
|
17773
18279
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17774
18280
|
});
|
|
17775
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18281
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17776
18282
|
var ScanCoverageProvider = external_exports.object({
|
|
17777
18283
|
provider: Provider,
|
|
17778
18284
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -18025,6 +18531,138 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
18025
18531
|
);
|
|
18026
18532
|
}
|
|
18027
18533
|
|
|
18534
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18535
|
+
import { randomUUID } from "crypto";
|
|
18536
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18537
|
+
import { basename, dirname, join } from "path";
|
|
18538
|
+
|
|
18539
|
+
// ../../packages/persistence/src/paths.ts
|
|
18540
|
+
import {
|
|
18541
|
+
chmodSync,
|
|
18542
|
+
linkSync,
|
|
18543
|
+
lstatSync,
|
|
18544
|
+
mkdirSync,
|
|
18545
|
+
renameSync,
|
|
18546
|
+
rmSync,
|
|
18547
|
+
writeFileSync
|
|
18548
|
+
} from "fs";
|
|
18549
|
+
import { threadId } from "worker_threads";
|
|
18550
|
+
var DATA_DIR_MODE = 448;
|
|
18551
|
+
var DATA_FILE_MODE = 384;
|
|
18552
|
+
var DB_FILENAME = "aka.db";
|
|
18553
|
+
function isSymlink(path) {
|
|
18554
|
+
try {
|
|
18555
|
+
return lstatSync(path).isSymbolicLink();
|
|
18556
|
+
} catch {
|
|
18557
|
+
return false;
|
|
18558
|
+
}
|
|
18559
|
+
}
|
|
18560
|
+
function chmodBestEffort(path, mode) {
|
|
18561
|
+
if (isSymlink(path)) return;
|
|
18562
|
+
try {
|
|
18563
|
+
chmodSync(path, mode);
|
|
18564
|
+
} catch {
|
|
18565
|
+
}
|
|
18566
|
+
}
|
|
18567
|
+
function tightenDir(dir) {
|
|
18568
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18569
|
+
}
|
|
18570
|
+
function ensureDataDirSync(dir) {
|
|
18571
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18572
|
+
tightenDir(dir);
|
|
18573
|
+
}
|
|
18574
|
+
function dbSidecars(file2) {
|
|
18575
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18576
|
+
}
|
|
18577
|
+
function tightenFile(file2) {
|
|
18578
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18579
|
+
}
|
|
18580
|
+
function tightenPerms(file2) {
|
|
18581
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18582
|
+
}
|
|
18583
|
+
|
|
18584
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18585
|
+
function backupPath(file2, tag) {
|
|
18586
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18587
|
+
}
|
|
18588
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18589
|
+
function reapStalePartials(file2) {
|
|
18590
|
+
const dir = dirname(file2);
|
|
18591
|
+
const prefix = `${basename(file2)}.`;
|
|
18592
|
+
let entries;
|
|
18593
|
+
try {
|
|
18594
|
+
entries = readdirSync(dir);
|
|
18595
|
+
} catch {
|
|
18596
|
+
return;
|
|
18597
|
+
}
|
|
18598
|
+
for (const name of entries) {
|
|
18599
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18600
|
+
const partial2 = join(dir, name);
|
|
18601
|
+
try {
|
|
18602
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18603
|
+
rmSync2(partial2, { force: true });
|
|
18604
|
+
}
|
|
18605
|
+
} catch {
|
|
18606
|
+
}
|
|
18607
|
+
}
|
|
18608
|
+
}
|
|
18609
|
+
function snapshotStore(db, backup) {
|
|
18610
|
+
const partial2 = `${backup}.partial`;
|
|
18611
|
+
try {
|
|
18612
|
+
rmSync2(partial2, { force: true });
|
|
18613
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18614
|
+
tightenFile(partial2);
|
|
18615
|
+
renameSync2(partial2, backup);
|
|
18616
|
+
} catch (error51) {
|
|
18617
|
+
try {
|
|
18618
|
+
rmSync2(partial2, { force: true });
|
|
18619
|
+
} catch {
|
|
18620
|
+
}
|
|
18621
|
+
throw error51;
|
|
18622
|
+
}
|
|
18623
|
+
}
|
|
18624
|
+
function moveStoreAside(file2, backup) {
|
|
18625
|
+
const undo = [];
|
|
18626
|
+
renameSync2(file2, backup);
|
|
18627
|
+
undo.push([backup, file2]);
|
|
18628
|
+
try {
|
|
18629
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18630
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18631
|
+
try {
|
|
18632
|
+
renameSync2(sidecar, moved);
|
|
18633
|
+
undo.push([moved, sidecar]);
|
|
18634
|
+
} catch {
|
|
18635
|
+
rmSync2(sidecar, { force: true });
|
|
18636
|
+
}
|
|
18637
|
+
}
|
|
18638
|
+
} catch (error51) {
|
|
18639
|
+
for (const [from, to] of undo.reverse()) {
|
|
18640
|
+
try {
|
|
18641
|
+
renameSync2(from, to);
|
|
18642
|
+
} catch {
|
|
18643
|
+
}
|
|
18644
|
+
}
|
|
18645
|
+
throw error51;
|
|
18646
|
+
}
|
|
18647
|
+
tightenPerms(backup);
|
|
18648
|
+
}
|
|
18649
|
+
function discardStore(file2, backup) {
|
|
18650
|
+
try {
|
|
18651
|
+
rmSync2(file2, { force: true });
|
|
18652
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18653
|
+
rmSync2(sidecar, { force: true });
|
|
18654
|
+
}
|
|
18655
|
+
} catch (error51) {
|
|
18656
|
+
if (existsSync(file2)) {
|
|
18657
|
+
try {
|
|
18658
|
+
rmSync2(backup, { force: true });
|
|
18659
|
+
} catch {
|
|
18660
|
+
}
|
|
18661
|
+
}
|
|
18662
|
+
throw error51;
|
|
18663
|
+
}
|
|
18664
|
+
}
|
|
18665
|
+
|
|
18028
18666
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
18029
18667
|
function escapeLikePattern(s) {
|
|
18030
18668
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18166,38 +18804,6 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18166
18804
|
return out;
|
|
18167
18805
|
}
|
|
18168
18806
|
|
|
18169
|
-
// ../../packages/persistence/src/paths.ts
|
|
18170
|
-
import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
18171
|
-
var DATA_DIR_MODE = 448;
|
|
18172
|
-
var DATA_FILE_MODE = 384;
|
|
18173
|
-
var DB_FILENAME = "aka.db";
|
|
18174
|
-
function chmodBestEffort(path, mode) {
|
|
18175
|
-
try {
|
|
18176
|
-
chmodSync(path, mode);
|
|
18177
|
-
} catch {
|
|
18178
|
-
}
|
|
18179
|
-
}
|
|
18180
|
-
function tightenDir(dir) {
|
|
18181
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18182
|
-
}
|
|
18183
|
-
function ensureDataDirSync(dir) {
|
|
18184
|
-
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18185
|
-
tightenDir(dir);
|
|
18186
|
-
}
|
|
18187
|
-
function dbSidecars(file2) {
|
|
18188
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18189
|
-
}
|
|
18190
|
-
function tightenFile(file2) {
|
|
18191
|
-
try {
|
|
18192
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18193
|
-
} catch {
|
|
18194
|
-
}
|
|
18195
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18196
|
-
}
|
|
18197
|
-
function tightenPerms(file2) {
|
|
18198
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18199
|
-
}
|
|
18200
|
-
|
|
18201
18807
|
// ../../packages/persistence/src/migrations.ts
|
|
18202
18808
|
function describeObject(object2) {
|
|
18203
18809
|
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
@@ -18313,9 +18919,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18313
18919
|
}
|
|
18314
18920
|
}
|
|
18315
18921
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18316
|
-
|
|
18317
|
-
|
|
18318
|
-
|
|
18922
|
+
reapStalePartials(file2);
|
|
18923
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18924
|
+
snapshotStore(db, backup);
|
|
18319
18925
|
return backup;
|
|
18320
18926
|
}
|
|
18321
18927
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18659,6 +19265,25 @@ function parseJsonObject(s) {
|
|
|
18659
19265
|
return void 0;
|
|
18660
19266
|
}
|
|
18661
19267
|
|
|
19268
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19269
|
+
function encodeKeysetCursor(payload) {
|
|
19270
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19271
|
+
}
|
|
19272
|
+
function decodeKeysetCursor(cursor) {
|
|
19273
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19274
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19275
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19276
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19277
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19278
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19279
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19280
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19281
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19282
|
+
return parsed;
|
|
19283
|
+
}
|
|
19284
|
+
return null;
|
|
19285
|
+
}
|
|
19286
|
+
|
|
18662
19287
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18663
19288
|
var DAY_MS = 864e5;
|
|
18664
19289
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18704,16 +19329,6 @@ function utcWindow(nowMs) {
|
|
|
18704
19329
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18705
19330
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18706
19331
|
}
|
|
18707
|
-
function encodeCursor(payload) {
|
|
18708
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18709
|
-
}
|
|
18710
|
-
function decodeCursor(cursor) {
|
|
18711
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18712
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18713
|
-
return parsed;
|
|
18714
|
-
}
|
|
18715
|
-
return null;
|
|
18716
|
-
}
|
|
18717
19332
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18718
19333
|
session: "session",
|
|
18719
19334
|
prompt: "prompt",
|
|
@@ -18858,7 +19473,7 @@ var SqliteActivityRepository = class {
|
|
|
18858
19473
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18859
19474
|
}
|
|
18860
19475
|
listSessions(query) {
|
|
18861
|
-
const cursor = query.cursor ?
|
|
19476
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18862
19477
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18863
19478
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18864
19479
|
const conditions = [SESSION_ROOT];
|
|
@@ -18932,7 +19547,7 @@ var SqliteActivityRepository = class {
|
|
|
18932
19547
|
)
|
|
18933
19548
|
);
|
|
18934
19549
|
const last = page[page.length - 1];
|
|
18935
|
-
const nextCursor = hasMore && last ?
|
|
19550
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
18936
19551
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
18937
19552
|
}
|
|
18938
19553
|
getSession(sessionId) {
|
|
@@ -19805,7 +20420,7 @@ var SqliteEventsRepository = class {
|
|
|
19805
20420
|
};
|
|
19806
20421
|
|
|
19807
20422
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19808
|
-
import { randomUUID } from "crypto";
|
|
20423
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19809
20424
|
|
|
19810
20425
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19811
20426
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19837,9 +20452,13 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19837
20452
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19838
20453
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19839
20454
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
20455
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
20456
|
+
AND conditions IS NULL
|
|
20457
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19840
20458
|
var SqliteExceptionsRepository = class {
|
|
19841
|
-
constructor(db) {
|
|
20459
|
+
constructor(db, now = () => Date.now()) {
|
|
19842
20460
|
this.db = db;
|
|
20461
|
+
this.now = now;
|
|
19843
20462
|
this.consumeStmt = db.prepare(
|
|
19844
20463
|
`UPDATE exceptions
|
|
19845
20464
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19857,6 +20476,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19857
20476
|
);
|
|
19858
20477
|
}
|
|
19859
20478
|
db;
|
|
20479
|
+
now;
|
|
19860
20480
|
consumeStmt;
|
|
19861
20481
|
insertBlockedStmt;
|
|
19862
20482
|
sweepBlockedStmt;
|
|
@@ -19883,8 +20503,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19883
20503
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19884
20504
|
);
|
|
19885
20505
|
}
|
|
19886
|
-
const id =
|
|
19887
|
-
const now =
|
|
20506
|
+
const id = randomUUID2();
|
|
20507
|
+
const now = this.now();
|
|
19888
20508
|
try {
|
|
19889
20509
|
this.insertExceptionRow(id, input, now);
|
|
19890
20510
|
} catch (err) {
|
|
@@ -19928,11 +20548,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19928
20548
|
this.db.prepare(
|
|
19929
20549
|
`INSERT INTO exceptions (
|
|
19930
20550
|
id, rule_id, category, value_fingerprint, key_version, masked_value,
|
|
19931
|
-
scope, expires_at, max_uses, use_count, last_used_at,
|
|
19932
|
-
conditions, created_by, created_via, created_at, updated_at
|
|
20551
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20552
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19933
20553
|
) VALUES (
|
|
19934
20554
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19935
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20555
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19936
20556
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19937
20557
|
)`
|
|
19938
20558
|
).run({
|
|
@@ -19942,6 +20562,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19942
20562
|
valueFingerprint: input.valueFingerprint,
|
|
19943
20563
|
keyVersion: input.keyVersion,
|
|
19944
20564
|
maskedValue: input.maskedValue,
|
|
20565
|
+
capability: input.capability ?? "suppress",
|
|
19945
20566
|
scope: input.scope,
|
|
19946
20567
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19947
20568
|
maxUses: input.maxUses,
|
|
@@ -19961,7 +20582,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19961
20582
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
19962
20583
|
const rows = allRows(
|
|
19963
20584
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
19964
|
-
opts?.includeTerminal ? {} : { now:
|
|
20585
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
19965
20586
|
);
|
|
19966
20587
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
19967
20588
|
return Promise.resolve(exceptions);
|
|
@@ -19996,7 +20617,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19996
20617
|
* already revoked.
|
|
19997
20618
|
*/
|
|
19998
20619
|
revoke(id, revokedBy, reason) {
|
|
19999
|
-
const now =
|
|
20620
|
+
const now = this.now();
|
|
20000
20621
|
const result = this.db.prepare(
|
|
20001
20622
|
`UPDATE exceptions
|
|
20002
20623
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -20010,7 +20631,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20010
20631
|
* callers must treat identically — means it does not and the detection is
|
|
20011
20632
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
20012
20633
|
*/
|
|
20013
|
-
consume(id, now =
|
|
20634
|
+
consume(id, now = this.now()) {
|
|
20014
20635
|
const result = this.consumeStmt.run({ id, now });
|
|
20015
20636
|
return Promise.resolve(Number(result.changes) === 1);
|
|
20016
20637
|
}
|
|
@@ -20019,7 +20640,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20019
20640
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
20020
20641
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
20021
20642
|
*/
|
|
20022
|
-
activeBundleEntries(keyVersion, now =
|
|
20643
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
20023
20644
|
const rows = allRows(
|
|
20024
20645
|
this.db.prepare(
|
|
20025
20646
|
`SELECT * FROM exceptions
|
|
@@ -20035,6 +20656,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20035
20656
|
ruleId: row.rule_id,
|
|
20036
20657
|
valueFingerprint: row.value_fingerprint,
|
|
20037
20658
|
keyVersion: row.key_version,
|
|
20659
|
+
capability: row.capability,
|
|
20038
20660
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20039
20661
|
maxUses: row.max_uses,
|
|
20040
20662
|
useCount: row.use_count,
|
|
@@ -20050,7 +20672,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20050
20672
|
* than the retention window on every write, so the ledger self-limits.
|
|
20051
20673
|
*/
|
|
20052
20674
|
recordBlocked(entry) {
|
|
20053
|
-
const now =
|
|
20675
|
+
const now = this.now();
|
|
20054
20676
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20055
20677
|
this.insertBlockedStmt.run({
|
|
20056
20678
|
reference: entry.reference,
|
|
@@ -20073,7 +20695,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20073
20695
|
WHERE blocked_at > :cutoff
|
|
20074
20696
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20075
20697
|
),
|
|
20076
|
-
{ cutoff:
|
|
20698
|
+
{ cutoff: this.now() - windowMs }
|
|
20077
20699
|
);
|
|
20078
20700
|
return Promise.resolve(
|
|
20079
20701
|
rows.map((row) => ({
|
|
@@ -20089,6 +20711,36 @@ var SqliteExceptionsRepository = class {
|
|
|
20089
20711
|
}))
|
|
20090
20712
|
);
|
|
20091
20713
|
}
|
|
20714
|
+
/**
|
|
20715
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20716
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20717
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20718
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20719
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20720
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20721
|
+
*
|
|
20722
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20723
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20724
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20725
|
+
*/
|
|
20726
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20727
|
+
try {
|
|
20728
|
+
const at = now ?? this.now();
|
|
20729
|
+
const row = getRow(
|
|
20730
|
+
this.db.prepare(
|
|
20731
|
+
`SELECT id FROM exceptions
|
|
20732
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20733
|
+
AND key_version = :keyVersion
|
|
20734
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20735
|
+
LIMIT 1`
|
|
20736
|
+
),
|
|
20737
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20738
|
+
);
|
|
20739
|
+
return Promise.resolve(row ?? null);
|
|
20740
|
+
} catch (err) {
|
|
20741
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20742
|
+
}
|
|
20743
|
+
}
|
|
20092
20744
|
/**
|
|
20093
20745
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20094
20746
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20096,7 +20748,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20096
20748
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20097
20749
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20098
20750
|
*/
|
|
20099
|
-
sweepTerminal(retentionMs, now =
|
|
20751
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20100
20752
|
const result = this.db.prepare(
|
|
20101
20753
|
`DELETE FROM exceptions
|
|
20102
20754
|
WHERE updated_at < :cutoff
|
|
@@ -20116,6 +20768,7 @@ function parseExceptionRow(row) {
|
|
|
20116
20768
|
valueFingerprint: row.value_fingerprint,
|
|
20117
20769
|
keyVersion: row.key_version,
|
|
20118
20770
|
maskedValue: row.masked_value,
|
|
20771
|
+
capability: row.capability,
|
|
20119
20772
|
scope: row.scope,
|
|
20120
20773
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20121
20774
|
maxUses: row.max_uses,
|
|
@@ -20158,6 +20811,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20158
20811
|
|
|
20159
20812
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20160
20813
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20814
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20815
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20816
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20817
|
+
function compareLocationOrder(a, b) {
|
|
20818
|
+
return compareFindingGroupOrder(
|
|
20819
|
+
{
|
|
20820
|
+
severity: a.maxSeverity,
|
|
20821
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20822
|
+
id: ""
|
|
20823
|
+
},
|
|
20824
|
+
{
|
|
20825
|
+
severity: b.maxSeverity,
|
|
20826
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20827
|
+
id: ""
|
|
20828
|
+
}
|
|
20829
|
+
);
|
|
20830
|
+
}
|
|
20161
20831
|
var CONCAT_SEP = ",";
|
|
20162
20832
|
var TUPLE_SEP = "|";
|
|
20163
20833
|
function splitConcat(value) {
|
|
@@ -20170,6 +20840,33 @@ function deriveInstanceStatus(row) {
|
|
|
20170
20840
|
latestResolutionStatus: row.latest_status
|
|
20171
20841
|
});
|
|
20172
20842
|
}
|
|
20843
|
+
function encodeGroupCursor(group) {
|
|
20844
|
+
const payload = {
|
|
20845
|
+
sev: group.severity,
|
|
20846
|
+
t: group.latestDetectedAt,
|
|
20847
|
+
id: group.id
|
|
20848
|
+
};
|
|
20849
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20850
|
+
}
|
|
20851
|
+
function decodeGroupCursor(cursor) {
|
|
20852
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20853
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20854
|
+
return {
|
|
20855
|
+
severity: parsed.sev,
|
|
20856
|
+
latestDetectedAt: parsed.t,
|
|
20857
|
+
id: parsed.id
|
|
20858
|
+
};
|
|
20859
|
+
}
|
|
20860
|
+
return null;
|
|
20861
|
+
}
|
|
20862
|
+
function firstAfter(sorted, cursor) {
|
|
20863
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20864
|
+
return index === -1 ? sorted.length : index;
|
|
20865
|
+
}
|
|
20866
|
+
function findDeepLinked(sorted, page, id) {
|
|
20867
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20868
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20869
|
+
}
|
|
20173
20870
|
var DAY_MS3 = 864e5;
|
|
20174
20871
|
var SqliteFindingsRepository = class {
|
|
20175
20872
|
constructor(db) {
|
|
@@ -20279,8 +20976,13 @@ var SqliteFindingsRepository = class {
|
|
|
20279
20976
|
*/
|
|
20280
20977
|
listGroupedFindings(query) {
|
|
20281
20978
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20282
|
-
const
|
|
20283
|
-
const
|
|
20979
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20980
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20981
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20982
|
+
const sessionParams = {
|
|
20983
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20984
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20985
|
+
};
|
|
20284
20986
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20285
20987
|
predicate,
|
|
20286
20988
|
params: sessionParams
|
|
@@ -20288,7 +20990,8 @@ var SqliteFindingsRepository = class {
|
|
|
20288
20990
|
const rows = allRows(
|
|
20289
20991
|
this.db.prepare(
|
|
20290
20992
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20291
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20993
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20994
|
+
kind, finding_key, latest_status
|
|
20292
20995
|
FROM (
|
|
20293
20996
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20294
20997
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20298,6 +21001,7 @@ var SqliteFindingsRepository = class {
|
|
|
20298
21001
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20299
21002
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20300
21003
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21004
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20301
21005
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20302
21006
|
latest.status AS latest_status,
|
|
20303
21007
|
ROW_NUMBER() OVER (
|
|
@@ -20329,6 +21033,8 @@ var SqliteFindingsRepository = class {
|
|
|
20329
21033
|
repo: r.repo ?? "",
|
|
20330
21034
|
file: r.file ?? "",
|
|
20331
21035
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21036
|
+
eventId: r.event_id,
|
|
21037
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20332
21038
|
status: deriveInstanceStatus(r)
|
|
20333
21039
|
}));
|
|
20334
21040
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20352,18 +21058,23 @@ var SqliteFindingsRepository = class {
|
|
|
20352
21058
|
groups: sorted.length
|
|
20353
21059
|
};
|
|
20354
21060
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21061
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21062
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21063
|
+
const page = sorted.slice(start, start + limit);
|
|
21064
|
+
const lastOnPage = page.at(-1);
|
|
21065
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21066
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20355
21067
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20356
|
-
const
|
|
20357
|
-
|
|
20358
|
-
|
|
20359
|
-
|
|
20360
|
-
|
|
20361
|
-
);
|
|
21068
|
+
const narrow = (g) => statusSet ? {
|
|
21069
|
+
...g,
|
|
21070
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21071
|
+
} : g;
|
|
21072
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20362
21073
|
return Promise.resolve({
|
|
20363
21074
|
totals,
|
|
20364
21075
|
facets,
|
|
20365
21076
|
items,
|
|
20366
|
-
nextCursor
|
|
21077
|
+
nextCursor,
|
|
20367
21078
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20368
21079
|
});
|
|
20369
21080
|
}
|
|
@@ -20395,6 +21106,266 @@ var SqliteFindingsRepository = class {
|
|
|
20395
21106
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20396
21107
|
* path repeating across tuples.)
|
|
20397
21108
|
*/
|
|
21109
|
+
/**
|
|
21110
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21111
|
+
* paged by keyset.
|
|
21112
|
+
*
|
|
21113
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21114
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21115
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21116
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21117
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21118
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21119
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21120
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21121
|
+
* none of the mappers names", which no IN-list can say.
|
|
21122
|
+
*
|
|
21123
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21124
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21125
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21126
|
+
* while the counting runs, and only the page itself is retained.
|
|
21127
|
+
*/
|
|
21128
|
+
listFindingInstances(query) {
|
|
21129
|
+
const opts = {
|
|
21130
|
+
severity: query.severity,
|
|
21131
|
+
subtype: query.subtype,
|
|
21132
|
+
providers: query.provider,
|
|
21133
|
+
actions: query.action,
|
|
21134
|
+
statuses: query.status,
|
|
21135
|
+
tools: query.tool,
|
|
21136
|
+
repo: query.repo,
|
|
21137
|
+
file: query.file,
|
|
21138
|
+
q: query.q
|
|
21139
|
+
};
|
|
21140
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21141
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21142
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21143
|
+
const items = [];
|
|
21144
|
+
let total = 0;
|
|
21145
|
+
let last;
|
|
21146
|
+
let hasMore = false;
|
|
21147
|
+
for (const row of this.scanFindingRows({
|
|
21148
|
+
sessionId: query.sessionId,
|
|
21149
|
+
from: query.from
|
|
21150
|
+
})) {
|
|
21151
|
+
accumulator.add(row);
|
|
21152
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21153
|
+
total += 1;
|
|
21154
|
+
if (items.length < limit) {
|
|
21155
|
+
items.push(toInstanceDetail(row));
|
|
21156
|
+
last = row;
|
|
21157
|
+
} else {
|
|
21158
|
+
hasMore = true;
|
|
21159
|
+
}
|
|
21160
|
+
}
|
|
21161
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21162
|
+
if (cursor !== null) {
|
|
21163
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21164
|
+
return Promise.resolve({
|
|
21165
|
+
totals: { findings: total },
|
|
21166
|
+
facets: accumulator.facets(),
|
|
21167
|
+
items: resumed.items,
|
|
21168
|
+
nextCursor: resumed.nextCursor
|
|
21169
|
+
});
|
|
21170
|
+
}
|
|
21171
|
+
return Promise.resolve({
|
|
21172
|
+
totals: { findings: total },
|
|
21173
|
+
facets: accumulator.facets(),
|
|
21174
|
+
items,
|
|
21175
|
+
nextCursor
|
|
21176
|
+
});
|
|
21177
|
+
}
|
|
21178
|
+
/**
|
|
21179
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21180
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21181
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21182
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21183
|
+
*/
|
|
21184
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21185
|
+
const items = [];
|
|
21186
|
+
let last;
|
|
21187
|
+
let hasMore = false;
|
|
21188
|
+
for (const row of this.scanFindingRows({
|
|
21189
|
+
sessionId: query.sessionId,
|
|
21190
|
+
from: query.from,
|
|
21191
|
+
after: cursor
|
|
21192
|
+
})) {
|
|
21193
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21194
|
+
if (items.length < limit) {
|
|
21195
|
+
items.push(toInstanceDetail(row));
|
|
21196
|
+
last = row;
|
|
21197
|
+
} else {
|
|
21198
|
+
hasMore = true;
|
|
21199
|
+
break;
|
|
21200
|
+
}
|
|
21201
|
+
}
|
|
21202
|
+
return {
|
|
21203
|
+
items,
|
|
21204
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21205
|
+
};
|
|
21206
|
+
}
|
|
21207
|
+
/**
|
|
21208
|
+
* The same findings folded by location: repository, then file within it.
|
|
21209
|
+
*
|
|
21210
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21211
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21212
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21213
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21214
|
+
* filter can name it.
|
|
21215
|
+
*/
|
|
21216
|
+
listFindingLocations(query) {
|
|
21217
|
+
const opts = {
|
|
21218
|
+
severity: query.severity,
|
|
21219
|
+
subtype: query.subtype,
|
|
21220
|
+
providers: query.provider,
|
|
21221
|
+
actions: query.action,
|
|
21222
|
+
statuses: query.status,
|
|
21223
|
+
tools: query.tool,
|
|
21224
|
+
q: query.q
|
|
21225
|
+
};
|
|
21226
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21227
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21228
|
+
let total = 0;
|
|
21229
|
+
for (const row of this.scanFindingRows({
|
|
21230
|
+
sessionId: query.sessionId,
|
|
21231
|
+
from: query.from
|
|
21232
|
+
})) {
|
|
21233
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21234
|
+
total += 1;
|
|
21235
|
+
let files = byRepo.get(row.repo);
|
|
21236
|
+
if (files === void 0) {
|
|
21237
|
+
files = /* @__PURE__ */ new Map();
|
|
21238
|
+
byRepo.set(row.repo, files);
|
|
21239
|
+
}
|
|
21240
|
+
let acc = files.get(row.file);
|
|
21241
|
+
if (acc === void 0) {
|
|
21242
|
+
acc = newLocationAccumulator();
|
|
21243
|
+
files.set(row.file, acc);
|
|
21244
|
+
}
|
|
21245
|
+
addToLocation(acc, row);
|
|
21246
|
+
}
|
|
21247
|
+
let fileCount = 0;
|
|
21248
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21249
|
+
fileCount += files.size;
|
|
21250
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21251
|
+
file: file2,
|
|
21252
|
+
instanceCount: acc.instanceCount,
|
|
21253
|
+
maxSeverity: acc.maxSeverity,
|
|
21254
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21255
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21256
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21257
|
+
})).sort(compareLocationOrder);
|
|
21258
|
+
const rollup = fileRows.reduce(
|
|
21259
|
+
(a, f) => ({
|
|
21260
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21261
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21262
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21263
|
+
}),
|
|
21264
|
+
{
|
|
21265
|
+
instanceCount: 0,
|
|
21266
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21267
|
+
latestDetectedAt: ""
|
|
21268
|
+
}
|
|
21269
|
+
);
|
|
21270
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21271
|
+
const folded = foldGroupStatus(statuses);
|
|
21272
|
+
return {
|
|
21273
|
+
repo,
|
|
21274
|
+
instanceCount: rollup.instanceCount,
|
|
21275
|
+
maxSeverity: rollup.maxSeverity,
|
|
21276
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21277
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21278
|
+
files: fileRows
|
|
21279
|
+
};
|
|
21280
|
+
});
|
|
21281
|
+
repos.sort(compareLocationOrder);
|
|
21282
|
+
return Promise.resolve({
|
|
21283
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21284
|
+
items: repos.slice(0, limit),
|
|
21285
|
+
hasMore: repos.length > limit
|
|
21286
|
+
});
|
|
21287
|
+
}
|
|
21288
|
+
/**
|
|
21289
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21290
|
+
*
|
|
21291
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21292
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21293
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21294
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21295
|
+
* rather than one unbounded result set.
|
|
21296
|
+
*
|
|
21297
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21298
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21299
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21300
|
+
* a window over the whole resolution table once per batch.
|
|
21301
|
+
*
|
|
21302
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21303
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21304
|
+
* dimension — see listFindingInstances.
|
|
21305
|
+
*/
|
|
21306
|
+
*scanFindingRows(scope) {
|
|
21307
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21308
|
+
const params = [];
|
|
21309
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21310
|
+
conditions.push("e.root_session_id = ?");
|
|
21311
|
+
params.push(scope.sessionId);
|
|
21312
|
+
}
|
|
21313
|
+
if (scope.from !== void 0) {
|
|
21314
|
+
conditions.push("e.started_at >= ?");
|
|
21315
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21316
|
+
}
|
|
21317
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21318
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21319
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21320
|
+
e.started_at AS occurred_at,
|
|
21321
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21322
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21323
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21324
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21325
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21326
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21327
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21328
|
+
FROM inspection_findings f
|
|
21329
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21330
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21331
|
+
WHERE ${conditions.join(" AND ")}
|
|
21332
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21333
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21334
|
+
LIMIT ?`;
|
|
21335
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21336
|
+
for (; ; ) {
|
|
21337
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21338
|
+
...params,
|
|
21339
|
+
after.startedAtMs,
|
|
21340
|
+
after.startedAtMs,
|
|
21341
|
+
after.id,
|
|
21342
|
+
SCAN_BATCH_ROWS
|
|
21343
|
+
]);
|
|
21344
|
+
for (const r of rows) {
|
|
21345
|
+
yield {
|
|
21346
|
+
id: r.id,
|
|
21347
|
+
ruleId: r.rule_id,
|
|
21348
|
+
category: r.category,
|
|
21349
|
+
severity: r.severity,
|
|
21350
|
+
maskedMatch: r.masked_match,
|
|
21351
|
+
actionTaken: r.action_taken,
|
|
21352
|
+
confidence: r.confidence,
|
|
21353
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21354
|
+
sourceTool: r.source_tool,
|
|
21355
|
+
repo: r.repo ?? "",
|
|
21356
|
+
file: r.file ?? "",
|
|
21357
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21358
|
+
eventId: r.event_id,
|
|
21359
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21360
|
+
status: deriveInstanceStatus(r)
|
|
21361
|
+
};
|
|
21362
|
+
}
|
|
21363
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21364
|
+
const lastRow = rows[rows.length - 1];
|
|
21365
|
+
if (lastRow === void 0) return;
|
|
21366
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21367
|
+
}
|
|
21368
|
+
}
|
|
20398
21369
|
groupAggregates(withSearchText, scope) {
|
|
20399
21370
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20400
21371
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20655,7 +21626,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20655
21626
|
};
|
|
20656
21627
|
|
|
20657
21628
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20658
|
-
import { createHash as createHash2, randomUUID as
|
|
21629
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20659
21630
|
|
|
20660
21631
|
// ../../packages/persistence/src/semver.ts
|
|
20661
21632
|
function parse3(version2) {
|
|
@@ -20806,7 +21777,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20806
21777
|
let behind = false;
|
|
20807
21778
|
for (const row of rows) {
|
|
20808
21779
|
const params = {
|
|
20809
|
-
id:
|
|
21780
|
+
id: randomUUID3(),
|
|
20810
21781
|
namespace: row.namespace,
|
|
20811
21782
|
packId: row.packId,
|
|
20812
21783
|
version: row.version,
|
|
@@ -20818,7 +21789,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20818
21789
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20819
21790
|
this.upsertAvailableStmt.run({
|
|
20820
21791
|
...params,
|
|
20821
|
-
id:
|
|
21792
|
+
id: randomUUID3(),
|
|
20822
21793
|
recordedBy: meta3?.recordedBy ?? null
|
|
20823
21794
|
});
|
|
20824
21795
|
} else {
|
|
@@ -21141,14 +22112,15 @@ var SqliteInventoryRepository = class {
|
|
|
21141
22112
|
};
|
|
21142
22113
|
|
|
21143
22114
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21144
|
-
import { randomUUID as
|
|
22115
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21145
22116
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21146
22117
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21147
22118
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21148
22119
|
var HARNESS_LABELS = {
|
|
21149
22120
|
claudecode: "Claude Code",
|
|
21150
22121
|
cursor: "Cursor",
|
|
21151
|
-
codex: "Codex"
|
|
22122
|
+
codex: "Codex",
|
|
22123
|
+
antigravity: "Antigravity"
|
|
21152
22124
|
};
|
|
21153
22125
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21154
22126
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21163,6 +22135,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21163
22135
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21164
22136
|
if (t.includes("cursor")) return "cursor";
|
|
21165
22137
|
if (t.includes("codex")) return "codex";
|
|
22138
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21166
22139
|
return null;
|
|
21167
22140
|
}
|
|
21168
22141
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21621,7 +22594,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21621
22594
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21622
22595
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21623
22596
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21624
|
-
).run({ id:
|
|
22597
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21625
22598
|
}
|
|
21626
22599
|
return true;
|
|
21627
22600
|
}
|
|
@@ -21642,7 +22615,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21642
22615
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21643
22616
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21644
22617
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21645
|
-
).run({ id:
|
|
22618
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21646
22619
|
}
|
|
21647
22620
|
this.configRowsCache = void 0;
|
|
21648
22621
|
return "ok";
|
|
@@ -21939,7 +22912,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21939
22912
|
};
|
|
21940
22913
|
|
|
21941
22914
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
21942
|
-
import { randomUUID as
|
|
22915
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
21943
22916
|
var SqlitePoliciesRepository = class {
|
|
21944
22917
|
constructor(db) {
|
|
21945
22918
|
this.db = db;
|
|
@@ -21974,7 +22947,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21974
22947
|
failOpenTransaction(this.db, () => {
|
|
21975
22948
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
21976
22949
|
stmt.run({
|
|
21977
|
-
id:
|
|
22950
|
+
id: randomUUID5(),
|
|
21978
22951
|
target: JSON.stringify({ category }),
|
|
21979
22952
|
action,
|
|
21980
22953
|
now: Date.now()
|
|
@@ -21994,7 +22967,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21994
22967
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
21995
22968
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
21996
22969
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
21997
|
-
).run({ id:
|
|
22970
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
21998
22971
|
}
|
|
21999
22972
|
// Caps every global per-category policy currently set to block/redact down
|
|
22000
22973
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22062,7 +23035,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22062
23035
|
};
|
|
22063
23036
|
|
|
22064
23037
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22065
|
-
import { randomUUID as
|
|
23038
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22066
23039
|
var SqliteProjectFilesRepository = class {
|
|
22067
23040
|
constructor(db) {
|
|
22068
23041
|
this.db = db;
|
|
@@ -22094,7 +23067,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22094
23067
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22095
23068
|
for (const file2 of scan2.files) {
|
|
22096
23069
|
this.upsertStmt.run({
|
|
22097
|
-
id:
|
|
23070
|
+
id: randomUUID6(),
|
|
22098
23071
|
projectId,
|
|
22099
23072
|
path: file2.path,
|
|
22100
23073
|
name: file2.name,
|
|
@@ -22108,7 +23081,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22108
23081
|
};
|
|
22109
23082
|
|
|
22110
23083
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22111
|
-
import { randomUUID as
|
|
23084
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22112
23085
|
var SqliteResolutionsRepository = class {
|
|
22113
23086
|
constructor(db, now = () => Date.now()) {
|
|
22114
23087
|
this.db = db;
|
|
@@ -22162,7 +23135,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22162
23135
|
*/
|
|
22163
23136
|
insertResolution(r) {
|
|
22164
23137
|
this.insertStmt.run({
|
|
22165
|
-
id:
|
|
23138
|
+
id: randomUUID7(),
|
|
22166
23139
|
findingKey: r.findingKey,
|
|
22167
23140
|
status: FindingStatus.parse(r.status),
|
|
22168
23141
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22221,13 +23194,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22221
23194
|
this.readStmt = db.prepare(
|
|
22222
23195
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22223
23196
|
);
|
|
23197
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23198
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23199
|
+
);
|
|
23200
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23201
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23202
|
+
);
|
|
22224
23203
|
}
|
|
22225
23204
|
db;
|
|
22226
23205
|
upsertStmt;
|
|
22227
23206
|
readStmt;
|
|
23207
|
+
countQuarantinedStmt;
|
|
23208
|
+
clearQuarantinedStmt;
|
|
22228
23209
|
getVerdict(ruleKey) {
|
|
22229
23210
|
return getRow(this.readStmt, { ruleKey });
|
|
22230
23211
|
}
|
|
23212
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23213
|
+
countQuarantined() {
|
|
23214
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23215
|
+
}
|
|
23216
|
+
/**
|
|
23217
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23218
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23219
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23220
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23221
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23222
|
+
*
|
|
23223
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23224
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23225
|
+
*
|
|
23226
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23227
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23228
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23229
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23230
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23231
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23232
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23233
|
+
* to restore are silently still disabled.
|
|
23234
|
+
*/
|
|
23235
|
+
clearQuarantined() {
|
|
23236
|
+
const before = this.countQuarantined();
|
|
23237
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23238
|
+
this.clearQuarantinedStmt.run();
|
|
23239
|
+
});
|
|
23240
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23241
|
+
}
|
|
22231
23242
|
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
22232
23243
|
failOpenTransaction(this.db, () => {
|
|
22233
23244
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
@@ -22281,6 +23292,419 @@ var SqliteScanLedgerRepository = class {
|
|
|
22281
23292
|
}
|
|
22282
23293
|
};
|
|
22283
23294
|
|
|
23295
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
23296
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23297
|
+
function pageLimit(requested, fallback) {
|
|
23298
|
+
if (requested === void 0) return fallback;
|
|
23299
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23300
|
+
}
|
|
23301
|
+
function encodeReuseCursor(payload) {
|
|
23302
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23303
|
+
}
|
|
23304
|
+
function decodeReuseCursor(cursor) {
|
|
23305
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23306
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23307
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23308
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23309
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23310
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23311
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23312
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23313
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23314
|
+
}
|
|
23315
|
+
return null;
|
|
23316
|
+
}
|
|
23317
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23318
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23319
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23320
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23321
|
+
function toSighting(row) {
|
|
23322
|
+
return {
|
|
23323
|
+
location: row.location,
|
|
23324
|
+
kind: row.kind,
|
|
23325
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23326
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23327
|
+
};
|
|
23328
|
+
}
|
|
23329
|
+
var SELECT_COLUMNS = `
|
|
23330
|
+
pointer_id AS pointerId,
|
|
23331
|
+
value_fingerprint AS valueFingerprint,
|
|
23332
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
23333
|
+
key_version AS keyVersion,
|
|
23334
|
+
format_version AS formatVersion,
|
|
23335
|
+
category,
|
|
23336
|
+
rule_id AS ruleId,
|
|
23337
|
+
masked_match AS maskedMatch,
|
|
23338
|
+
provider,
|
|
23339
|
+
ciphertext,
|
|
23340
|
+
nonce,
|
|
23341
|
+
auth_tag AS authTag,
|
|
23342
|
+
occurrence_count AS occurrenceCount,
|
|
23343
|
+
first_seen AS firstSeen,
|
|
23344
|
+
last_seen AS lastSeen`;
|
|
23345
|
+
function toRow(raw) {
|
|
23346
|
+
const { provider, ...rest } = raw;
|
|
23347
|
+
return provider === null ? rest : { ...rest, provider };
|
|
23348
|
+
}
|
|
23349
|
+
var SqliteSecretVaultRepository = class {
|
|
23350
|
+
constructor(db) {
|
|
23351
|
+
this.db = db;
|
|
23352
|
+
this.insertStmt = db.prepare(
|
|
23353
|
+
`INSERT INTO secret_vault (
|
|
23354
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
23355
|
+
format_version, category, rule_id, masked_match, provider,
|
|
23356
|
+
ciphertext, nonce, auth_tag,
|
|
23357
|
+
occurrence_count, first_seen, last_seen
|
|
23358
|
+
) VALUES (
|
|
23359
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
23360
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
23361
|
+
:ciphertext, :nonce, :authTag,
|
|
23362
|
+
1, :now, :now
|
|
23363
|
+
)`
|
|
23364
|
+
);
|
|
23365
|
+
this.bumpStmt = db.prepare(
|
|
23366
|
+
`UPDATE secret_vault
|
|
23367
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
23368
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
23369
|
+
);
|
|
23370
|
+
this.byPointerStmt = db.prepare(
|
|
23371
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
23372
|
+
);
|
|
23373
|
+
this.byFingerprintStmt = db.prepare(
|
|
23374
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
23375
|
+
);
|
|
23376
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
23377
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
23378
|
+
`UPDATE secret_vault
|
|
23379
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
23380
|
+
WHERE pointer_id = :pointerId`
|
|
23381
|
+
);
|
|
23382
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
23383
|
+
`UPDATE secret_vault
|
|
23384
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
23385
|
+
WHERE pointer_id = :pointerId`
|
|
23386
|
+
);
|
|
23387
|
+
this.derefStmt = db.prepare(
|
|
23388
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
23389
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
23390
|
+
);
|
|
23391
|
+
}
|
|
23392
|
+
db;
|
|
23393
|
+
insertStmt;
|
|
23394
|
+
bumpStmt;
|
|
23395
|
+
byPointerStmt;
|
|
23396
|
+
byFingerprintStmt;
|
|
23397
|
+
listStmt;
|
|
23398
|
+
replaceCiphertextStmt;
|
|
23399
|
+
refreshFingerprintStmt;
|
|
23400
|
+
derefStmt;
|
|
23401
|
+
/**
|
|
23402
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
23403
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
23404
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
23405
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
23406
|
+
* wire token. `minted` is true only when this call created the row.
|
|
23407
|
+
*
|
|
23408
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
23409
|
+
* writers cannot both decide they are minting.
|
|
23410
|
+
*/
|
|
23411
|
+
upsert(input, now) {
|
|
23412
|
+
let minted = false;
|
|
23413
|
+
withTransaction(
|
|
23414
|
+
this.db,
|
|
23415
|
+
() => {
|
|
23416
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
23417
|
+
valueFingerprint: input.valueFingerprint
|
|
23418
|
+
});
|
|
23419
|
+
if (existing === void 0) {
|
|
23420
|
+
this.insertStmt.run(
|
|
23421
|
+
bindParams({
|
|
23422
|
+
pointerId: input.pointerId,
|
|
23423
|
+
valueFingerprint: input.valueFingerprint,
|
|
23424
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
23425
|
+
keyVersion: input.keyVersion,
|
|
23426
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
23427
|
+
category: input.category,
|
|
23428
|
+
ruleId: input.ruleId,
|
|
23429
|
+
maskedMatch: input.maskedMatch,
|
|
23430
|
+
provider: input.provider,
|
|
23431
|
+
ciphertext: input.ciphertext,
|
|
23432
|
+
nonce: input.nonce,
|
|
23433
|
+
authTag: input.authTag,
|
|
23434
|
+
now
|
|
23435
|
+
})
|
|
23436
|
+
);
|
|
23437
|
+
minted = true;
|
|
23438
|
+
return;
|
|
23439
|
+
}
|
|
23440
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
23441
|
+
},
|
|
23442
|
+
"IMMEDIATE"
|
|
23443
|
+
);
|
|
23444
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
23445
|
+
valueFingerprint: input.valueFingerprint
|
|
23446
|
+
});
|
|
23447
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
23448
|
+
return { row: toRow(row), minted };
|
|
23449
|
+
}
|
|
23450
|
+
byPointerId(pointerId) {
|
|
23451
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
23452
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23453
|
+
}
|
|
23454
|
+
byValueFingerprint(fingerprint) {
|
|
23455
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
23456
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23457
|
+
}
|
|
23458
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
23459
|
+
recordDeref(entry) {
|
|
23460
|
+
this.derefStmt.run(
|
|
23461
|
+
bindParams({
|
|
23462
|
+
id: entry.id,
|
|
23463
|
+
pointerId: entry.pointerId,
|
|
23464
|
+
at: entry.at,
|
|
23465
|
+
target: entry.target,
|
|
23466
|
+
reason: entry.reason,
|
|
23467
|
+
outcome: entry.outcome,
|
|
23468
|
+
grantId: entry.grantId,
|
|
23469
|
+
pointerCount: entry.pointerCount ?? 1
|
|
23470
|
+
})
|
|
23471
|
+
);
|
|
23472
|
+
}
|
|
23473
|
+
listAll() {
|
|
23474
|
+
return allRows(this.listStmt).map(toRow);
|
|
23475
|
+
}
|
|
23476
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
23477
|
+
replaceCiphertext(pointerId, next) {
|
|
23478
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
23479
|
+
}
|
|
23480
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
23481
|
+
refreshFingerprint(pointerId, next) {
|
|
23482
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
23483
|
+
}
|
|
23484
|
+
/**
|
|
23485
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
23486
|
+
* audit is left alone on purpose — see the table note above.
|
|
23487
|
+
*/
|
|
23488
|
+
purgeAll() {
|
|
23489
|
+
let destroyed = 0;
|
|
23490
|
+
withTransaction(
|
|
23491
|
+
this.db,
|
|
23492
|
+
() => {
|
|
23493
|
+
destroyed = this.countEntries();
|
|
23494
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
23495
|
+
},
|
|
23496
|
+
"IMMEDIATE"
|
|
23497
|
+
);
|
|
23498
|
+
return destroyed;
|
|
23499
|
+
}
|
|
23500
|
+
/**
|
|
23501
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
23502
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
23503
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
23504
|
+
* so callers wrap this, not the other way around.
|
|
23505
|
+
*/
|
|
23506
|
+
recordSighting(entry, now) {
|
|
23507
|
+
this.db.prepare(
|
|
23508
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
23509
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
23510
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
23511
|
+
).run({
|
|
23512
|
+
id: randomUUID8(),
|
|
23513
|
+
pointerId: entry.pointerId,
|
|
23514
|
+
location: entry.location,
|
|
23515
|
+
kind: entry.kind,
|
|
23516
|
+
now
|
|
23517
|
+
});
|
|
23518
|
+
}
|
|
23519
|
+
/**
|
|
23520
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23521
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23522
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23523
|
+
*
|
|
23524
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23525
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23526
|
+
*/
|
|
23527
|
+
sightingsFor(pointerIds) {
|
|
23528
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23529
|
+
if (pointerIds.length === 0) return byPointer;
|
|
23530
|
+
const rows = allRows(
|
|
23531
|
+
this.db.prepare(
|
|
23532
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23533
|
+
FROM secret_vault_sighting
|
|
23534
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23535
|
+
ORDER BY last_seen DESC`
|
|
23536
|
+
),
|
|
23537
|
+
pointerIds
|
|
23538
|
+
);
|
|
23539
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23540
|
+
return byPointer;
|
|
23541
|
+
}
|
|
23542
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23543
|
+
toInventoryEntries(rows) {
|
|
23544
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
23545
|
+
return rows.map((r) => ({
|
|
23546
|
+
pointerId: r.pointer_id,
|
|
23547
|
+
category: r.category,
|
|
23548
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23549
|
+
maskedMatch: r.masked_match,
|
|
23550
|
+
occurrences: r.occurrence_count,
|
|
23551
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
23552
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23553
|
+
revealGrantId: r.grant_id,
|
|
23554
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
23555
|
+
}));
|
|
23556
|
+
}
|
|
23557
|
+
/**
|
|
23558
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23559
|
+
* value's descriptor data joined with its sightings and the active
|
|
23560
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23561
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23562
|
+
*
|
|
23563
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23564
|
+
* sees never depends on how far they have paged.
|
|
23565
|
+
*/
|
|
23566
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23567
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23568
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23569
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
23570
|
+
const rows = allRows(
|
|
23571
|
+
this.db.prepare(
|
|
23572
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23573
|
+
(SELECT e.id FROM exceptions e
|
|
23574
|
+
WHERE e.rule_id = v.rule_id
|
|
23575
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23576
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23577
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23578
|
+
LIMIT 1) AS grant_id
|
|
23579
|
+
FROM secret_vault v
|
|
23580
|
+
${where}
|
|
23581
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23582
|
+
LIMIT :limit`
|
|
23583
|
+
),
|
|
23584
|
+
bindParams({
|
|
23585
|
+
now,
|
|
23586
|
+
limit: limit + 1,
|
|
23587
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23588
|
+
})
|
|
23589
|
+
);
|
|
23590
|
+
const hasMore = rows.length > limit;
|
|
23591
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23592
|
+
const last = page[page.length - 1];
|
|
23593
|
+
return {
|
|
23594
|
+
totals: { values: this.countEntries() },
|
|
23595
|
+
items: this.toInventoryEntries(page),
|
|
23596
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23597
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23598
|
+
};
|
|
23599
|
+
}
|
|
23600
|
+
/**
|
|
23601
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23602
|
+
* than one location — most-reused first, one page at a time.
|
|
23603
|
+
*
|
|
23604
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23605
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23606
|
+
* under-report exactly the values a reader most needs to see.
|
|
23607
|
+
*/
|
|
23608
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23609
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23610
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23611
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23612
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23613
|
+
const rows = allRows(
|
|
23614
|
+
this.db.prepare(
|
|
23615
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23616
|
+
(SELECT e.id FROM exceptions e
|
|
23617
|
+
WHERE e.rule_id = v.rule_id
|
|
23618
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23619
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23620
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23621
|
+
LIMIT 1) AS grant_id
|
|
23622
|
+
FROM secret_vault v
|
|
23623
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23624
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23625
|
+
LIMIT :limit`
|
|
23626
|
+
),
|
|
23627
|
+
bindParams({
|
|
23628
|
+
now,
|
|
23629
|
+
limit: limit + 1,
|
|
23630
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23631
|
+
})
|
|
23632
|
+
);
|
|
23633
|
+
const hasMore = rows.length > limit;
|
|
23634
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23635
|
+
const last = page[page.length - 1];
|
|
23636
|
+
return {
|
|
23637
|
+
totals: { reused: this.countReused() },
|
|
23638
|
+
items: this.toInventoryEntries(page),
|
|
23639
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23640
|
+
};
|
|
23641
|
+
}
|
|
23642
|
+
/**
|
|
23643
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23644
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23645
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23646
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23647
|
+
*
|
|
23648
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23649
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23650
|
+
* the reader pages.
|
|
23651
|
+
*/
|
|
23652
|
+
listDerefs(query = {}) {
|
|
23653
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23654
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23655
|
+
const conditions = [];
|
|
23656
|
+
if (query.includeBatched !== true) {
|
|
23657
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23658
|
+
}
|
|
23659
|
+
if (cursor !== null) {
|
|
23660
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23661
|
+
}
|
|
23662
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
23663
|
+
const rows = allRows(
|
|
23664
|
+
this.db.prepare(
|
|
23665
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
23666
|
+
FROM secret_vault_deref ${where}
|
|
23667
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
23668
|
+
),
|
|
23669
|
+
bindParams({
|
|
23670
|
+
limit: limit + 1,
|
|
23671
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23672
|
+
})
|
|
23673
|
+
);
|
|
23674
|
+
const hasMore = rows.length > limit;
|
|
23675
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23676
|
+
const last = page[page.length - 1];
|
|
23677
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
23678
|
+
this.db,
|
|
23679
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
23680
|
+
);
|
|
23681
|
+
return {
|
|
23682
|
+
items: page.map((r) => ({
|
|
23683
|
+
id: r.id,
|
|
23684
|
+
pointerId: r.pointer_id,
|
|
23685
|
+
at: new Date(r.at).toISOString(),
|
|
23686
|
+
target: r.target,
|
|
23687
|
+
reason: r.reason,
|
|
23688
|
+
outcome: r.outcome,
|
|
23689
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
23690
|
+
pointerCount: r.pointer_count
|
|
23691
|
+
})),
|
|
23692
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
23693
|
+
hiddenBatched
|
|
23694
|
+
};
|
|
23695
|
+
}
|
|
23696
|
+
countEntries() {
|
|
23697
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
23698
|
+
}
|
|
23699
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23700
|
+
countReused() {
|
|
23701
|
+
return countScalar(
|
|
23702
|
+
this.db,
|
|
23703
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23704
|
+
);
|
|
23705
|
+
}
|
|
23706
|
+
};
|
|
23707
|
+
|
|
22284
23708
|
// ../../packages/persistence/src/repositories/security.ts
|
|
22285
23709
|
var DAY_MS4 = 864e5;
|
|
22286
23710
|
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
@@ -22293,7 +23717,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22293
23717
|
var SCAN_COVERAGE = [
|
|
22294
23718
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22295
23719
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22296
|
-
{ provider: "codex", coverage:
|
|
23720
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23721
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23722
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22297
23723
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22298
23724
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22299
23725
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -22626,7 +24052,7 @@ var SqliteSecurityRepository = class {
|
|
|
22626
24052
|
};
|
|
22627
24053
|
|
|
22628
24054
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22629
|
-
import { randomUUID as
|
|
24055
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
22630
24056
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22631
24057
|
var IN_CHUNK = 500;
|
|
22632
24058
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22882,7 +24308,7 @@ var SqliteSharesRepository = class {
|
|
|
22882
24308
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22883
24309
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22884
24310
|
).run({
|
|
22885
|
-
id:
|
|
24311
|
+
id: randomUUID9(),
|
|
22886
24312
|
destinationId,
|
|
22887
24313
|
host: dest.host,
|
|
22888
24314
|
decision,
|
|
@@ -23031,7 +24457,7 @@ var SqliteSharesRepository = class {
|
|
|
23031
24457
|
let destinationId = destIds.get(hit.host);
|
|
23032
24458
|
if (destinationId === void 0) {
|
|
23033
24459
|
destStmt.run({
|
|
23034
|
-
id:
|
|
24460
|
+
id: randomUUID9(),
|
|
23035
24461
|
kind: hit.kind,
|
|
23036
24462
|
name: hit.name,
|
|
23037
24463
|
host: hit.host,
|
|
@@ -23047,7 +24473,7 @@ var SqliteSharesRepository = class {
|
|
|
23047
24473
|
let endpointId = endpointIds.get(endpointKey);
|
|
23048
24474
|
if (endpointId === void 0) {
|
|
23049
24475
|
endpointStmt.run({
|
|
23050
|
-
id:
|
|
24476
|
+
id: randomUUID9(),
|
|
23051
24477
|
destinationId,
|
|
23052
24478
|
method: hit.method,
|
|
23053
24479
|
transport: hit.transport,
|
|
@@ -23060,7 +24486,7 @@ var SqliteSharesRepository = class {
|
|
|
23060
24486
|
endpointIds.set(endpointKey, endpointId);
|
|
23061
24487
|
}
|
|
23062
24488
|
siteStmt.run({
|
|
23063
|
-
id:
|
|
24489
|
+
id: randomUUID9(),
|
|
23064
24490
|
endpointId,
|
|
23065
24491
|
project: input.project,
|
|
23066
24492
|
projectKey: input.projectKey,
|
|
@@ -23425,6 +24851,9 @@ function purgeSampleData(db) {
|
|
|
23425
24851
|
}
|
|
23426
24852
|
|
|
23427
24853
|
// ../../packages/persistence/src/database.ts
|
|
24854
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24855
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24856
|
+
);
|
|
23428
24857
|
function linkHost(input, hostId) {
|
|
23429
24858
|
return hostId ? { ...input, hostId } : input;
|
|
23430
24859
|
}
|
|
@@ -23446,21 +24875,34 @@ function openWithPragmas(file2) {
|
|
|
23446
24875
|
}
|
|
23447
24876
|
return db;
|
|
23448
24877
|
}
|
|
23449
|
-
function backupLegacyStore(file2) {
|
|
23450
|
-
|
|
23451
|
-
|
|
23452
|
-
|
|
23453
|
-
|
|
23454
|
-
|
|
24878
|
+
function backupLegacyStore(db, file2) {
|
|
24879
|
+
reapStalePartials(file2);
|
|
24880
|
+
const backup = backupPath(file2, "legacy");
|
|
24881
|
+
let snapshotted = false;
|
|
24882
|
+
let snapshotError;
|
|
24883
|
+
try {
|
|
24884
|
+
snapshotStore(db, backup);
|
|
24885
|
+
snapshotted = true;
|
|
24886
|
+
} catch (error51) {
|
|
24887
|
+
snapshotError = error51;
|
|
24888
|
+
} finally {
|
|
24889
|
+
db.close();
|
|
23455
24890
|
}
|
|
24891
|
+
if (!snapshotted) {
|
|
24892
|
+
akaWarn(
|
|
24893
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24894
|
+
);
|
|
24895
|
+
moveStoreAside(file2, backup);
|
|
24896
|
+
return backup;
|
|
24897
|
+
}
|
|
24898
|
+
discardStore(file2, backup);
|
|
23456
24899
|
return backup;
|
|
23457
24900
|
}
|
|
23458
24901
|
function openAndInitialize(file2) {
|
|
23459
24902
|
let db = openWithPragmas(file2);
|
|
23460
24903
|
try {
|
|
23461
24904
|
if (isForeignSqliteLineage(db)) {
|
|
23462
|
-
db
|
|
23463
|
-
const backup = backupLegacyStore(file2);
|
|
24905
|
+
const backup = backupLegacyStore(db, file2);
|
|
23464
24906
|
db = openWithPragmas(file2);
|
|
23465
24907
|
akaWarn(
|
|
23466
24908
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23476,6 +24918,7 @@ function openAndInitialize(file2) {
|
|
|
23476
24918
|
policies,
|
|
23477
24919
|
installedPacks,
|
|
23478
24920
|
scanLedger: new SqliteScanLedgerRepository(db),
|
|
24921
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23479
24922
|
exceptions: new SqliteExceptionsRepository(db),
|
|
23480
24923
|
resolutions: new SqliteResolutionsRepository(db),
|
|
23481
24924
|
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
@@ -23503,7 +24946,7 @@ function openAndInitialize(file2) {
|
|
|
23503
24946
|
}
|
|
23504
24947
|
function openLocalDatabase(dir) {
|
|
23505
24948
|
ensureDataDirSync(dir);
|
|
23506
|
-
const file2 =
|
|
24949
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23507
24950
|
const {
|
|
23508
24951
|
db,
|
|
23509
24952
|
events,
|
|
@@ -23511,6 +24954,7 @@ function openLocalDatabase(dir) {
|
|
|
23511
24954
|
policies,
|
|
23512
24955
|
installedPacks,
|
|
23513
24956
|
scanLedger,
|
|
24957
|
+
secretVault,
|
|
23514
24958
|
exceptions,
|
|
23515
24959
|
resolutions,
|
|
23516
24960
|
ruleProbeCache,
|
|
@@ -23619,7 +25063,7 @@ function openLocalDatabase(dir) {
|
|
|
23619
25063
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23620
25064
|
if (!definitionId) continue;
|
|
23621
25065
|
inspectionFindings.insertFinding({
|
|
23622
|
-
id:
|
|
25066
|
+
id: randomUUID10(),
|
|
23623
25067
|
auditEventId: record2.scanEvent.id,
|
|
23624
25068
|
inspectionDefinitionId: definitionId,
|
|
23625
25069
|
span: finding.span,
|
|
@@ -23696,6 +25140,7 @@ function openLocalDatabase(dir) {
|
|
|
23696
25140
|
policies,
|
|
23697
25141
|
installedPacks,
|
|
23698
25142
|
scanLedger,
|
|
25143
|
+
secretVault,
|
|
23699
25144
|
exceptions,
|
|
23700
25145
|
resolutions,
|
|
23701
25146
|
ruleProbeCache,
|
|
@@ -23724,22 +25169,38 @@ function openLocalDatabase(dir) {
|
|
|
23724
25169
|
transaction,
|
|
23725
25170
|
close: () => {
|
|
23726
25171
|
db.close();
|
|
23727
|
-
}
|
|
25172
|
+
},
|
|
25173
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25174
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
23728
25175
|
};
|
|
23729
25176
|
}
|
|
23730
25177
|
|
|
25178
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25179
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25180
|
+
import {
|
|
25181
|
+
closeSync,
|
|
25182
|
+
existsSync as existsSync2,
|
|
25183
|
+
openSync,
|
|
25184
|
+
readFileSync,
|
|
25185
|
+
rmSync as rmSync3,
|
|
25186
|
+
statSync as statSync2,
|
|
25187
|
+
writeFileSync as writeFileSync2
|
|
25188
|
+
} from "fs";
|
|
25189
|
+
import { hostname as hostname3 } from "os";
|
|
25190
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25191
|
+
|
|
23731
25192
|
// ../../packages/persistence/src/finding-key.ts
|
|
23732
25193
|
import { createHash as createHash3 } from "crypto";
|
|
23733
25194
|
|
|
23734
25195
|
// ../../packages/persistence/src/fingerprint.ts
|
|
23735
25196
|
import { createHmac, randomBytes } from "crypto";
|
|
23736
|
-
import { existsSync as
|
|
23737
|
-
import { join as
|
|
25197
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25198
|
+
import { join as join3 } from "path";
|
|
23738
25199
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23739
|
-
var
|
|
25200
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
23740
25201
|
var KEY_MATERIAL_BYTES = 32;
|
|
23741
25202
|
function keyFilePath(dataDir2) {
|
|
23742
|
-
return
|
|
25203
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
23743
25204
|
}
|
|
23744
25205
|
function parseKeyFile(raw) {
|
|
23745
25206
|
const parsed = JSON.parse(raw);
|
|
@@ -23762,7 +25223,7 @@ function parseKeyFile(raw) {
|
|
|
23762
25223
|
function readFingerprintKey(dataDir2) {
|
|
23763
25224
|
let raw;
|
|
23764
25225
|
try {
|
|
23765
|
-
raw =
|
|
25226
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
23766
25227
|
} catch (err) {
|
|
23767
25228
|
if (err.code === "ENOENT") return null;
|
|
23768
25229
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -23774,18 +25235,18 @@ function readFingerprintKey(dataDir2) {
|
|
|
23774
25235
|
import { renameSync as renameSync3 } from "fs";
|
|
23775
25236
|
import { mkdir } from "fs/promises";
|
|
23776
25237
|
import { homedir } from "os";
|
|
23777
|
-
import { join as
|
|
25238
|
+
import { join as join4 } from "path";
|
|
23778
25239
|
function defaultDataDir() {
|
|
23779
|
-
return
|
|
25240
|
+
return join4(homedir(), ".aka");
|
|
23780
25241
|
}
|
|
23781
25242
|
function settingsDir(base = defaultDataDir()) {
|
|
23782
|
-
return
|
|
25243
|
+
return join4(base, "settings");
|
|
23783
25244
|
}
|
|
23784
25245
|
function dataDir(base = defaultDataDir()) {
|
|
23785
|
-
return
|
|
25246
|
+
return join4(base, "data");
|
|
23786
25247
|
}
|
|
23787
25248
|
function dbPath(base = defaultDataDir()) {
|
|
23788
|
-
return
|
|
25249
|
+
return join4(dataDir(base), "aka.db");
|
|
23789
25250
|
}
|
|
23790
25251
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
23791
25252
|
ensureDataDirSync(dir);
|
|
@@ -23798,8 +25259,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23798
25259
|
for (const { name, dest } of moves) {
|
|
23799
25260
|
try {
|
|
23800
25261
|
ensureDataDirSync(dest);
|
|
23801
|
-
const moved =
|
|
23802
|
-
renameSync3(
|
|
25262
|
+
const moved = join4(dest, name);
|
|
25263
|
+
renameSync3(join4(base, name), moved);
|
|
23803
25264
|
tightenFile(moved);
|
|
23804
25265
|
} catch {
|
|
23805
25266
|
}
|
|
@@ -23807,10 +25268,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23807
25268
|
}
|
|
23808
25269
|
|
|
23809
25270
|
// ../../packages/persistence/src/settings.ts
|
|
23810
|
-
import { readFileSync as
|
|
23811
|
-
import { join as
|
|
25271
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25272
|
+
import { join as join5 } from "path";
|
|
25273
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
23812
25274
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
23813
|
-
const record2 = readJson(
|
|
25275
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
23814
25276
|
if (!record2) return defaultWorkspaceSettings();
|
|
23815
25277
|
try {
|
|
23816
25278
|
return WorkspaceSettings.parse(record2);
|
|
@@ -23821,30 +25283,56 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
23821
25283
|
function readJson(file2) {
|
|
23822
25284
|
let text;
|
|
23823
25285
|
try {
|
|
23824
|
-
text =
|
|
25286
|
+
text = readFileSync3(file2, "utf8");
|
|
23825
25287
|
} catch {
|
|
23826
25288
|
return null;
|
|
23827
25289
|
}
|
|
23828
25290
|
return parseJsonObject(text) ?? null;
|
|
23829
25291
|
}
|
|
23830
25292
|
|
|
25293
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
25294
|
+
import {
|
|
25295
|
+
createCipheriv,
|
|
25296
|
+
createDecipheriv,
|
|
25297
|
+
createHmac as createHmac2,
|
|
25298
|
+
hkdfSync,
|
|
25299
|
+
timingSafeEqual
|
|
25300
|
+
} from "crypto";
|
|
25301
|
+
|
|
25302
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25303
|
+
import { execFileSync } from "child_process";
|
|
25304
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
25305
|
+
import {
|
|
25306
|
+
chmodSync as chmodSync2,
|
|
25307
|
+
mkdirSync as mkdirSync2,
|
|
25308
|
+
readFileSync as readFileSync4,
|
|
25309
|
+
renameSync as renameSync4,
|
|
25310
|
+
rmSync as rmSync4,
|
|
25311
|
+
statSync as statSync3,
|
|
25312
|
+
writeFileSync as writeFileSync3
|
|
25313
|
+
} from "fs";
|
|
25314
|
+
import { join as join6 } from "path";
|
|
25315
|
+
|
|
25316
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
25317
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
25318
|
+
|
|
23831
25319
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
23832
|
-
import { existsSync as
|
|
23833
|
-
import { join as
|
|
25320
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25321
|
+
import { join as join7 } from "path";
|
|
23834
25322
|
var MARKER = "warn-era-capped";
|
|
23835
25323
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
23836
25324
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
23837
|
-
const marker =
|
|
23838
|
-
if (
|
|
25325
|
+
const marker = join7(dataDir2, MARKER);
|
|
25326
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
23839
25327
|
const capped = db.policies.capCategoryActions();
|
|
23840
|
-
|
|
25328
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
23841
25329
|
`, { mode: DATA_FILE_MODE });
|
|
23842
25330
|
return { capped };
|
|
23843
25331
|
}
|
|
23844
25332
|
|
|
23845
25333
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23846
|
-
import { existsSync as
|
|
23847
|
-
import { join as
|
|
25334
|
+
import { existsSync as existsSync5 } from "fs";
|
|
25335
|
+
import { join as join8 } from "path";
|
|
23848
25336
|
|
|
23849
25337
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
23850
25338
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -23895,11 +25383,11 @@ function resolveProvider() {
|
|
|
23895
25383
|
}
|
|
23896
25384
|
|
|
23897
25385
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23898
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25386
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
23899
25387
|
try {
|
|
23900
25388
|
ensureLayoutDirSync(base);
|
|
23901
|
-
const settingsFile =
|
|
23902
|
-
if (
|
|
25389
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25390
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
23903
25391
|
} catch {
|
|
23904
25392
|
}
|
|
23905
25393
|
migrateLegacyLayout(base);
|
|
@@ -23910,21 +25398,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
23910
25398
|
dbPath: dbPath(base),
|
|
23911
25399
|
settingsDir: settingsDir(base),
|
|
23912
25400
|
onboarded: settings.onboardedAt != null,
|
|
23913
|
-
provider: resolveProviderSafe()
|
|
25401
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
23914
25402
|
};
|
|
23915
25403
|
}
|
|
23916
|
-
function resolveProviderSafe() {
|
|
25404
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
23917
25405
|
try {
|
|
23918
|
-
return
|
|
25406
|
+
return resolveProviderFn();
|
|
23919
25407
|
} catch {
|
|
23920
25408
|
return { provider: "anthropic" };
|
|
23921
25409
|
}
|
|
23922
25410
|
}
|
|
23923
25411
|
|
|
23924
25412
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23925
|
-
import { readdirSync, readFileSync as
|
|
25413
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
23926
25414
|
import { homedir as homedir2 } from "os";
|
|
23927
|
-
import { basename as
|
|
25415
|
+
import { basename as basename3, join as join10 } from "path";
|
|
23928
25416
|
|
|
23929
25417
|
// ../../packages/detections/src/egress/registry.ts
|
|
23930
25418
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -26288,7 +27776,7 @@ var gcp_service_account_default = {
|
|
|
26288
27776
|
severity: "critical",
|
|
26289
27777
|
matcher: {
|
|
26290
27778
|
type: "regex",
|
|
26291
|
-
pattern: "
|
|
27779
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
26292
27780
|
flags: "g"
|
|
26293
27781
|
},
|
|
26294
27782
|
examples: [
|
|
@@ -26660,40 +28148,71 @@ function bundledDetections() {
|
|
|
26660
28148
|
}
|
|
26661
28149
|
|
|
26662
28150
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
26663
|
-
import { existsSync as
|
|
26664
|
-
import { basename, dirname, isAbsolute, join as
|
|
28151
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
28152
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
26665
28153
|
|
|
26666
28154
|
// ../../packages/plugin-sdk/src/events.ts
|
|
26667
|
-
import { createHash as createHash4, randomUUID as
|
|
28155
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
28156
|
+
|
|
28157
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
28158
|
+
import { existsSync as existsSync7 } from "fs";
|
|
28159
|
+
import { fileURLToPath } from "url";
|
|
28160
|
+
import { Worker } from "worker_threads";
|
|
26668
28161
|
|
|
26669
28162
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
26670
|
-
import { arch, hostname as
|
|
28163
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
26671
28164
|
|
|
26672
28165
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
26673
|
-
import { mkdirSync as
|
|
26674
|
-
import { join as
|
|
28166
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
28167
|
+
import { join as join11 } from "path";
|
|
26675
28168
|
|
|
26676
28169
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
26677
|
-
import { readdirSync as
|
|
26678
|
-
import { basename as
|
|
28170
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
28171
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
26679
28172
|
|
|
26680
28173
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
26681
28174
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
26682
|
-
import { existsSync as
|
|
26683
|
-
import { basename as
|
|
28175
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
28176
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
28177
|
+
|
|
28178
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
28179
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
28180
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28181
|
+
return v;
|
|
28182
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28183
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
28184
|
+
if (typeof v !== "string") return false;
|
|
28185
|
+
const normalized = v.trim().toLowerCase();
|
|
28186
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
28187
|
+
}, external_exports.boolean()).catch(false);
|
|
28188
|
+
var antigravityProviderEnvShape = {
|
|
28189
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
28190
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
28191
|
+
};
|
|
28192
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
28193
|
+
|
|
28194
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
28195
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
28196
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
28197
|
+
return v;
|
|
28198
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
28199
|
+
var codexProviderEnvShape = {
|
|
28200
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
28201
|
+
};
|
|
28202
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
26684
28203
|
|
|
26685
28204
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
26686
|
-
import { randomUUID as
|
|
28205
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
26687
28206
|
|
|
26688
28207
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
26689
28208
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
26690
28209
|
|
|
26691
28210
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
26692
|
-
import { mkdirSync as
|
|
26693
|
-
import { join as
|
|
28211
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
28212
|
+
import { join as join13 } from "path";
|
|
26694
28213
|
|
|
26695
28214
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
26696
|
-
import { randomUUID as
|
|
28215
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
26697
28216
|
|
|
26698
28217
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
26699
28218
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -26855,7 +28374,7 @@ var StandaloneDataGateway = class {
|
|
|
26855
28374
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
26856
28375
|
const installed = this.installedScanRules();
|
|
26857
28376
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
26858
|
-
id:
|
|
28377
|
+
id: randomUUID15(),
|
|
26859
28378
|
scope: "global",
|
|
26860
28379
|
target: { ruleId },
|
|
26861
28380
|
action,
|
|
@@ -27008,7 +28527,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
27008
28527
|
}
|
|
27009
28528
|
|
|
27010
28529
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
27011
|
-
import { randomUUID as
|
|
28530
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
27012
28531
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
27013
28532
|
|
|
27014
28533
|
// src/present.ts
|
|
@@ -27127,10 +28646,58 @@ function fenced(body) {
|
|
|
27127
28646
|
return [fence, body, fence].join("\n");
|
|
27128
28647
|
}
|
|
27129
28648
|
|
|
28649
|
+
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
28650
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
28651
|
+
import { join as join14 } from "path";
|
|
28652
|
+
|
|
28653
|
+
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
28654
|
+
import { mkdtempSync, readFileSync as readFileSync9, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
28655
|
+
import { tmpdir } from "os";
|
|
28656
|
+
import { basename as basename6, dirname as dirname4, join as join15 } from "path";
|
|
28657
|
+
var SuppressionEntrySchema = external_exports.object({
|
|
28658
|
+
ruleId: external_exports.string(),
|
|
28659
|
+
category: DetectionCategory,
|
|
28660
|
+
valueFingerprint: external_exports.string(),
|
|
28661
|
+
keyVersion: external_exports.number(),
|
|
28662
|
+
maskedValue: external_exports.string(),
|
|
28663
|
+
justification: external_exports.string()
|
|
28664
|
+
});
|
|
28665
|
+
var ShowcaseCategorySchema = external_exports.object({
|
|
28666
|
+
category: DetectionCategory,
|
|
28667
|
+
action: BuiltinPolicyId,
|
|
28668
|
+
genuineCount: external_exports.number(),
|
|
28669
|
+
fpCount: external_exports.number(),
|
|
28670
|
+
reasoning: external_exports.string()
|
|
28671
|
+
});
|
|
28672
|
+
var JoinEntrySchema = external_exports.object({
|
|
28673
|
+
id: external_exports.string(),
|
|
28674
|
+
ruleId: external_exports.string(),
|
|
28675
|
+
category: DetectionCategory,
|
|
28676
|
+
valueFingerprint: external_exports.string().optional(),
|
|
28677
|
+
keyVersion: external_exports.number().optional(),
|
|
28678
|
+
maskedMatch: external_exports.string(),
|
|
28679
|
+
maskedContext: external_exports.string()
|
|
28680
|
+
});
|
|
28681
|
+
var PLAN_FILE_VERSION = 3;
|
|
28682
|
+
var PersistedPlanSchema = external_exports.object({
|
|
28683
|
+
version: external_exports.literal(PLAN_FILE_VERSION),
|
|
28684
|
+
// partialRecord (not record): a posture only covers the categories present in
|
|
28685
|
+
// the evidence, so an exhaustive-key record would reject every real plan.
|
|
28686
|
+
posture: external_exports.partialRecord(DetectionCategory, BuiltinPolicyId),
|
|
28687
|
+
entries: external_exports.array(SuppressionEntrySchema),
|
|
28688
|
+
showcase: external_exports.array(ShowcaseCategorySchema),
|
|
28689
|
+
join: external_exports.array(JoinEntrySchema),
|
|
28690
|
+
notes: external_exports.string(),
|
|
28691
|
+
// The store's per-category action at preview time. The downgrade view is
|
|
28692
|
+
// rendered from it at PREVIEW (renderPosturePlan); confirm reads it back ONLY to
|
|
28693
|
+
// compare against the live store and reject a stale plan (runConfirm's drift gate).
|
|
28694
|
+
current: external_exports.partialRecord(DetectionCategory, ActionTaken)
|
|
28695
|
+
});
|
|
28696
|
+
|
|
27130
28697
|
// src/command-registry.ts
|
|
27131
|
-
import { readdirSync as
|
|
27132
|
-
import { fileURLToPath } from "url";
|
|
27133
|
-
var COMMANDS_DIR =
|
|
28698
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
28699
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
28700
|
+
var COMMANDS_DIR = fileURLToPath2(new URL("../commands", import.meta.url));
|
|
27134
28701
|
|
|
27135
28702
|
// src/render.ts
|
|
27136
28703
|
var SEVERITY_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
@@ -27217,8 +28784,8 @@ function renderStatusBar(s, opts = {}) {
|
|
|
27217
28784
|
const score = `${dot} health ${paint.bold(String(s.score))}${paint.dim("/100")}`;
|
|
27218
28785
|
const tally = `${paint.dim("unreviewed")} ${paint.critical(sq)}${String(u.critical)} ${paint.high(sq)}${String(u.high)} ${paint.medium(sq)}${String(u.medium)} ${paint.low(sq)}${String(u.low)}`;
|
|
27219
28786
|
const flag = s.openFindings > 0 ? paint.critical("\u2691") : paint.dim("\u2691");
|
|
27220
|
-
const
|
|
27221
|
-
return `${paint.brand("\u25B8\u25B8 AKA")}${sep5}${score}${sep5}${tally}${sep5}${
|
|
28787
|
+
const open2 = `${flag} ${String(s.openFindings)} open findings`;
|
|
28788
|
+
return `${paint.brand("\u25B8\u25B8 AKA")}${sep5}${score}${sep5}${tally}${sep5}${open2}`;
|
|
27222
28789
|
}
|
|
27223
28790
|
function findingStatus(summary) {
|
|
27224
28791
|
return {
|
|
@@ -27444,7 +29011,11 @@ function renderExceptions(exceptions, nowMs = Date.now()) {
|
|
|
27444
29011
|
}
|
|
27445
29012
|
const rows = exceptions.map((e) => [
|
|
27446
29013
|
e.id.slice(0, 8),
|
|
27447
|
-
|
|
29014
|
+
// A reveal grant is strictly stronger than a plain suppression: while it is
|
|
29015
|
+
// active the model can receive this value's RAW form at tool boundaries.
|
|
29016
|
+
// Tag the row so it can never be mistaken for a suppress-only grant. The
|
|
29017
|
+
// value itself stays masked — this list shows metadata, never raw values.
|
|
29018
|
+
e.capability === "reveal_to_model" ? `${e.maskedValue} \xB7 REVEALS-TO-MODEL` : e.maskedValue,
|
|
27448
29019
|
e.ruleId,
|
|
27449
29020
|
e.scope,
|
|
27450
29021
|
relativeExpiry(e.expiresAt, nowMs),
|