@akasecurity/ai-tc-claude-code 0.9.3 → 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,13 +492,13 @@ var require_ignore = __commonJS({
492
492
  });
493
493
 
494
494
  // src/apply-suppressions.ts
495
- import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
495
+ import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
496
496
  import { userInfo } from "os";
497
- import { dirname as dirname5, join as join14 } from "path";
497
+ import { dirname as dirname5, join as join15 } from "path";
498
498
  import { fileURLToPath as fileURLToPath3 } from "url";
499
499
 
500
500
  // ../../packages/persistence/src/database.ts
501
- import { randomUUID as randomUUID8 } from "crypto";
501
+ import { randomUUID as randomUUID9 } from "crypto";
502
502
  import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
503
503
  import { join, sep } from "path";
504
504
  import { DatabaseSync } from "node:sqlite";
@@ -564,6 +564,22 @@ var SQLITE_MIGRATIONS = [
564
564
  {
565
565
  tag: "0014_drop_legacy_events_findings",
566
566
  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"
567
+ },
568
+ {
569
+ tag: "0015_busy_vengeance",
570
+ 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`);"
571
+ },
572
+ {
573
+ tag: "0016_breezy_zodiak",
574
+ sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
575
+ },
576
+ {
577
+ tag: "0017_rainy_kat_farrell",
578
+ 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`);"
579
+ },
580
+ {
581
+ tag: "0018_serious_tana_nile",
582
+ sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
567
583
  }
568
584
  ];
569
585
 
@@ -16220,6 +16236,7 @@ var ExceptionConditions = external_exports.object({
16220
16236
  sourceTool: external_exports.string().optional(),
16221
16237
  provider: external_exports.string().optional()
16222
16238
  }).strict();
