@akasecurity/ai-tc-claude-code 0.9.2 → 0.9.4

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.
@@ -492,11 +492,11 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // ../../packages/plugin-sdk/src/config.ts
495
- import { existsSync as existsSync3 } from "fs";
496
- import { join as join6 } from "path";
495
+ import { existsSync as existsSync4 } from "fs";
496
+ import { join as join7 } from "path";
497
497
 
498
498
  // ../../packages/persistence/src/database.ts
499
- import { randomUUID as randomUUID8 } from "crypto";
499
+ import { randomUUID as randomUUID9 } from "crypto";
500
500
  import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
501
501
  import { join, sep } from "path";
502
502
  import { DatabaseSync } from "node:sqlite";
@@ -562,6 +562,22 @@ var SQLITE_MIGRATIONS = [
562
562
  {
563
563
  tag: "0014_drop_legacy_events_findings",
564
564
  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"
565
+ },
566
+ {
567
+ tag: "0015_busy_vengeance",
568
+ 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`);"
569
+ },
570
+ {
571
+ tag: "0016_breezy_zodiak",
572
+ sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
573
+ },
574
+ {
575
+ tag: "0017_rainy_kat_farrell",
576
+ 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`);"
577
+ },
578
+ {
579
+ tag: "0018_serious_tana_nile",
580
+ sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
565
581
  }
566
582
  ];
567
583
 
@@ -16218,6 +16234,7 @@ var ExceptionConditions = external_exports.object({
16218
16234
  sourceTool: external_exports.string().optional(),
16219
16235
  provider: external_exports.string().optional()
16220
16236
  }).strict();
16237
+ var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
16221
16238
  var DetectionException = external_exports.object({
16222
16239
  id: external_exports.guid(),
16223
16240
  ruleId: external_exports.string(),
@@ -16234,6 +16251,7 @@ var DetectionException = external_exports.object({
16234
16251
  keyVersion: external_exports.number().int().positive(),
16235
16252
  // maskMatch() preview of the approved value — never the raw value.
16236
16253
  maskedValue: external_exports.string(),
16254
+ capability: ExceptionCapability.default("suppress"),
16237
16255
  scope: ExceptionScope,
16238
16256
  expiresAt: external_exports.iso.datetime().nullable(),
16239
16257
  maxUses: external_exports.number().int().positive().nullable(),
@@ -16257,6 +16275,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16257
16275
  ruleId: true,
16258
16276
  valueFingerprint: true,
16259
16277
  keyVersion: true,
16278
+ capability: true,
16260
16279
  expiresAt: true,
16261
16280
  maxUses: true,
16262
16281
  useCount: true,
@@ -17361,8 +17380,117 @@ var PatchInstalledPackRequest = external_exports.object({
17361
17380
  message: "At least one field must be provided"
17362
17381
  }).meta({ id: "PatchInstalledPackRequest" });
17363
17382
 
17383
+ // ../../packages/schema/src/zod/vault.ts
17384
+ var POINTER_FORMAT_VERSION = 2;
17385
+ var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
17386
+ var POINTER_TOKEN_PATTERN = new RegExp(
17387
+ `\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
17388
+ );
17389
+ var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
17390
+ var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
17391
+ var ParsedPointer = external_exports.object({
17392
+ category: DetectionCategory,
17393
+ keyVersion: external_exports.number().int().positive(),
17394
+ pointerId: external_exports.string(),
17395
+ tag: external_exports.string()
17396
+ });
17397
+ var VaultEntry = external_exports.object({
17398
+ pointerId: external_exports.string(),
17399
+ // The keyed HMAC of the raw value under `exception.key`, and the epoch it was
17400
+ // derived under. This is what a reveal-to-model grant matches on, and it rotates
17401
+ // independently of the vault encryption key below.
17402
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17403
+ fingerprintKeyVersion: external_exports.number().int().positive(),
17404
+ // The vault-key epoch this row's ciphertext was sealed under.
17405
+ keyVersion: external_exports.number().int().positive(),
17406
+ // Fixed at first mint and never updated: the same value detected later under a
17407
+ // different rule's category keeps the category it was minted with, so one
17408
+ // value always produces exactly one wire token.
17409
+ category: DetectionCategory,
17410
+ ruleId: external_exports.string(),
17411
+ // Partial-reveal preview for badges and listings. Never the raw value.
17412
+ maskedMatch: external_exports.string(),
17413
+ provider: external_exports.string().optional(),
17414
+ ciphertext: external_exports.string(),
17415
+ nonce: external_exports.string(),
17416
+ authTag: external_exports.string(),
17417
+ // How many times this value has been detected on this machine — the reuse
17418
+ // signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
17419
+ occurrenceCount: external_exports.number().int().nonnegative(),
17420
+ firstSeen: external_exports.string(),
17421
+ lastSeen: external_exports.string()
17422
+ });
17423
+ var PointerDescriptor = external_exports.object({
17424
+ category: DetectionCategory,
17425
+ provider: external_exports.string().optional(),
17426
+ maskedMatch: external_exports.string(),
17427
+ occurrences: external_exports.number().int().nonnegative(),
17428
+ firstSeen: external_exports.string(),
17429
+ lastSeen: external_exports.string()
17430
+ });
17431
+ var PointerIdentity = external_exports.object({
17432
+ ruleId: external_exports.string(),
17433
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17434
+ fingerprintKeyVersion: external_exports.number().int().positive()
17435
+ });
17436
+ var DetokenizeTarget = external_exports.enum(["human", "model"]);
17437
+ var VaultDerefReason = external_exports.enum([
17438
+ "display",
17439
+ "explicit-reveal",
17440
+ "view-render",
17441
+ "model-input",
17442
+ "remediation",
17443
+ "purge"
17444
+ ]);
17445
+ var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
17446
+ var VaultDeref = external_exports.object({
17447
+ id: external_exports.guid(),
17448
+ pointerId: external_exports.string(),
17449
+ at: external_exports.string(),
17450
+ target: DetokenizeTarget,
17451
+ reason: VaultDerefReason,
17452
+ outcome: VaultDerefOutcome,
17453
+ // Present only on a model-target crossing that a reveal grant authorized.
17454
+ grantId: external_exports.string().optional(),
17455
+ // How many pointers ONE batched render resolved. 1 for unbatched rows. Named
17456
+ // apart from VaultEntry.occurrenceCount, which counts detections of a value.
17457
+ pointerCount: external_exports.number().int().positive().default(1)
17458
+ });
17459
+ var VaultSightingKind = external_exports.enum([
17460
+ "prompt",
17461
+ "tool-input",
17462
+ "tool-output",
17463
+ "file",
17464
+ "transcript"
17465
+ ]);
17466
+ var VaultSighting = external_exports.object({
17467
+ location: external_exports.string(),
17468
+ kind: VaultSightingKind,
17469
+ firstSeen: external_exports.string(),
17470
+ lastSeen: external_exports.string()
17471
+ });
17472
+ var VaultInventoryEntry = external_exports.object({
17473
+ pointerId: external_exports.string(),
17474
+ category: DetectionCategory,
17475
+ provider: external_exports.string().optional(),
17476
+ maskedMatch: external_exports.string(),
17477
+ occurrences: external_exports.number().int().nonnegative(),
17478
+ firstSeen: external_exports.string(),
17479
+ lastSeen: external_exports.string(),
17480
+ // The active reveal-to-model grant covering this value, when one exists —
17481
+ // the inventory badges it, the row links to revocation.
17482
+ revealGrantId: external_exports.string().nullable(),
17483
+ sightings: external_exports.array(VaultSighting)
17484
+ });
17485
+ var VaultKeyCustody = external_exports.string();
17486
+ var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17487
+ var VaultConsent = external_exports.object({
17488
+ acknowledgedAt: external_exports.iso.datetime(),
17489
+ version: external_exports.number().int().positive()
17490
+ });
17491
+
17364
17492
  // ../../packages/schema/src/zod/local.ts
17365
- var WORKSPACE_SETTINGS_SPEC_VERSION = 4;
17493
+ var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17366
17494
  var RunMode = external_exports.enum(["standalone"]);
17367
17495
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17368
17496
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
@@ -17384,6 +17512,16 @@ var WorkspaceSettings = external_exports.object({
17384
17512
  // In-place egress extraction on the scan paths; disable to stop all Data
17385
17513
  // Shares writes.
17386
17514
  dataSharesInPlace: external_exports.boolean().default(true),
17515
+ // Consent to keep a RECOVERABLE encrypted copy of detected values in the local
17516
+ // vault, instead of destroying them. Absent by default: this is a custody
17517
+ // change from one-way redaction, so it is never an assumed grant on upgrade.
17518
+ // Revoking stops future vaulting; it does not erase what is already stored —
17519
+ // purging the vault is the eraser.
17520
+ vaultConsent: VaultConsent.optional(),
17521
+ // Where the vault master key lives.
17522
+ vaultKeyCustody: VaultKeyCustody.default("file"),
17523
+ // How a pointer renders in assistant prose on screen (see VaultInlineReveal).
17524
+ vaultInlineReveal: VaultInlineReveal.default("masked"),
17387
17525
  // Absent until /aka:setup completes; its presence is what "onboarded" means.
17388
17526
  onboardedAt: external_exports.iso.datetime().optional(),
17389
17527
  // Records that the user consented to sending findings to the model API for
@@ -19781,6 +19919,9 @@ var AmbiguousExceptionIdError = class extends Error {
19781
19919
  var ACTIVE_PREDICATE = `revoked_at IS NULL
19782
19920
  AND (expires_at IS NULL OR expires_at > :now)
19783
19921
  AND (max_uses IS NULL OR use_count < max_uses)`;
19922
+ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19923
+ AND conditions IS NULL
19924
+ AND ${ACTIVE_PREDICATE}`;
19784
19925
  var SqliteExceptionsRepository = class {
19785
19926
  constructor(db) {
19786
19927
  this.db = db;
@@ -19872,11 +20013,11 @@ var SqliteExceptionsRepository = class {
19872
20013
  this.db.prepare(
19873
20014
  `INSERT INTO exceptions (
19874
20015
  id, rule_id, category, value_fingerprint, key_version, masked_value,
19875
- scope, expires_at, max_uses, use_count, last_used_at, justification,
19876
- conditions, created_by, created_via, created_at, updated_at
20016
+ capability, scope, expires_at, max_uses, use_count, last_used_at,
20017
+ justification, conditions, created_by, created_via, created_at, updated_at
19877
20018
  ) VALUES (
19878
20019
  :id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
19879
- :scope, :expiresAt, :maxUses, 0, NULL, :justification,
20020
+ :capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
19880
20021
  :conditions, :createdBy, :createdVia, :now, :now
19881
20022
  )`
19882
20023
  ).run({
@@ -19886,6 +20027,7 @@ var SqliteExceptionsRepository = class {
19886
20027
  valueFingerprint: input.valueFingerprint,
19887
20028
  keyVersion: input.keyVersion,
19888
20029
  maskedValue: input.maskedValue,
20030
+ capability: input.capability ?? "suppress",
19889
20031
  scope: input.scope,
19890
20032
  expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
19891
20033
  maxUses: input.maxUses,
@@ -19979,6 +20121,7 @@ var SqliteExceptionsRepository = class {
19979
20121
  ruleId: row.rule_id,
19980
20122
  valueFingerprint: row.value_fingerprint,
19981
20123
  keyVersion: row.key_version,
20124
+ capability: row.capability,
19982
20125
  expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
19983
20126
  maxUses: row.max_uses,
19984
20127
  useCount: row.use_count,
@@ -20033,6 +20176,35 @@ var SqliteExceptionsRepository = class {
20033
20176
  }))
20034
20177
  );
20035
20178
  }
20179
+ /**
20180
+ * The active reveal-to-model grant for a vaulted value's identity, or null.
20181
+ * Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
20182
+ * suppression uses — plus the capability: a suppression grant must never
20183
+ * authorize a reveal. Read-only: the caller does NOT consume here, because a
20184
+ * revealed value re-enters the detection scan immediately afterward and the
20185
+ * suppression match there claims the use — one crossing, one use.
20186
+ *
20187
+ * A grant with `conditions` NEVER matches here: the reveal path does not yet
20188
+ * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20189
+ * grant instead. Fail closed until reveal-side condition evaluation exists.
20190
+ */
20191
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20192
+ try {
20193
+ const row = getRow(
20194
+ this.db.prepare(
20195
+ `SELECT id FROM exceptions
20196
+ WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
20197
+ AND key_version = :keyVersion
20198
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20199
+ LIMIT 1`
20200
+ ),
20201
+ { ruleId, valueFingerprint, keyVersion, now }
20202
+ );
20203
+ return Promise.resolve(row ?? null);
20204
+ } catch (err) {
20205
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
20206
+ }
20207
+ }
20036
20208
  /**
20037
20209
  * Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
20038
20210
  * exhausted) whose last transition is older than the retention window.
@@ -20060,6 +20232,7 @@ function parseExceptionRow(row) {
20060
20232
  valueFingerprint: row.value_fingerprint,
20061
20233
  keyVersion: row.key_version,
20062
20234
  maskedValue: row.masked_value,
20235
+ capability: row.capability,
20063
20236
  scope: row.scope,
20064
20237
  expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
20065
20238
  maxUses: row.max_uses,
@@ -22225,6 +22398,287 @@ var SqliteScanLedgerRepository = class {
22225
22398
  }
22226
22399
  };
22227
22400
 
22401
+ // ../../packages/persistence/src/repositories/secret-vault.ts
22402
+ import { randomUUID as randomUUID7 } from "crypto";
22403
+ var SELECT_COLUMNS = `
22404
+ pointer_id AS pointerId,
22405
+ value_fingerprint AS valueFingerprint,
22406
+ fingerprint_key_version AS fingerprintKeyVersion,
22407
+ key_version AS keyVersion,
22408
+ format_version AS formatVersion,
22409
+ category,
22410
+ rule_id AS ruleId,
22411
+ masked_match AS maskedMatch,
22412
+ provider,
22413
+ ciphertext,
22414
+ nonce,
22415
+ auth_tag AS authTag,
22416
+ occurrence_count AS occurrenceCount,
22417
+ first_seen AS firstSeen,
22418
+ last_seen AS lastSeen`;
22419
+ function toRow(raw) {
22420
+ const { provider, ...rest } = raw;
22421
+ return provider === null ? rest : { ...rest, provider };
22422
+ }
22423
+ var SqliteSecretVaultRepository = class {
22424
+ constructor(db) {
22425
+ this.db = db;
22426
+ this.insertStmt = db.prepare(
22427
+ `INSERT INTO secret_vault (
22428
+ pointer_id, value_fingerprint, fingerprint_key_version, key_version,
22429
+ format_version, category, rule_id, masked_match, provider,
22430
+ ciphertext, nonce, auth_tag,
22431
+ occurrence_count, first_seen, last_seen
22432
+ ) VALUES (
22433
+ :pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
22434
+ :formatVersion, :category, :ruleId, :maskedMatch, :provider,
22435
+ :ciphertext, :nonce, :authTag,
22436
+ 1, :now, :now
22437
+ )`
22438
+ );
22439
+ this.bumpStmt = db.prepare(
22440
+ `UPDATE secret_vault
22441
+ SET occurrence_count = occurrence_count + 1, last_seen = :now
22442
+ WHERE value_fingerprint = :valueFingerprint`
22443
+ );
22444
+ this.byPointerStmt = db.prepare(
22445
+ `SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
22446
+ );
22447
+ this.byFingerprintStmt = db.prepare(
22448
+ `SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
22449
+ );
22450
+ this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
22451
+ this.replaceCiphertextStmt = db.prepare(
22452
+ `UPDATE secret_vault
22453
+ SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
22454
+ WHERE pointer_id = :pointerId`
22455
+ );
22456
+ this.refreshFingerprintStmt = db.prepare(
22457
+ `UPDATE secret_vault
22458
+ SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
22459
+ WHERE pointer_id = :pointerId`
22460
+ );
22461
+ this.derefStmt = db.prepare(
22462
+ `INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
22463
+ VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
22464
+ );
22465
+ }
22466
+ db;
22467
+ insertStmt;
22468
+ bumpStmt;
22469
+ byPointerStmt;
22470
+ byFingerprintStmt;
22471
+ listStmt;
22472
+ replaceCiphertextStmt;
22473
+ refreshFingerprintStmt;
22474
+ derefStmt;
22475
+ /**
22476
+ * Vault a value, or record another sighting of one already vaulted. Keyed on
22477
+ * `valueFingerprint`, never on the caller's pointer id: a value seen again
22478
+ * bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
22479
+ * pointer, category and ciphertext, so the same secret always resolves to one
22480
+ * wire token. `minted` is true only when this call created the row.
22481
+ *
22482
+ * The read-then-write runs in one IMMEDIATE transaction so two concurrent
22483
+ * writers cannot both decide they are minting.
22484
+ */
22485
+ upsert(input, now) {
22486
+ let minted = false;
22487
+ withTransaction(
22488
+ this.db,
22489
+ () => {
22490
+ const existing = getRow(this.byFingerprintStmt, {
22491
+ valueFingerprint: input.valueFingerprint
22492
+ });
22493
+ if (existing === void 0) {
22494
+ this.insertStmt.run(
22495
+ bindParams({
22496
+ pointerId: input.pointerId,
22497
+ valueFingerprint: input.valueFingerprint,
22498
+ fingerprintKeyVersion: input.fingerprintKeyVersion,
22499
+ keyVersion: input.keyVersion,
22500
+ formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
22501
+ category: input.category,
22502
+ ruleId: input.ruleId,
22503
+ maskedMatch: input.maskedMatch,
22504
+ provider: input.provider,
22505
+ ciphertext: input.ciphertext,
22506
+ nonce: input.nonce,
22507
+ authTag: input.authTag,
22508
+ now
22509
+ })
22510
+ );
22511
+ minted = true;
22512
+ return;
22513
+ }
22514
+ this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
22515
+ },
22516
+ "IMMEDIATE"
22517
+ );
22518
+ const row = getRow(this.byFingerprintStmt, {
22519
+ valueFingerprint: input.valueFingerprint
22520
+ });
22521
+ if (row === void 0) throw new Error("vault: row vanished immediately after write");
22522
+ return { row: toRow(row), minted };
22523
+ }
22524
+ byPointerId(pointerId) {
22525
+ const raw = getRow(this.byPointerStmt, { pointerId });
22526
+ return raw === void 0 ? null : toRow(raw);
22527
+ }
22528
+ byValueFingerprint(fingerprint) {
22529
+ const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
22530
+ return raw === void 0 ? null : toRow(raw);
22531
+ }
22532
+ /** Append one audit row. Carries no raw value and no ciphertext, by shape. */
22533
+ recordDeref(entry) {
22534
+ this.derefStmt.run(
22535
+ bindParams({
22536
+ id: entry.id,
22537
+ pointerId: entry.pointerId,
22538
+ at: entry.at,
22539
+ target: entry.target,
22540
+ reason: entry.reason,
22541
+ outcome: entry.outcome,
22542
+ grantId: entry.grantId,
22543
+ pointerCount: entry.pointerCount ?? 1
22544
+ })
22545
+ );
22546
+ }
22547
+ listAll() {
22548
+ return allRows(this.listStmt).map(toRow);
22549
+ }
22550
+ /** Re-seal an entry under a new key epoch, leaving its identity untouched. */
22551
+ replaceCiphertext(pointerId, next) {
22552
+ this.replaceCiphertextStmt.run({ pointerId, ...next });
22553
+ }
22554
+ /** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
22555
+ refreshFingerprint(pointerId, next) {
22556
+ this.refreshFingerprintStmt.run({ pointerId, ...next });
22557
+ }
22558
+ /**
22559
+ * Destroy every vaulted value and report how many were destroyed. The deref
22560
+ * audit is left alone on purpose — see the table note above.
22561
+ */
22562
+ purgeAll() {
22563
+ let destroyed = 0;
22564
+ withTransaction(
22565
+ this.db,
22566
+ () => {
22567
+ destroyed = this.countEntries();
22568
+ this.db.exec("DELETE FROM secret_vault");
22569
+ },
22570
+ "IMMEDIATE"
22571
+ );
22572
+ return destroyed;
22573
+ }
22574
+ /**
22575
+ * Record (or re-stamp) one place a pointer has been written. One row per
22576
+ * (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
22577
+ * on hook paths — a failure must never affect the rewrite that triggered it,
22578
+ * so callers wrap this, not the other way around.
22579
+ */
22580
+ recordSighting(entry, now) {
22581
+ this.db.prepare(
22582
+ `INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
22583
+ VALUES (:id, :pointerId, :location, :kind, :now, :now)
22584
+ ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22585
+ ).run({
22586
+ id: randomUUID7(),
22587
+ pointerId: entry.pointerId,
22588
+ location: entry.location,
22589
+ kind: entry.kind,
22590
+ now
22591
+ });
22592
+ }
22593
+ listSightings(pointerId) {
22594
+ const rows = allRows(
22595
+ this.db.prepare(
22596
+ `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22597
+ WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
22598
+ ),
22599
+ { pointerId }
22600
+ );
22601
+ return rows.map((r) => ({
22602
+ location: r.location,
22603
+ kind: r.kind,
22604
+ firstSeen: new Date(r.first_seen).toISOString(),
22605
+ lastSeen: new Date(r.last_seen).toISOString()
22606
+ }));
22607
+ }
22608
+ /**
22609
+ * The dashboard inventory: every vaulted value's descriptor data joined with
22610
+ * its sightings and the active reveal-to-model grant when one exists.
22611
+ * Raw-free by construction — neither the fingerprint nor the ciphertext
22612
+ * columns are selected.
22613
+ */
22614
+ listInventory(now = Date.now()) {
22615
+ const rows = allRows(
22616
+ this.db.prepare(
22617
+ `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22618
+ v.occurrence_count, v.first_seen, v.last_seen,
22619
+ (SELECT e.id FROM exceptions e
22620
+ WHERE e.rule_id = v.rule_id
22621
+ AND e.value_fingerprint = v.value_fingerprint
22622
+ AND e.key_version = v.fingerprint_key_version
22623
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22624
+ LIMIT 1) AS grant_id
22625
+ FROM secret_vault v
22626
+ ORDER BY v.last_seen DESC`
22627
+ ),
22628
+ { now }
22629
+ );
22630
+ return rows.map((r) => ({
22631
+ pointerId: r.pointer_id,
22632
+ category: r.category,
22633
+ ...r.provider === null ? {} : { provider: r.provider },
22634
+ maskedMatch: r.masked_match,
22635
+ occurrences: r.occurrence_count,
22636
+ firstSeen: new Date(r.first_seen).toISOString(),
22637
+ lastSeen: new Date(r.last_seen).toISOString(),
22638
+ revealGrantId: r.grant_id,
22639
+ sightings: this.listSightings(r.pointer_id)
22640
+ }));
22641
+ }
22642
+ /**
22643
+ * The de-reference trail, newest first. By default the batched, high-volume
22644
+ * reasons (display, view-render) are hidden and counted instead — the rows
22645
+ * that matter as a signal are the model crossings, and burying them under
22646
+ * render noise would defeat the audit's purpose.
22647
+ */
22648
+ listDerefs(opts) {
22649
+ const limit = opts?.limit ?? 200;
22650
+ const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
22651
+ const rows = allRows(
22652
+ this.db.prepare(
22653
+ `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22654
+ FROM secret_vault_deref ${where}
22655
+ ORDER BY at DESC, rowid DESC LIMIT :limit`
22656
+ ),
22657
+ { limit }
22658
+ );
22659
+ const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
22660
+ this.db,
22661
+ `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22662
+ );
22663
+ return {
22664
+ rows: rows.map((r) => ({
22665
+ id: r.id,
22666
+ pointerId: r.pointer_id,
22667
+ at: new Date(r.at).toISOString(),
22668
+ target: r.target,
22669
+ reason: r.reason,
22670
+ outcome: r.outcome,
22671
+ ...r.grant_id === null ? {} : { grantId: r.grant_id },
22672
+ pointerCount: r.pointer_count
22673
+ })),
22674
+ hiddenBatched
22675
+ };
22676
+ }
22677
+ countEntries() {
22678
+ return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22679
+ }
22680
+ };
22681
+
22228
22682
  // ../../packages/persistence/src/repositories/security.ts
22229
22683
  var DAY_MS4 = 864e5;
22230
22684
  var SEVERITIES = ["critical", "high", "medium", "low"];
@@ -22570,7 +23024,7 @@ var SqliteSecurityRepository = class {
22570
23024
  };
22571
23025
 
22572
23026
  // ../../packages/persistence/src/repositories/shares.ts
22573
- import { randomUUID as randomUUID7 } from "crypto";
23027
+ import { randomUUID as randomUUID8 } from "crypto";
22574
23028
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
22575
23029
  var IN_CHUNK = 500;
22576
23030
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -22826,7 +23280,7 @@ var SqliteSharesRepository = class {
22826
23280
  (id, destination_id, host, decision, created_at, updated_at)
22827
23281
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
22828
23282
  ).run({
22829
- id: randomUUID7(),
23283
+ id: randomUUID8(),
22830
23284
  destinationId,
22831
23285
  host: dest.host,
22832
23286
  decision,
@@ -22975,7 +23429,7 @@ var SqliteSharesRepository = class {
22975
23429
  let destinationId = destIds.get(hit.host);
22976
23430
  if (destinationId === void 0) {
22977
23431
  destStmt.run({
22978
- id: randomUUID7(),
23432
+ id: randomUUID8(),
22979
23433
  kind: hit.kind,
22980
23434
  name: hit.name,
22981
23435
  host: hit.host,
@@ -22991,7 +23445,7 @@ var SqliteSharesRepository = class {
22991
23445
  let endpointId = endpointIds.get(endpointKey);
22992
23446
  if (endpointId === void 0) {
22993
23447
  endpointStmt.run({
22994
- id: randomUUID7(),
23448
+ id: randomUUID8(),
22995
23449
  destinationId,
22996
23450
  method: hit.method,
22997
23451
  transport: hit.transport,
@@ -23004,7 +23458,7 @@ var SqliteSharesRepository = class {
23004
23458
  endpointIds.set(endpointKey, endpointId);
23005
23459
  }
23006
23460
  siteStmt.run({
23007
- id: randomUUID7(),
23461
+ id: randomUUID8(),
23008
23462
  endpointId,
23009
23463
  project: input.project,
23010
23464
  projectKey: input.projectKey,
@@ -23372,11 +23826,22 @@ function purgeSampleData(db) {
23372
23826
  function linkHost(input, hostId) {
23373
23827
  return hostId ? { ...input, hostId } : input;
23374
23828
  }
23829
+ function closeQuietly(db) {
23830
+ try {
23831
+ db.close();
23832
+ } catch {
23833
+ }
23834
+ }
23375
23835
  function openWithPragmas(file2) {
23376
23836
  const db = new DatabaseSync(file2);
23377
- db.exec("PRAGMA journal_mode = WAL");
23378
- db.exec("PRAGMA busy_timeout = 2000");
23379
- db.exec("PRAGMA foreign_keys = ON");
23837
+ try {
23838
+ db.exec("PRAGMA journal_mode = WAL");
23839
+ db.exec("PRAGMA busy_timeout = 2000");
23840
+ db.exec("PRAGMA foreign_keys = ON");
23841
+ } catch (err) {
23842
+ closeQuietly(db);
23843
+ throw err;
23844
+ }
23380
23845
  return db;
23381
23846
  }
23382
23847
  function backupLegacyStore(file2) {
@@ -23388,43 +23853,82 @@ function backupLegacyStore(file2) {
23388
23853
  }
23389
23854
  return backup;
23390
23855
  }
23856
+ function openAndInitialize(file2) {
23857
+ let db = openWithPragmas(file2);
23858
+ try {
23859
+ if (isForeignSqliteLineage(db)) {
23860
+ db.close();
23861
+ const backup = backupLegacyStore(file2);
23862
+ db = openWithPragmas(file2);
23863
+ akaWarn(
23864
+ `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
23865
+ );
23866
+ }
23867
+ applyMigrations(db, file2);
23868
+ tightenPerms(file2);
23869
+ const policies = new SqlitePoliciesRepository(db);
23870
+ const installedPacks = new SqliteInstalledPacksRepository(db);
23871
+ const repositories = {
23872
+ events: new SqliteEventsRepository(db),
23873
+ findings: new SqliteFindingsRepository(db),
23874
+ policies,
23875
+ installedPacks,
23876
+ scanLedger: new SqliteScanLedgerRepository(db),
23877
+ secretVault: new SqliteSecretVaultRepository(db),
23878
+ exceptions: new SqliteExceptionsRepository(db),
23879
+ resolutions: new SqliteResolutionsRepository(db),
23880
+ ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
23881
+ security: new SqliteSecurityRepository(db),
23882
+ detections: new SqliteDetectionsRepository(db),
23883
+ shares: new SqliteSharesRepository(db),
23884
+ policyCatalog: new SqlitePolicyCatalogRepository(installedPacks),
23885
+ inventory: new SqliteInventoryRepository(db),
23886
+ inventoryAssets: new SqliteInventoryAssetsRepository(db),
23887
+ projectFiles: new SqliteProjectFilesRepository(db),
23888
+ activity: new SqliteActivityRepository(db),
23889
+ sourceProject: new SqliteSourceProjectRepository(db),
23890
+ auditEvents: new SqliteAuditEventsRepository(db),
23891
+ classifiedData: new SqliteClassifiedDataRepository(db),
23892
+ inspectionDefinitions: new SqliteInspectionDefinitionsRepository(db),
23893
+ inspectionFindings: new SqliteInspectionFindingsRepository(db),
23894
+ configInventory: new SqliteConfigInventoryRepository(db)
23895
+ };
23896
+ policies.seedDefaults();
23897
+ return { db, ...repositories };
23898
+ } catch (err) {
23899
+ closeQuietly(db);
23900
+ throw err;
23901
+ }
23902
+ }
23391
23903
  function openLocalDatabase(dir) {
23392
23904
  ensureDataDirSync(dir);
23393
23905
  const file2 = join(dir, DB_FILENAME);
23394
- let db = openWithPragmas(file2);
23395
- if (isForeignSqliteLineage(db)) {
23396
- db.close();
23397
- const backup = backupLegacyStore(file2);
23398
- db = openWithPragmas(file2);
23399
- akaWarn(
23400
- `Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
23401
- );
23402
- }
23403
- applyMigrations(db, file2);
23404
- tightenPerms(file2);
23405
- const events = new SqliteEventsRepository(db);
23406
- const findings = new SqliteFindingsRepository(db);
23407
- const policies = new SqlitePoliciesRepository(db);
23408
- const installedPacks = new SqliteInstalledPacksRepository(db);
23409
- const scanLedger = new SqliteScanLedgerRepository(db);
23410
- const exceptions = new SqliteExceptionsRepository(db);
23411
- const resolutions = new SqliteResolutionsRepository(db);
23412
- const ruleProbeCache = new SqliteRuleProbeCacheRepository(db);
23413
- const security = new SqliteSecurityRepository(db);
23414
- const detections = new SqliteDetectionsRepository(db);
23415
- const shares = new SqliteSharesRepository(db);
23416
- const policyCatalog = new SqlitePolicyCatalogRepository(installedPacks);
23417
- const inventory = new SqliteInventoryRepository(db);
23418
- const inventoryAssets = new SqliteInventoryAssetsRepository(db);
23419
- const projectFiles = new SqliteProjectFilesRepository(db);
23420
- const activity = new SqliteActivityRepository(db);
23421
- const sourceProject = new SqliteSourceProjectRepository(db);
23422
- const auditEvents = new SqliteAuditEventsRepository(db);
23423
- const classifiedData = new SqliteClassifiedDataRepository(db);
23424
- const inspectionDefinitions = new SqliteInspectionDefinitionsRepository(db);
23425
- const inspectionFindings = new SqliteInspectionFindingsRepository(db);
23426
- const configInventory = new SqliteConfigInventoryRepository(db);
23427
- policies.seedDefaults();
23906
+ const {
23907
+ db,
23908
+ events,
23909
+ findings,
23910
+ policies,
23911
+ installedPacks,
23912
+ scanLedger,
23913
+ secretVault,
23914
+ exceptions,
23915
+ resolutions,
23916
+ ruleProbeCache,
23917
+ security,
23918
+ detections,
23919
+ shares,
23920
+ policyCatalog,
23921
+ inventory,
23922
+ inventoryAssets,
23923
+ projectFiles,
23924
+ activity,
23925
+ sourceProject,
23926
+ auditEvents,
23927
+ classifiedData,
23928
+ inspectionDefinitions,
23929
+ inspectionFindings,
23930
+ configInventory
23931
+ } = openAndInitialize(file2);
23428
23932
  function recordCapture(event, detected) {
23429
23933
  failOpenTransaction(db, () => {
23430
23934
  const sessionId = event.metadata?.sessionId;
@@ -23515,7 +24019,7 @@ function openLocalDatabase(dir) {
23515
24019
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
23516
24020
  if (!definitionId) continue;
23517
24021
  inspectionFindings.insertFinding({
23518
- id: randomUUID8(),
24022
+ id: randomUUID9(),
23519
24023
  auditEventId: record2.scanEvent.id,
23520
24024
  inspectionDefinitionId: definitionId,
23521
24025
  span: finding.span,
@@ -23592,6 +24096,7 @@ function openLocalDatabase(dir) {
23592
24096
  policies,
23593
24097
  installedPacks,
23594
24098
  scanLedger,
24099
+ secretVault,
23595
24100
  exceptions,
23596
24101
  resolutions,
23597
24102
  ruleProbeCache,
@@ -23629,8 +24134,9 @@ import { createHash as createHash3 } from "crypto";
23629
24134
 
23630
24135
  // ../../packages/persistence/src/fingerprint.ts
23631
24136
  import { createHmac, randomBytes } from "crypto";
23632
- import { readFileSync } from "fs";
24137
+ import { existsSync as existsSync2, readFileSync } from "fs";
23633
24138
  import { join as join2 } from "path";
24139
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
23634
24140
  var KEY_FILENAME = "exception.key";
23635
24141
  var KEY_MATERIAL_BYTES = 32;
23636
24142
  function keyFilePath(dataDir2) {
@@ -23723,16 +24229,42 @@ function readJson(file2) {
23723
24229
  return parseJsonObject(text) ?? null;
23724
24230
  }
23725
24231
 
23726
- // ../../packages/persistence/src/warn-era-cap.ts
23727
- import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
24232
+ // ../../packages/persistence/src/vault/crypto.ts
24233
+ import {
24234
+ createCipheriv,
24235
+ createDecipheriv,
24236
+ createHmac as createHmac2,
24237
+ hkdfSync,
24238
+ timingSafeEqual
24239
+ } from "crypto";
24240
+
24241
+ // ../../packages/persistence/src/vault/key-provider.ts
24242
+ import { execFileSync } from "child_process";
24243
+ import { randomBytes as randomBytes2 } from "crypto";
24244
+ import {
24245
+ chmodSync as chmodSync2,
24246
+ mkdirSync as mkdirSync2,
24247
+ readFileSync as readFileSync3,
24248
+ renameSync as renameSync4,
24249
+ rmSync as rmSync3,
24250
+ statSync,
24251
+ writeFileSync as writeFileSync2
24252
+ } from "fs";
23728
24253
  import { join as join5 } from "path";
24254
+
24255
+ // ../../packages/persistence/src/vault/vault.ts
24256
+ import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
24257
+
24258
+ // ../../packages/persistence/src/warn-era-cap.ts
24259
+ import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
24260
+ import { join as join6 } from "path";
23729
24261
  var MARKER = "warn-era-capped";
23730
24262
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
23731
24263
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
23732
- const marker = join5(dataDir2, MARKER);
23733
- if (existsSync2(marker)) return { capped: 0, skipped: "already-run" };
24264
+ const marker = join6(dataDir2, MARKER);
24265
+ if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
23734
24266
  const capped = db.policies.capCategoryActions();
23735
- writeFileSync2(marker, `${new Date(Date.now()).toISOString()}
24267
+ writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
23736
24268
  `, { mode: DATA_FILE_MODE });
23737
24269
  return { capped };
23738
24270
  }
@@ -23789,8 +24321,8 @@ function resolveProvider() {
23789
24321
  function loadConfig(base = defaultDataDir()) {
23790
24322
  try {
23791
24323
  ensureLayoutDirSync(base);
23792
- const settingsFile = join6(settingsDir(base), "settings.json");
23793
- if (existsSync3(settingsFile)) tightenFile(settingsFile);
24324
+ const settingsFile = join7(settingsDir(base), "settings.json");
24325
+ if (existsSync4(settingsFile)) tightenFile(settingsFile);
23794
24326
  } catch {
23795
24327
  }
23796
24328
  migrateLegacyLayout(base);
@@ -23813,9 +24345,9 @@ function resolveProviderSafe() {
23813
24345
  }
23814
24346
 
23815
24347
  // ../../packages/plugin-sdk/src/config-inventory.ts
23816
- import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "fs";
24348
+ import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
23817
24349
  import { homedir as homedir2 } from "os";
23818
- import { basename as basename2, join as join8 } from "path";
24350
+ import { basename as basename2, join as join9 } from "path";
23819
24351
 
23820
24352
  // ../../packages/detections/src/egress/registry.ts
23821
24353
  var EXTRACTOR_VERSION = "1";
@@ -26551,18 +27083,18 @@ function bundledDetections() {
26551
27083
  }
26552
27084
 
26553
27085
  // ../../packages/plugin-sdk/src/repo.ts
26554
- import { existsSync as existsSync4, readFileSync as readFileSync3, statSync } from "fs";
26555
- import { basename, dirname, isAbsolute, join as join7, sep as sep2 } from "path";
27086
+ import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
27087
+ import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
26556
27088
 
26557
27089
  // ../../packages/plugin-sdk/src/events.ts
26558
- import { createHash as createHash4, randomUUID as randomUUID9 } from "crypto";
27090
+ import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
26559
27091
 
26560
27092
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
26561
27093
  import { arch, hostname as hostname3, platform, release } from "os";
26562
27094
 
26563
27095
  // ../../packages/plugin-sdk/src/nudge.ts
26564
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
26565
- import { join as join9 } from "path";
27096
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
27097
+ import { join as join10 } from "path";
26566
27098
 
26567
27099
  // ../../packages/plugin-sdk/src/paths.ts
26568
27100
  import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
@@ -26570,21 +27102,21 @@ import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
26570
27102
 
26571
27103
  // ../../packages/plugin-sdk/src/project-files.ts
26572
27104
  var import_ignore = __toESM(require_ignore(), 1);
26573
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
26574
- import { basename as basename4, join as join10, relative, sep as sep4 } from "path";
27105
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
27106
+ import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
26575
27107
 
26576
27108
  // ../../packages/plugin-sdk/src/runtime.ts
26577
- import { randomUUID as randomUUID10 } from "crypto";
27109
+ import { randomUUID as randomUUID12 } from "crypto";
26578
27110
 
26579
27111
  // ../../packages/plugin-sdk/src/suppressions.ts
26580
27112
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
26581
27113
 
26582
27114
  // ../../packages/plugin-sdk/src/throttle.ts
26583
- import { mkdirSync as mkdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
26584
- import { join as join11 } from "path";
27115
+ import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
27116
+ import { join as join12 } from "path";
26585
27117
 
26586
27118
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
26587
- import { randomUUID as randomUUID11 } from "crypto";
27119
+ import { randomUUID as randomUUID13 } from "crypto";
26588
27120
 
26589
27121
  // ../../packages/plugin-runtime/src/recorder.ts
26590
27122
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -26746,7 +27278,7 @@ var StandaloneDataGateway = class {
26746
27278
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
26747
27279
  const installed = this.installedScanRules();
26748
27280
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
26749
- id: randomUUID11(),
27281
+ id: randomUUID13(),
26750
27282
  scope: "global",
26751
27283
  target: { ruleId },
26752
27284
  action,
@@ -26899,7 +27431,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
26899
27431
  }
26900
27432
 
26901
27433
  // ../../packages/plugin-runtime/src/handle-session-start.ts
26902
- import { randomUUID as randomUUID12 } from "crypto";
27434
+ import { randomUUID as randomUUID14 } from "crypto";
26903
27435
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
26904
27436
 
26905
27437
  // src/hooks/shared.ts
@@ -26988,8 +27520,8 @@ function renderStatusBar(s, opts = {}) {
26988
27520
  const score = `${dot} health ${paint.bold(String(s.score))}${paint.dim("/100")}`;
26989
27521
  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)}`;
26990
27522
  const flag = s.openFindings > 0 ? paint.critical("\u2691") : paint.dim("\u2691");
26991
- const open = `${flag} ${String(s.openFindings)} open findings`;
26992
- return `${paint.brand("\u25B8\u25B8 AKA")}${sep5}${score}${sep5}${tally}${sep5}${open}`;
27523
+ const open2 = `${flag} ${String(s.openFindings)} open findings`;
27524
+ return `${paint.brand("\u25B8\u25B8 AKA")}${sep5}${score}${sep5}${tally}${sep5}${open2}`;
26993
27525
  }
26994
27526
  function renderStatusLine(summary) {
26995
27527
  return renderStatusBar(findingStatus(summary), { color: true });