16239
+ var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
16223
16240
  var DetectionException = external_exports.object({
16224
16241
  id: external_exports.guid(),
16225
16242
  ruleId: external_exports.string(),
@@ -16236,6 +16253,7 @@ var DetectionException = external_exports.object({
16236
16253
  keyVersion: external_exports.number().int().positive(),
16237
16254
  // maskMatch() preview of the approved value — never the raw value.
16238
16255
  maskedValue: external_exports.string(),
16256
+ capability: ExceptionCapability.default("suppress"),
16239
16257
  scope: ExceptionScope,
16240
16258
  expiresAt: external_exports.iso.datetime().nullable(),
16241
16259
  maxUses: external_exports.number().int().positive().nullable(),
@@ -16259,6 +16277,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16259
16277
  ruleId: true,
16260
16278
  valueFingerprint: true,
16261
16279
  keyVersion: true,
16280
+ capability: true,
16262
16281
  expiresAt: true,
16263
16282
  maxUses: true,
16264
16283
  useCount: true,
@@ -17368,8 +17387,120 @@ var PatchInstalledPackRequest = external_exports.object({
17368
17387
  message: "At least one field must be provided"
17369
17388
  }).meta({ id: "PatchInstalledPackRequest" });
17370
17389
 
17390
+ // ../../packages/schema/src/zod/vault.ts
17391
+ var POINTER_FORMAT_VERSION = 2;
17392
+ var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
17393
+ var POINTER_TOKEN_PATTERN = new RegExp(
17394
+ `\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
17395
+ );
17396
+ var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
17397
+ function pointerTokenScanner() {
17398
+ return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
17399
+ }
17400
+ var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
17401
+ var ParsedPointer = external_exports.object({
17402
+ category: DetectionCategory,
17403
+ keyVersion: external_exports.number().int().positive(),
17404
+ pointerId: external_exports.string(),
17405
+ tag: external_exports.string()
17406
+ });
17407
+ var VaultEntry = external_exports.object({
17408
+ pointerId: external_exports.string(),
17409
+ // The keyed HMAC of the raw value under `exception.key`, and the epoch it was
17410
+ // derived under. This is what a reveal-to-model grant matches on, and it rotates
17411
+ // independently of the vault encryption key below.
17412
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17413
+ fingerprintKeyVersion: external_exports.number().int().positive(),
17414
+ // The vault-key epoch this row's ciphertext was sealed under.
17415
+ keyVersion: external_exports.number().int().positive(),
17416
+ // Fixed at first mint and never updated: the same value detected later under a
17417
+ // different rule's category keeps the category it was minted with, so one
17418
+ // value always produces exactly one wire token.
17419
+ category: DetectionCategory,
17420
+ ruleId: external_exports.string(),
17421
+ // Partial-reveal preview for badges and listings. Never the raw value.
17422
+ maskedMatch: external_exports.string(),
17423
+ provider: external_exports.string().optional(),
17424
+ ciphertext: external_exports.string(),
17425
+ nonce: external_exports.string(),
17426
+ authTag: external_exports.string(),
17427
+ // How many times this value has been detected on this machine — the reuse
17428
+ // signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
17429
+ occurrenceCount: external_exports.number().int().nonnegative(),
17430
+ firstSeen: external_exports.string(),
17431
+ lastSeen: external_exports.string()
17432
+ });
17433
+ var PointerDescriptor = external_exports.object({
17434
+ category: DetectionCategory,
17435
+ provider: external_exports.string().optional(),
17436
+ maskedMatch: external_exports.string(),
17437
+ occurrences: external_exports.number().int().nonnegative(),
17438
+ firstSeen: external_exports.string(),
17439
+ lastSeen: external_exports.string()
17440
+ });
17441
+ var PointerIdentity = external_exports.object({
17442
+ ruleId: external_exports.string(),
17443
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17444
+ fingerprintKeyVersion: external_exports.number().int().positive()
17445
+ });
17446
+ var DetokenizeTarget = external_exports.enum(["human", "model"]);
17447
+ var VaultDerefReason = external_exports.enum([
17448
+ "display",
17449
+ "explicit-reveal",
17450
+ "view-render",
17451
+ "model-input",
17452
+ "remediation",
17453
+ "purge"
17454
+ ]);
17455
+ var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
17456
+ var VaultDeref = external_exports.object({
17457
+ id: external_exports.guid(),
17458
+ pointerId: external_exports.string(),
17459
+ at: external_exports.string(),
17460
+ target: DetokenizeTarget,
17461
+ reason: VaultDerefReason,
17462
+ outcome: VaultDerefOutcome,
17463
+ // Present only on a model-target crossing that a reveal grant authorized.
17464
+ grantId: external_exports.string().optional(),
17465
+ // How many pointers ONE batched render resolved. 1 for unbatched rows. Named
17466
+ // apart from VaultEntry.occurrenceCount, which counts detections of a value.
17467
+ pointerCount: external_exports.number().int().positive().default(1)
17468
+ });
17469
+ var VaultSightingKind = external_exports.enum([
17470
+ "prompt",
17471
+ "tool-input",
17472
+ "tool-output",
17473
+ "file",
17474
+ "transcript"
17475
+ ]);
17476
+ var VaultSighting = external_exports.object({
17477
+ location: external_exports.string(),
17478
+ kind: VaultSightingKind,
17479
+ firstSeen: external_exports.string(),
17480
+ lastSeen: external_exports.string()
17481
+ });
17482
+ var VaultInventoryEntry = external_exports.object({
17483
+ pointerId: external_exports.string(),
17484
+ category: DetectionCategory,
17485
+ provider: external_exports.string().optional(),
17486
+ maskedMatch: external_exports.string(),
17487
+ occurrences: external_exports.number().int().nonnegative(),
17488
+ firstSeen: external_exports.string(),
17489
+ lastSeen: external_exports.string(),
17490
+ // The active reveal-to-model grant covering this value, when one exists —
17491
+ // the inventory badges it, the row links to revocation.
17492
+ revealGrantId: external_exports.string().nullable(),
17493
+ sightings: external_exports.array(VaultSighting)
17494
+ });
17495
+ var VaultKeyCustody = external_exports.string();
17496
+ var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17497
+ var VaultConsent = external_exports.object({
17498
+ acknowledgedAt: external_exports.iso.datetime(),
17499
+ version: external_exports.number().int().positive()
17500
+ });
17501
+
17371
17502
  // ../../packages/schema/src/zod/local.ts
17372
- var WORKSPACE_SETTINGS_SPEC_VERSION = 4;
17503
+ var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17373
17504
  var MODEL_JUDGE_PAYLOAD_VERSION = 1;
17374
17505
  var RunMode = external_exports.enum(["standalone"]);
17375
17506
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
@@ -17395,6 +17526,16 @@ var WorkspaceSettings = external_exports.object({
17395
17526
  // In-place egress extraction on the scan paths; disable to stop all Data
17396
17527
  // Shares writes.
17397
17528
  dataSharesInPlace: external_exports.boolean().default(true),
17529
+ // Consent to keep a RECOVERABLE encrypted copy of detected values in the local
17530
+ // vault, instead of destroying them. Absent by default: this is a custody
17531
+ // change from one-way redaction, so it is never an assumed grant on upgrade.
17532
+ // Revoking stops future vaulting; it does not erase what is already stored —
17533
+ // purging the vault is the eraser.
17534
+ vaultConsent: VaultConsent.optional(),
17535
+ // Where the vault master key lives.
17536
+ vaultKeyCustody: VaultKeyCustody.default("file"),
17537
+ // How a pointer renders in assistant prose on screen (see VaultInlineReveal).
17538
+ vaultInlineReveal: VaultInlineReveal.default("masked"),
17398
17539
  // Absent until /aka:setup completes; its presence is what "onboarded" means.
17399
17540
  onboardedAt: external_exports.iso.datetime().optional(),
17400
17541
  // Records that the user consented to sending findings to the model API for
@@ -19781,6 +19922,9 @@ var AmbiguousExceptionIdError = class extends Error {
19781
19922
  var ACTIVE_PREDICATE = `revoked_at IS NULL
19782
19923
  AND (expires_at IS NULL OR expires_at > :now)
19783
19924
  AND (max_uses IS NULL OR use_count < max_uses)`;
19925
+ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19926
+ AND conditions IS NULL
19927
+ AND ${ACTIVE_PREDICATE}`;
19784
19928
  var SqliteExceptionsRepository = class {
19785
19929
  constructor(db) {
19786
19930
  this.db = db;
@@ -19872,11 +20016,11 @@ var SqliteExceptionsRepository = class {
19872
20016
  this.db.prepare(
19873
20017
  `INSERT INTO exceptions (
19874
20018
  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
20019
+ capability, scope, expires_at, max_uses, use_count, last_used_at,
20020
+ justification, conditions, created_by, created_via, created_at, updated_at
19877
20021
  ) VALUES (
19878
20022
  :id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
19879
- :scope, :expiresAt, :maxUses, 0, NULL, :justification,
20023
+ :capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
19880
20024
  :conditions, :createdBy, :createdVia, :now, :now
19881
20025
  )`
19882
20026
  ).run({
@@ -19886,6 +20030,7 @@ var SqliteExceptionsRepository = class {
19886
20030
  valueFingerprint: input.valueFingerprint,
19887
20031
  keyVersion: input.keyVersion,
19888
20032
  maskedValue: input.maskedValue,
20033
+ capability: input.capability ?? "suppress",
19889
20034
  scope: input.scope,
19890
20035
  expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
19891
20036
  maxUses: input.maxUses,
@@ -19979,6 +20124,7 @@ var SqliteExceptionsRepository = class {
19979
20124
  ruleId: row.rule_id,
19980
20125
  valueFingerprint: row.value_fingerprint,
19981
20126
  keyVersion: row.key_version,
20127
+ capability: row.capability,
19982
20128
  expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
19983
20129
  maxUses: row.max_uses,
19984
20130
  useCount: row.use_count,
@@ -20033,6 +20179,35 @@ var SqliteExceptionsRepository = class {
20033
20179
  }))
20034
20180
  );
20035
20181
  }
20182
+ /**
20183
+ * The active reveal-to-model grant for a vaulted value's identity, or null.
20184
+ * Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
20185
+ * suppression uses — plus the capability: a suppression grant must never
20186
+ * authorize a reveal. Read-only: the caller does NOT consume here, because a
20187
+ * revealed value re-enters the detection scan immediately afterward and the
20188
+ * suppression match there claims the use — one crossing, one use.
20189
+ *
20190
+ * A grant with `conditions` NEVER matches here: the reveal path does not yet
20191
+ * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20192
+ * grant instead. Fail closed until reveal-side condition evaluation exists.
20193
+ */
20194
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20195
+ try {
20196
+ const row = getRow(
20197
+ this.db.prepare(
20198
+ `SELECT id FROM exceptions
20199
+ WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
20200
+ AND key_version = :keyVersion
20201
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20202
+ LIMIT 1`
20203
+ ),
20204
+ { ruleId, valueFingerprint, keyVersion, now }
20205
+ );
20206
+ return Promise.resolve(row ?? null);
20207
+ } catch (err) {
20208
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
20209
+ }
20210
+ }
20036
20211
  /**
20037
20212
  * Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
20038
20213
  * exhausted) whose last transition is older than the retention window.
@@ -20060,6 +20235,7 @@ function parseExceptionRow(row) {
20060
20235
  valueFingerprint: row.value_fingerprint,
20061
20236
  keyVersion: row.key_version,
20062
20237
  maskedValue: row.masked_value,
20238
+ capability: row.capability,
20063
20239
  scope: row.scope,
20064
20240
  expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
20065
20241
  maxUses: row.max_uses,
@@ -22225,6 +22401,287 @@ var SqliteScanLedgerRepository = class {
22225
22401
  }
22226
22402
  };
22227
22403
 
22404
+ // ../../packages/persistence/src/repositories/secret-vault.ts
22405
+ import { randomUUID as randomUUID7 } from "crypto";
22406
+ var SELECT_COLUMNS = `
22407
+ pointer_id AS pointerId,
22408
+ value_fingerprint AS valueFingerprint,
22409
+ fingerprint_key_version AS fingerprintKeyVersion,
22410
+ key_version AS keyVersion,
22411
+ format_version AS formatVersion,
22412
+ category,
22413
+ rule_id AS ruleId,
22414
+ masked_match AS maskedMatch,
22415
+ provider,
22416
+ ciphertext,
22417
+ nonce,
22418
+ auth_tag AS authTag,
22419
+ occurrence_count AS occurrenceCount,
22420
+ first_seen AS firstSeen,
22421
+ last_seen AS lastSeen`;
22422
+ function toRow(raw) {
22423
+ const { provider, ...rest } = raw;
22424
+ return provider === null ? rest : { ...rest, provider };
22425
+ }
22426
+ var SqliteSecretVaultRepository = class {
22427
+ constructor(db) {
22428
+ this.db = db;
22429
+ this.insertStmt = db.prepare(
22430
+ `INSERT INTO secret_vault (
22431
+ pointer_id, value_fingerprint, fingerprint_key_version, key_version,
22432
+ format_version, category, rule_id, masked_match, provider,
22433
+ ciphertext, nonce, auth_tag,
22434
+ occurrence_count, first_seen, last_seen
22435
+ ) VALUES (
22436
+ :pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
22437
+ :formatVersion, :category, :ruleId, :maskedMatch, :provider,
22438
+ :ciphertext, :nonce, :authTag,
22439
+ 1, :now, :now
22440
+ )`
22441
+ );
22442
+ this.bumpStmt = db.prepare(
22443
+ `UPDATE secret_vault
22444
+ SET occurrence_count = occurrence_count + 1, last_seen = :now
22445
+ WHERE value_fingerprint = :valueFingerprint`
22446
+ );
22447
+ this.byPointerStmt = db.prepare(
22448
+ `SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
22449
+ );
22450
+ this.byFingerprintStmt = db.prepare(
22451
+ `SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
22452
+ );
22453
+ this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
22454
+ this.replaceCiphertextStmt = db.prepare(
22455
+ `UPDATE secret_vault
22456
+ SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
22457
+ WHERE pointer_id = :pointerId`
22458
+ );
22459
+ this.refreshFingerprintStmt = db.prepare(
22460
+ `UPDATE secret_vault
22461
+ SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
22462
+ WHERE pointer_id = :pointerId`
22463
+ );
22464
+ this.derefStmt = db.prepare(
22465
+ `INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
22466
+ VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
22467
+ );
22468
+ }
22469
+ db;
22470
+ insertStmt;
22471
+ bumpStmt;
22472
+ byPointerStmt;
22473
+ byFingerprintStmt;
22474
+ listStmt;
22475
+ replaceCiphertextStmt;
22476
+ refreshFingerprintStmt;
22477
+ derefStmt;
22478
+ /**
22479
+ * Vault a value, or record another sighting of one already vaulted. Keyed on
22480
+ * `valueFingerprint`, never on the caller's pointer id: a value seen again
22481
+ * bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
22482
+ * pointer, category and ciphertext, so the same secret always resolves to one
22483
+ * wire token. `minted` is true only when this call created the row.
22484
+ *
22485
+ * The read-then-write runs in one IMMEDIATE transaction so two concurrent
22486
+ * writers cannot both decide they are minting.
22487
+ */
22488
+ upsert(input, now) {
22489
+ let minted = false;
22490
+ withTransaction(
22491
+ this.db,
22492
+ () => {
22493
+ const existing = getRow(this.byFingerprintStmt, {
22494
+ valueFingerprint: input.valueFingerprint
22495
+ });
22496
+ if (existing === void 0) {
22497
+ this.insertStmt.run(
22498
+ bindParams({
22499
+ pointerId: input.pointerId,
22500
+ valueFingerprint: input.valueFingerprint,
22501
+ fingerprintKeyVersion: input.fingerprintKeyVersion,
22502
+ keyVersion: input.keyVersion,
22503
+ formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
22504
+ category: input.category,
22505
+ ruleId: input.ruleId,
22506
+ maskedMatch: input.maskedMatch,
22507
+ provider: input.provider,
22508
+ ciphertext: input.ciphertext,
22509
+ nonce: input.nonce,
22510
+ authTag: input.authTag,
22511
+ now
22512
+ })
22513
+ );
22514
+ minted = true;
22515
+ return;
22516
+ }
22517
+ this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
22518
+ },
22519
+ "IMMEDIATE"
22520
+ );
22521
+ const row = getRow(this.byFingerprintStmt, {
22522
+ valueFingerprint: input.valueFingerprint
22523
+ });
22524
+ if (row === void 0) throw new Error("vault: row vanished immediately after write");
22525
+ return { row: toRow(row), minted };
22526
+ }
22527
+ byPointerId(pointerId) {
22528
+ const raw = getRow(this.byPointerStmt, { pointerId });
22529
+ return raw === void 0 ? null : toRow(raw);
22530
+ }
22531
+ byValueFingerprint(fingerprint) {
22532
+ const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
22533
+ return raw === void 0 ? null : toRow(raw);
22534
+ }
22535
+ /** Append one audit row. Carries no raw value and no ciphertext, by shape. */
22536
+ recordDeref(entry) {
22537
+ this.derefStmt.run(
22538
+ bindParams({
22539
+ id: entry.id,
22540
+ pointerId: entry.pointerId,
22541
+ at: entry.at,
22542
+ target: entry.target,
22543
+ reason: entry.reason,
22544
+ outcome: entry.outcome,
22545
+ grantId: entry.grantId,
22546
+ pointerCount: entry.pointerCount ?? 1
22547
+ })
22548
+ );
22549
+ }
22550
+ listAll() {
22551
+ return allRows(this.listStmt).map(toRow);
22552
+ }
22553
+ /** Re-seal an entry under a new key epoch, leaving its identity untouched. */
22554
+ replaceCiphertext(pointerId, next) {
22555
+ this.replaceCiphertextStmt.run({ pointerId, ...next });
22556
+ }
22557
+ /** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
22558
+ refreshFingerprint(pointerId, next) {
22559
+ this.refreshFingerprintStmt.run({ pointerId, ...next });
22560
+ }
22561
+ /**
22562
+ * Destroy every vaulted value and report how many were destroyed. The deref
22563
+ * audit is left alone on purpose — see the table note above.
22564
+ */
22565
+ purgeAll() {
22566
+ let destroyed = 0;
22567
+ withTransaction(
22568
+ this.db,
22569
+ () => {
22570
+ destroyed = this.countEntries();
22571
+ this.db.exec("DELETE FROM secret_vault");
22572
+ },
22573
+ "IMMEDIATE"
22574
+ );
22575
+ return destroyed;
22576
+ }
22577
+ /**
22578
+ * Record (or re-stamp) one place a pointer has been written. One row per
22579
+ * (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
22580
+ * on hook paths — a failure must never affect the rewrite that triggered it,
22581
+ * so callers wrap this, not the other way around.
22582
+ */
22583
+ recordSighting(entry, now) {
22584
+ this.db.prepare(
22585
+ `INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
22586
+ VALUES (:id, :pointerId, :location, :kind, :now, :now)
22587
+ ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22588
+ ).run({
22589
+ id: randomUUID7(),
22590
+ pointerId: entry.pointerId,
22591
+ location: entry.location,
22592
+ kind: entry.kind,
22593
+ now
22594
+ });
22595
+ }
22596
+ listSightings(pointerId) {
22597
+ const rows = allRows(
22598
+ this.db.prepare(
22599
+ `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22600
+ WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
22601
+ ),
22602
+ { pointerId }
22603
+ );
22604
+ return rows.map((r) => ({
22605
+ location: r.location,
22606
+ kind: r.kind,
22607
+ firstSeen: new Date(r.first_seen).toISOString(),
22608
+ lastSeen: new Date(r.last_seen).toISOString()
22609
+ }));
22610
+ }
22611
+ /**
22612
+ * The dashboard inventory: every vaulted value's descriptor data joined with
22613
+ * its sightings and the active reveal-to-model grant when one exists.
22614
+ * Raw-free by construction — neither the fingerprint nor the ciphertext
22615
+ * columns are selected.
22616
+ */
22617
+ listInventory(now = Date.now()) {
22618
+ const rows = allRows(
22619
+ this.db.prepare(
22620
+ `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22621
+ v.occurrence_count, v.first_seen, v.last_seen,
22622
+ (SELECT e.id FROM exceptions e
22623
+ WHERE e.rule_id = v.rule_id
22624
+ AND e.value_fingerprint = v.value_fingerprint
22625
+ AND e.key_version = v.fingerprint_key_version
22626
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22627
+ LIMIT 1) AS grant_id
22628
+ FROM secret_vault v
22629
+ ORDER BY v.last_seen DESC`
22630
+ ),
22631
+ { now }
22632
+ );
22633
+ return rows.map((r) => ({
22634
+ pointerId: r.pointer_id,
22635
+ category: r.category,
22636
+ ...r.provider === null ? {} : { provider: r.provider },
22637
+ maskedMatch: r.masked_match,
22638
+ occurrences: r.occurrence_count,
22639
+ firstSeen: new Date(r.first_seen).toISOString(),
22640
+ lastSeen: new Date(r.last_seen).toISOString(),
22641
+ revealGrantId: r.grant_id,
22642
+ sightings: this.listSightings(r.pointer_id)
22643
+ }));
22644
+ }
22645
+ /**
22646
+ * The de-reference trail, newest first. By default the batched, high-volume
22647
+ * reasons (display, view-render) are hidden and counted instead — the rows
22648
+ * that matter as a signal are the model crossings, and burying them under
22649
+ * render noise would defeat the audit's purpose.
22650
+ */
22651
+ listDerefs(opts) {
22652
+ const limit = opts?.limit ?? 200;
22653
+ const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
22654
+ const rows = allRows(
22655
+ this.db.prepare(
22656
+ `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22657
+ FROM secret_vault_deref ${where}
22658
+ ORDER BY at DESC, rowid DESC LIMIT :limit`
22659
+ ),
22660
+ { limit }
22661
+ );
22662
+ const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
22663
+ this.db,
22664
+ `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22665
+ );
22666
+ return {
22667
+ rows: rows.map((r) => ({
22668
+ id: r.id,
22669
+ pointerId: r.pointer_id,
22670
+ at: new Date(r.at).toISOString(),
22671
+ target: r.target,
22672
+ reason: r.reason,
22673
+ outcome: r.outcome,
22674
+ ...r.grant_id === null ? {} : { grantId: r.grant_id },
22675
+ pointerCount: r.pointer_count
22676
+ })),
22677
+ hiddenBatched
22678
+ };
22679
+ }
22680
+ countEntries() {
22681
+ return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22682
+ }
22683
+ };
22684
+
22228
22685
  // ../../packages/persistence/src/repositories/security.ts
22229
22686
  var DAY_MS4 = 864e5;
22230
22687
  var SEVERITIES = ["critical", "high", "medium", "low"];
@@ -22570,7 +23027,7 @@ var SqliteSecurityRepository = class {
22570
23027
  };
22571
23028
 
22572
23029
  // ../../packages/persistence/src/repositories/shares.ts
22573
- import { randomUUID as randomUUID7 } from "crypto";
23030
+ import { randomUUID as randomUUID8 } from "crypto";
22574
23031
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
22575
23032
  var IN_CHUNK = 500;
22576
23033
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -22826,7 +23283,7 @@ var SqliteSharesRepository = class {
22826
23283
  (id, destination_id, host, decision, created_at, updated_at)
22827
23284
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
22828
23285
  ).run({
22829
- id: randomUUID7(),
23286
+ id: randomUUID8(),
22830
23287
  destinationId,
22831
23288
  host: dest.host,
22832
23289
  decision,
@@ -22975,7 +23432,7 @@ var SqliteSharesRepository = class {
22975
23432
  let destinationId = destIds.get(hit.host);
22976
23433
  if (destinationId === void 0) {
22977
23434
  destStmt.run({
22978
- id: randomUUID7(),
23435
+ id: randomUUID8(),
22979
23436
  kind: hit.kind,
22980
23437
  name: hit.name,
22981
23438
  host: hit.host,
@@ -22991,7 +23448,7 @@ var SqliteSharesRepository = class {
22991
23448
  let endpointId = endpointIds.get(endpointKey);
22992
23449
  if (endpointId === void 0) {
22993
23450
  endpointStmt.run({
22994
- id: randomUUID7(),
23451
+ id: randomUUID8(),
22995
23452
  destinationId,
22996
23453
  method: hit.method,
22997
23454
  transport: hit.transport,
@@ -23004,7 +23461,7 @@ var SqliteSharesRepository = class {
23004
23461
  endpointIds.set(endpointKey, endpointId);
23005
23462
  }
23006
23463
  siteStmt.run({
23007
- id: randomUUID7(),
23464
+ id: randomUUID8(),
23008
23465
  endpointId,
23009
23466
  project: input.project,
23010
23467
  projectKey: input.projectKey,
@@ -23420,6 +23877,7 @@ function openAndInitialize(file2) {
23420
23877
  policies,
23421
23878
  installedPacks,
23422
23879
  scanLedger: new SqliteScanLedgerRepository(db),
23880
+ secretVault: new SqliteSecretVaultRepository(db),
23423
23881
  exceptions: new SqliteExceptionsRepository(db),
23424
23882
  resolutions: new SqliteResolutionsRepository(db),
23425
23883
  ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
@@ -23455,6 +23913,7 @@ function openLocalDatabase(dir) {
23455
23913
  policies,
23456
23914
  installedPacks,
23457
23915
  scanLedger,
23916
+ secretVault,
23458
23917
  exceptions,
23459
23918
  resolutions,
23460
23919
  ruleProbeCache,
@@ -23563,7 +24022,7 @@ function openLocalDatabase(dir) {
23563
24022
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
23564
24023
  if (!definitionId) continue;
23565
24024
  inspectionFindings.insertFinding({
23566
- id: randomUUID8(),
24025
+ id: randomUUID9(),
23567
24026
  auditEventId: record2.scanEvent.id,
23568
24027
  inspectionDefinitionId: definitionId,
23569
24028
  span: finding.span,
@@ -23640,6 +24099,7 @@ function openLocalDatabase(dir) {
23640
24099
  policies,
23641
24100
  installedPacks,
23642
24101
  scanLedger,
24102
+ secretVault,
23643
24103
  exceptions,
23644
24104
  resolutions,
23645
24105
  ruleProbeCache,
@@ -23739,13 +24199,39 @@ function readJson(file2) {
23739
24199
  return parseJsonObject(text) ?? null;
23740
24200
  }
23741
24201
 
23742
- // ../../packages/persistence/src/warn-era-cap.ts
23743
- import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
24202
+ // ../../packages/persistence/src/vault/crypto.ts
24203
+ import {
24204
+ createCipheriv,
24205
+ createDecipheriv,
24206
+ createHmac as createHmac2,
24207
+ hkdfSync,
24208
+ timingSafeEqual
24209
+ } from "crypto";
24210
+
24211
+ // ../../packages/persistence/src/vault/key-provider.ts
24212
+ import { execFileSync } from "child_process";
24213
+ import { randomBytes as randomBytes2 } from "crypto";
24214
+ import {
24215
+ chmodSync as chmodSync2,
24216
+ mkdirSync as mkdirSync2,
24217
+ readFileSync as readFileSync3,
24218
+ renameSync as renameSync4,
24219
+ rmSync as rmSync3,
24220
+ statSync,
24221
+ writeFileSync as writeFileSync2
24222
+ } from "fs";
23744
24223
  import { join as join5 } from "path";
23745
24224
 
24225
+ // ../../packages/persistence/src/vault/vault.ts
24226
+ import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
24227
+
24228
+ // ../../packages/persistence/src/warn-era-cap.ts
24229
+ import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
24230
+ import { join as join6 } from "path";
24231
+
23746
24232
  // ../../packages/plugin-sdk/src/config.ts
23747
24233
  import { existsSync as existsSync4 } from "fs";
23748
- import { join as join6 } from "path";
24234
+ import { join as join7 } from "path";
23749
24235
 
23750
24236
  // ../../packages/plugin-sdk/src/provider-env.ts
23751
24237
  var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
@@ -23799,7 +24285,7 @@ function resolveProvider() {
23799
24285
  function loadConfig(base = defaultDataDir()) {
23800
24286
  try {
23801
24287
  ensureLayoutDirSync(base);
23802
- const settingsFile = join6(settingsDir(base), "settings.json");
24288
+ const settingsFile = join7(settingsDir(base), "settings.json");
23803
24289
  if (existsSync4(settingsFile)) tightenFile(settingsFile);
23804
24290
  } catch {
23805
24291
  }
@@ -23823,9 +24309,9 @@ function resolveProviderSafe() {
23823
24309
  }
23824
24310
 
23825
24311
  // ../../packages/plugin-sdk/src/config-inventory.ts
23826
- import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "fs";
24312
+ import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
23827
24313
  import { homedir as homedir2 } from "os";
23828
- import { basename as basename2, join as join8 } from "path";
24314
+ import { basename as basename2, join as join9 } from "path";
23829
24315
 
23830
24316
  // ../../packages/detections/src/egress/registry.ts
23831
24317
  var EXTRACTOR_VERSION = "1";
@@ -24613,12 +25099,12 @@ function redact(text, findings) {
24613
25099
  const regions = [];
24614
25100
  for (const f of sorted) {
24615
25101
  const rank = SEVERITY_RANK2[f.severity];
24616
- const open = regions[regions.length - 1];
24617
- if (open && f.span.start < open.end) {
24618
- open.end = Math.max(open.end, f.span.end);
24619
- if (rank > open.rank) {
24620
- open.rank = rank;
24621
- open.category = f.category;
25102
+ const open2 = regions[regions.length - 1];
25103
+ if (open2 && f.span.start < open2.end) {
25104
+ open2.end = Math.max(open2.end, f.span.end);
25105
+ if (rank > open2.rank) {
25106
+ open2.rank = rank;
25107
+ open2.category = f.category;
24622
25108
  }
24623
25109
  } else {
24624
25110
  regions.push({ start: f.span.start, end: f.span.end, category: f.category, rank });
@@ -24647,6 +25133,24 @@ function maskMatch(raw) {
24647
25133
  return `${raw.charAt(0)}${"*".repeat(6)}${raw.charAt(raw.length - 1)}`;
24648
25134
  }
24649
25135
 
25136
+ // ../../packages/detections/src/pointer-shield.ts
25137
+ function shieldPointers(text) {
25138
+ const spans = [];
25139
+ let out = null;
25140
+ for (const match of text.matchAll(pointerTokenScanner())) {
25141
+ spans.push({ start: match.index, end: match.index + match[0].length });
25142
+ out ??= text;
25143
+ out = out.slice(0, match.index) + " ".repeat(match[0].length) + out.slice(match.index + match[0].length);
25144
+ }
25145
+ return { text: out ?? text, spans };
25146
+ }
25147
+ function dropShieldedFindings(findings, spans) {
25148
+ if (spans.length === 0) return findings;
25149
+ return findings.filter(
25150
+ (finding) => !spans.some((s) => finding.span.start < s.end && finding.span.end > s.start)
25151
+ );
25152
+ }
25153
+
24650
25154
  // ../../packages/detections/src/posture/config-posture.ts
24651
25155
  var RULE_VERSION = "1";
24652
25156
  var KNOWN_TOOLS = /* @__PURE__ */ new Set([
@@ -26768,7 +27272,8 @@ function scanText(text, ruleVersions) {
26768
27272
  if (!ensureBundledPacks()) return { masked: "[REDACTED]", findings: [] };
26769
27273
  try {
26770
27274
  const rules = getLoadedRules();
26771
- const matches = scan(text, rules);
27275
+ const shielded = shieldPointers(text);
27276
+ const matches = dropShieldedFindings(scan(shielded.text, rules), shielded.spans);
26772
27277
  if (matches.length === 0) return { masked: text, findings: [] };
26773
27278
  const byId = new Map(rules.map((r) => [r.id, r]));
26774
27279
  const findings = matches.map((m) => {
@@ -26794,18 +27299,18 @@ function maskText(text) {
26794
27299
  }
26795
27300
 
26796
27301
  // ../../packages/plugin-sdk/src/repo.ts
26797
- import { existsSync as existsSync5, readFileSync as readFileSync3, statSync } from "fs";
26798
- import { basename, dirname, isAbsolute, join as join7, sep as sep2 } from "path";
27302
+ import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
27303
+ import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
26799
27304
 
26800
27305
  // ../../packages/plugin-sdk/src/events.ts
26801
- import { createHash as createHash4, randomUUID as randomUUID9 } from "crypto";
27306
+ import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
26802
27307
 
26803
27308
  // ../../packages/plugin-sdk/src/inventory-resolver.ts
26804
27309
  import { arch, hostname as hostname3, platform, release } from "os";
26805
27310
 
26806
27311
  // ../../packages/plugin-sdk/src/nudge.ts
26807
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
26808
- import { join as join9 } from "path";
27312
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
27313
+ import { join as join10 } from "path";
26809
27314
 
26810
27315
  // ../../packages/plugin-sdk/src/paths.ts
26811
27316
  import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
@@ -26823,8 +27328,8 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
26823
27328
 
26824
27329
  // ../../packages/plugin-sdk/src/project-files.ts
26825
27330
  var import_ignore = __toESM(require_ignore(), 1);
26826
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
26827
- import { basename as basename4, join as join10, relative, sep as sep4 } from "path";
27331
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
27332
+ import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
26828
27333
 
26829
27334
  // ../../packages/plugin-sdk/src/raw-egress.ts
26830
27335
  var RawEgressError = class extends Error {
@@ -26875,7 +27380,7 @@ function assertRawFree(text, rawValues) {
26875
27380
  }
26876
27381
 
26877
27382
  // ../../packages/plugin-sdk/src/runtime.ts
26878
- import { randomUUID as randomUUID10 } from "crypto";
27383
+ import { randomUUID as randomUUID12 } from "crypto";
26879
27384
 
26880
27385
  // ../../packages/plugin-sdk/src/suppressions.ts
26881
27386
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -26915,8 +27420,8 @@ async function applySetupTriageSuppressions(entries, writer, opts) {
26915
27420
  }
26916
27421
 
26917
27422
  // ../../packages/plugin-sdk/src/throttle.ts
26918
- import { mkdirSync as mkdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
26919
- import { join as join11 } from "path";
27423
+ import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
27424
+ import { join as join12 } from "path";
26920
27425
 
26921
27426
  // src/command-registry.ts
26922
27427
  import { readdirSync as readdirSync4 } from "fs";
@@ -27031,12 +27536,12 @@ function show(body) {
27031
27536
  }
27032
27537
 
27033
27538
  // src/triage/gate-display.ts
27034
- function findContext(entry, join15) {
27035
- const byFingerprint = join15.find(
27539
+ function findContext(entry, join16) {
27540
+ const byFingerprint = join16.find(
27036
27541
  (j) => j.valueFingerprint !== void 0 && j.valueFingerprint === entry.valueFingerprint
27037
27542
  );
27038
27543
  if (byFingerprint) return byFingerprint.maskedContext;
27039
- const byRuleAndMask = join15.find(
27544
+ const byRuleAndMask = join16.find(
27040
27545
  (j) => j.ruleId === entry.ruleId && j.maskedMatch === entry.maskedValue
27041
27546
  );
27042
27547
  return byRuleAndMask?.maskedContext;
@@ -27107,13 +27612,13 @@ function renderShowcase(showcase) {
27107
27612
 
27108
27613
  ${blocks.join("\n\n")}`;
27109
27614
  }
27110
- function renderSuppressionGate(entries, join15) {
27615
+ function renderSuppressionGate(entries, join16) {
27111
27616
  if (entries.length === 0) {
27112
27617
  return "No false-positive suppressions to confirm \u2014 nothing will be written.";
27113
27618
  }
27114
27619
  const header = entries.length === 1 ? "This looks like a false positive \u2014 take a look before I suppress it:" : `These ${String(entries.length)} look like false positives \u2014 take a look before I suppress them:`;
27115
27620
  const blocks = entries.map((entry, i) => {
27116
- const context = findContext(entry, join15);
27621
+ const context = findContext(entry, join16);
27117
27622
  const lines = [
27118
27623
  `${String(i + 1)}. ${entry.ruleId} [${entry.category}]`,
27119
27624
  ` value: ${entry.maskedValue}`,
@@ -27343,9 +27848,9 @@ function mergeRecommendations(verdicts) {
27343
27848
  }
27344
27849
 
27345
27850
  // src/triage/plan-file.ts
27346
- import { mkdtempSync, readFileSync as readFileSync7, rmdirSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
27851
+ import { mkdtempSync, readFileSync as readFileSync8, rmdirSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
27347
27852
  import { tmpdir } from "os";
27348
- import { basename as basename5, dirname as dirname3, join as join12 } from "path";
27853
+ import { basename as basename5, dirname as dirname3, join as join13 } from "path";
27349
27854
  var SuppressionEntrySchema = external_exports.object({
27350
27855
  ruleId: external_exports.string(),
27351
27856
  category: DetectionCategory,
@@ -27400,18 +27905,18 @@ function serializePlan(plan, current) {
27400
27905
  function writePlanFile(plan, current, rawValues, deps = {}) {
27401
27906
  const serialized = serializePlan(plan, current);
27402
27907
  assertRawFree(serialized, rawValues);
27403
- const dir = (deps.mkTempDir ?? (() => mkdtempSync(join12(tmpdir(), "aka-plan-"))))();
27404
- const path = join12(dir, "setup-plan.json");
27405
- writeFileSync5(path, serialized, { encoding: "utf8", mode: 384 });
27908
+ const dir = (deps.mkTempDir ?? (() => mkdtempSync(join13(tmpdir(), "aka-plan-"))))();
27909
+ const path = join13(dir, "setup-plan.json");
27910
+ writeFileSync6(path, serialized, { encoding: "utf8", mode: 384 });
27406
27911
  return path;
27407
27912
  }
27408
27913
  function readPlanFile(path) {
27409
- const text = readFileSync7(path, "utf8");
27914
+ const text = readFileSync8(path, "utf8");
27410
27915
  const json2 = JSON.parse(text);
27411
27916
  return PersistedPlanSchema.parse(json2);
27412
27917
  }
27413
27918
  function deletePlanFile(path) {
27414
- rmSync3(path, { force: true });
27919
+ rmSync4(path, { force: true });
27415
27920
  const dir = dirname3(path);
27416
27921
  if (!basename5(dir).startsWith("aka-plan-")) return;
27417
27922
  try {
@@ -27480,8 +27985,8 @@ function buildJoinEntries(hits) {
27480
27985
  }
27481
27986
 
27482
27987
  // src/triage/resolve.ts
27483
- function resolveSuppressions(rec, join15) {
27484
- const byId = new Map(join15.map((e) => [e.id, e]));
27988
+ function resolveSuppressions(rec, join16) {
27989
+ const byId = new Map(join16.map((e) => [e.id, e]));
27485
27990
  const entries = [];
27486
27991
  const skipped = [];
27487
27992
  for (const cat of rec.perCategory) {
@@ -27583,7 +28088,7 @@ function parseTriageStream(text) {
27583
28088
  return { hits, status: "complete" };
27584
28089
  }
27585
28090
  function planTriageWriteback(hits, rec) {
27586
- const join15 = buildJoinEntries(hits);
28091
+ const join16 = buildJoinEntries(hits);
27587
28092
  const rawValues = hits.map((h) => h.rawMatch);
27588
28093
  const skipped = [];
27589
28094
  const posture = {};
@@ -27623,7 +28128,7 @@ function planTriageWriteback(hits, rec) {
27623
28128
  }
27624
28129
  const { entries, skipped: resolveSkips } = resolveSuppressions(
27625
28130
  { perCategory: safeCategories, notes: rec.notes },
27626
- join15
28131
+ join16
27627
28132
  );
27628
28133
  skipped.push(...resolveSkips);
27629
28134
  let notes = rec.notes;
@@ -27633,7 +28138,7 @@ function planTriageWriteback(hits, rec) {
27633
28138
  if (err instanceof RawEgressError) notes = SCRUBBED_NOTES;
27634
28139
  else throw err;
27635
28140
  }
27636
- return { entries, posture, showcase, join: join15, notes, skipped };
28141
+ return { entries, posture, showcase, join: join16, notes, skipped };
27637
28142
  }
27638
28143
  function recommendedPosture(evidence) {
27639
28144
  return { ...severityFloorPosture(), ...evidence };
@@ -27889,10 +28394,10 @@ async function runConfirm(deps, planIO) {
27889
28394
  }
27890
28395
 
27891
28396
  // src/triage/judge.ts
27892
- import { execFileSync } from "child_process";
27893
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
28397
+ import { execFileSync as execFileSync2 } from "child_process";
28398
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync9, rmSync as rmSync5 } from "fs";
27894
28399
  import { tmpdir as tmpdir2 } from "os";
27895
- import { dirname as dirname4, join as join13 } from "path";
28400
+ import { dirname as dirname4, join as join14 } from "path";
27896
28401
  import { fileURLToPath as fileURLToPath2 } from "url";
27897
28402
 
27898
28403
  // src/triage/parse-verdict.ts
@@ -27906,7 +28411,7 @@ function parseRecommendation(text) {
27906
28411
 
27907
28412
  // src/triage/judge.ts
27908
28413
  var TRIAGE_DIR = dirname4(fileURLToPath2(import.meta.url));
27909
- var DEFAULT_RUBRIC_PATH = join13(TRIAGE_DIR, "..", "..", "eval", "prompt.md");
28414
+ var DEFAULT_RUBRIC_PATH = join14(TRIAGE_DIR, "..", "..", "eval", "prompt.md");
27910
28415
  function parseVerdict(stdout) {
27911
28416
  let envelope;
27912
28417
  try {
@@ -27925,20 +28430,20 @@ function parseVerdict(stdout) {
27925
28430
  throw new Error("claude -p returned an unparseable TriageRecommendation");
27926
28431
  }
27927
28432
  }
27928
- function judgeEnv() {
28433
+ function judgeEnv(platform2 = process.platform) {
27929
28434
  const env = {
27930
28435
  // eslint-disable-next-line n/no-process-env -- subprocess must inherit PATH/auth
27931
28436
  ...process.env,
27932
28437
  CLAUDE_CODE_SKIP_PROMPT_HISTORY: "1",
27933
28438
  CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
27934
28439
  };
27935
- if (process.platform === "darwin") {
27936
- env.CLAUDE_CONFIG_DIR = mkdtempSync2(join13(tmpdir2(), "aka-judge-cfg-"));
28440
+ if (platform2 === "darwin") {
28441
+ env.CLAUDE_CONFIG_DIR = mkdtempSync2(join14(tmpdir2(), "aka-judge-cfg-"));
27937
28442
  }
27938
28443
  return env;
27939
28444
  }
27940
28445
  function spawnClaude(argv, env, stdin) {
27941
- return execFileSync("claude", [...argv], {
28446
+ return execFileSync2("claude", [...argv], {
27942
28447
  env,
27943
28448
  input: stdin,
27944
28449
  encoding: "utf8",
@@ -27962,7 +28467,10 @@ function toJudgePayload(hit) {
27962
28467
  return payload;
27963
28468
  }
27964
28469
  function runJudge(hits, deps) {
27965
- const rubric = deps.loadRubric?.() ?? readFileSync8(DEFAULT_RUBRIC_PATH, "utf8");
28470
+ if (typeof deps.spawn !== "function") {
28471
+ throw new TypeError("runJudge requires deps.spawn \u2014 there is no live-spawn fallback");
28472
+ }
28473
+ const rubric = deps.loadRubric?.() ?? readFileSync9(DEFAULT_RUBRIC_PATH, "utf8");
27966
28474
  const hitsJsonl = hits.map((h) => JSON.stringify(toJudgePayload(h))).join("\n");
27967
28475
  const fullPrompt = `${rubric}
27968
28476
 
@@ -27973,7 +28481,8 @@ ${hitsJsonl}
27973
28481
  \`\`\`
27974
28482
  `;
27975
28483
  const argv = ["-p", "--no-session-persistence", "--output-format", "json"];
27976
- const env = judgeEnv();
28484
+ const platform2 = deps.platform ?? process.platform;
28485
+ const env = judgeEnv(platform2);
27977
28486
  try {
27978
28487
  let stdout;
27979
28488
  try {
@@ -27983,8 +28492,11 @@ ${hitsJsonl}
27983
28492
  }
27984
28493
  return parseVerdict(stdout);
27985
28494
  } finally {
27986
- if (process.platform === "darwin" && env.CLAUDE_CONFIG_DIR) {
27987
- rmSync4(env.CLAUDE_CONFIG_DIR, { recursive: true, force: true });
28495
+ if (platform2 === "darwin" && env.CLAUDE_CONFIG_DIR) {
28496
+ try {
28497
+ rmSync5(env.CLAUDE_CONFIG_DIR, { recursive: true, force: true });
28498
+ } catch {
28499
+ }
27988
28500
  }
27989
28501
  }
27990
28502
  }
@@ -28004,9 +28516,9 @@ function resolveCreatedBy() {
28004
28516
  }
28005
28517
  function loadRubric() {
28006
28518
  const here = dirname5(fileURLToPath3(import.meta.url));
28007
- const shipped = join14(here, "triage-rubric.md");
28008
- if (existsSync7(shipped)) return readFileSync9(shipped, "utf8");
28009
- return readFileSync9(join14(here, "..", "eval", "prompt.md"), "utf8");
28519
+ const shipped = join15(here, "triage-rubric.md");
28520
+ if (existsSync7(shipped)) return readFileSync10(shipped, "utf8");
28521
+ return readFileSync10(join15(here, "..", "eval", "prompt.md"), "utf8");
28010
28522
  }
28011
28523
  async function main() {
28012
28524
  const argv = process.argv.slice(2);
@@ -28014,7 +28526,7 @@ async function main() {
28014
28526
  argv,
28015
28527
  // fd 0 = stdin; the wizard pipes `backfill.js --triage` into this on preview.
28016
28528
  // Called only on the preview path — the confirm path never reads a stream.
28017
- readStream: (streamPath) => streamPath !== void 0 ? readFileSync9(streamPath, "utf8") : readFileSync9(0, "utf8"),
28529
+ readStream: (streamPath) => streamPath !== void 0 ? readFileSync10(streamPath, "utf8") : readFileSync10(0, "utf8"),
28018
28530
  runJudge: (hits) => runJudge(hits, { spawn: spawnClaude, loadRubric }),
28019
28531
  // The distinct model-judge egress consent, read from settings.json. When it
28020
28532
  // is absent or stale the preview skips the judge instead of sending findings