@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.
@@ -493,10 +493,10 @@ var require_ignore = __commonJS({
493
493
 
494
494
  // ../../packages/plugin-sdk/src/config.ts
495
495
  import { existsSync as existsSync4 } from "fs";
496
- import { join as join6 } from "path";
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,120 @@ 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
+ function pointerTokenScanner() {
17391
+ return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
17392
+ }
17393
+ var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
17394
+ var ParsedPointer = external_exports.object({
17395
+ category: DetectionCategory,
17396
+ keyVersion: external_exports.number().int().positive(),
17397
+ pointerId: external_exports.string(),
17398
+ tag: external_exports.string()
17399
+ });
17400
+ var VaultEntry = external_exports.object({
17401
+ pointerId: external_exports.string(),
17402
+ // The keyed HMAC of the raw value under `exception.key`, and the epoch it was
17403
+ // derived under. This is what a reveal-to-model grant matches on, and it rotates
17404
+ // independently of the vault encryption key below.
17405
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17406
+ fingerprintKeyVersion: external_exports.number().int().positive(),
17407
+ // The vault-key epoch this row's ciphertext was sealed under.
17408
+ keyVersion: external_exports.number().int().positive(),
17409
+ // Fixed at first mint and never updated: the same value detected later under a
17410
+ // different rule's category keeps the category it was minted with, so one
17411
+ // value always produces exactly one wire token.
17412
+ category: DetectionCategory,
17413
+ ruleId: external_exports.string(),
17414
+ // Partial-reveal preview for badges and listings. Never the raw value.
17415
+ maskedMatch: external_exports.string(),
17416
+ provider: external_exports.string().optional(),
17417
+ ciphertext: external_exports.string(),
17418
+ nonce: external_exports.string(),
17419
+ authTag: external_exports.string(),
17420
+ // How many times this value has been detected on this machine — the reuse
17421
+ // signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
17422
+ occurrenceCount: external_exports.number().int().nonnegative(),
17423
+ firstSeen: external_exports.string(),
17424
+ lastSeen: external_exports.string()
17425
+ });
17426
+ var PointerDescriptor = external_exports.object({
17427
+ category: DetectionCategory,
17428
+ provider: external_exports.string().optional(),
17429
+ maskedMatch: external_exports.string(),
17430
+ occurrences: external_exports.number().int().nonnegative(),
17431
+ firstSeen: external_exports.string(),
17432
+ lastSeen: external_exports.string()
17433
+ });
17434
+ var PointerIdentity = external_exports.object({
17435
+ ruleId: external_exports.string(),
17436
+ valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
17437
+ fingerprintKeyVersion: external_exports.number().int().positive()
17438
+ });
17439
+ var DetokenizeTarget = external_exports.enum(["human", "model"]);
17440
+ var VaultDerefReason = external_exports.enum([
17441
+ "display",
17442
+ "explicit-reveal",
17443
+ "view-render",
17444
+ "model-input",
17445
+ "remediation",
17446
+ "purge"
17447
+ ]);
17448
+ var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
17449
+ var VaultDeref = external_exports.object({
17450
+ id: external_exports.guid(),
17451
+ pointerId: external_exports.string(),
17452
+ at: external_exports.string(),
17453
+ target: DetokenizeTarget,
17454
+ reason: VaultDerefReason,
17455
+ outcome: VaultDerefOutcome,
17456
+ // Present only on a model-target crossing that a reveal grant authorized.
17457
+ grantId: external_exports.string().optional(),
17458
+ // How many pointers ONE batched render resolved. 1 for unbatched rows. Named
17459
+ // apart from VaultEntry.occurrenceCount, which counts detections of a value.
17460
+ pointerCount: external_exports.number().int().positive().default(1)
17461
+ });
17462
+ var VaultSightingKind = external_exports.enum([
17463
+ "prompt",
17464
+ "tool-input",
17465
+ "tool-output",
17466
+ "file",
17467
+ "transcript"
17468
+ ]);
17469
+ var VaultSighting = external_exports.object({
17470
+ location: external_exports.string(),
17471
+ kind: VaultSightingKind,
17472
+ firstSeen: external_exports.string(),
17473
+ lastSeen: external_exports.string()
17474
+ });
17475
+ var VaultInventoryEntry = external_exports.object({
17476
+ pointerId: external_exports.string(),
17477
+ category: DetectionCategory,
17478
+ provider: external_exports.string().optional(),
17479
+ maskedMatch: external_exports.string(),
17480
+ occurrences: external_exports.number().int().nonnegative(),
17481
+ firstSeen: external_exports.string(),
17482
+ lastSeen: external_exports.string(),
17483
+ // The active reveal-to-model grant covering this value, when one exists —
17484
+ // the inventory badges it, the row links to revocation.
17485
+ revealGrantId: external_exports.string().nullable(),
17486
+ sightings: external_exports.array(VaultSighting)
17487
+ });
17488
+ var VaultKeyCustody = external_exports.string();
17489
+ var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
17490
+ var VaultConsent = external_exports.object({
17491
+ acknowledgedAt: external_exports.iso.datetime(),
17492
+ version: external_exports.number().int().positive()
17493
+ });
17494
+
17364
17495
  // ../../packages/schema/src/zod/local.ts
17365
- var WORKSPACE_SETTINGS_SPEC_VERSION = 4;
17496
+ var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
17366
17497
  var RunMode = external_exports.enum(["standalone"]);
17367
17498
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17368
17499
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
@@ -17384,6 +17515,16 @@ var WorkspaceSettings = external_exports.object({
17384
17515
  // In-place egress extraction on the scan paths; disable to stop all Data
17385
17516
  // Shares writes.
17386
17517
  dataSharesInPlace: external_exports.boolean().default(true),
17518
+ // Consent to keep a RECOVERABLE encrypted copy of detected values in the local
17519
+ // vault, instead of destroying them. Absent by default: this is a custody
17520
+ // change from one-way redaction, so it is never an assumed grant on upgrade.
17521
+ // Revoking stops future vaulting; it does not erase what is already stored —
17522
+ // purging the vault is the eraser.
17523
+ vaultConsent: VaultConsent.optional(),
17524
+ // Where the vault master key lives.
17525
+ vaultKeyCustody: VaultKeyCustody.default("file"),
17526
+ // How a pointer renders in assistant prose on screen (see VaultInlineReveal).
17527
+ vaultInlineReveal: VaultInlineReveal.default("masked"),
17387
17528
  // Absent until /aka:setup completes; its presence is what "onboarded" means.
17388
17529
  onboardedAt: external_exports.iso.datetime().optional(),
17389
17530
  // Records that the user consented to sending findings to the model API for
@@ -19798,6 +19939,9 @@ var AmbiguousExceptionIdError = class extends Error {
19798
19939
  var ACTIVE_PREDICATE = `revoked_at IS NULL
19799
19940
  AND (expires_at IS NULL OR expires_at > :now)
19800
19941
  AND (max_uses IS NULL OR use_count < max_uses)`;
19942
+ var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
19943
+ AND conditions IS NULL
19944
+ AND ${ACTIVE_PREDICATE}`;
19801
19945
  var SqliteExceptionsRepository = class {
19802
19946
  constructor(db) {
19803
19947
  this.db = db;
@@ -19889,11 +20033,11 @@ var SqliteExceptionsRepository = class {
19889
20033
  this.db.prepare(
19890
20034
  `INSERT INTO exceptions (
19891
20035
  id, rule_id, category, value_fingerprint, key_version, masked_value,
19892
- scope, expires_at, max_uses, use_count, last_used_at, justification,
19893
- conditions, created_by, created_via, created_at, updated_at
20036
+ capability, scope, expires_at, max_uses, use_count, last_used_at,
20037
+ justification, conditions, created_by, created_via, created_at, updated_at
19894
20038
  ) VALUES (
19895
20039
  :id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
19896
- :scope, :expiresAt, :maxUses, 0, NULL, :justification,
20040
+ :capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
19897
20041
  :conditions, :createdBy, :createdVia, :now, :now
19898
20042
  )`
19899
20043
  ).run({
@@ -19903,6 +20047,7 @@ var SqliteExceptionsRepository = class {
19903
20047
  valueFingerprint: input.valueFingerprint,
19904
20048
  keyVersion: input.keyVersion,
19905
20049
  maskedValue: input.maskedValue,
20050
+ capability: input.capability ?? "suppress",
19906
20051
  scope: input.scope,
19907
20052
  expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
19908
20053
  maxUses: input.maxUses,
@@ -19996,6 +20141,7 @@ var SqliteExceptionsRepository = class {
19996
20141
  ruleId: row.rule_id,
19997
20142
  valueFingerprint: row.value_fingerprint,
19998
20143
  keyVersion: row.key_version,
20144
+ capability: row.capability,
19999
20145
  expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
20000
20146
  maxUses: row.max_uses,
20001
20147
  useCount: row.use_count,
@@ -20050,6 +20196,35 @@ var SqliteExceptionsRepository = class {
20050
20196
  }))
20051
20197
  );
20052
20198
  }
20199
+ /**
20200
+ * The active reveal-to-model grant for a vaulted value's identity, or null.
20201
+ * Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
20202
+ * suppression uses — plus the capability: a suppression grant must never
20203
+ * authorize a reveal. Read-only: the caller does NOT consume here, because a
20204
+ * revealed value re-enters the detection scan immediately afterward and the
20205
+ * suppression match there claims the use — one crossing, one use.
20206
+ *
20207
+ * A grant with `conditions` NEVER matches here: the reveal path does not yet
20208
+ * evaluate conditions, and a narrowing clause that is ignored would WIDEN the
20209
+ * grant instead. Fail closed until reveal-side condition evaluation exists.
20210
+ */
20211
+ activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
20212
+ try {
20213
+ const row = getRow(
20214
+ this.db.prepare(
20215
+ `SELECT id FROM exceptions
20216
+ WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
20217
+ AND key_version = :keyVersion
20218
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
20219
+ LIMIT 1`
20220
+ ),
20221
+ { ruleId, valueFingerprint, keyVersion, now }
20222
+ );
20223
+ return Promise.resolve(row ?? null);
20224
+ } catch (err) {
20225
+ return Promise.reject(err instanceof Error ? err : new Error(String(err)));
20226
+ }
20227
+ }
20053
20228
  /**
20054
20229
  * Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
20055
20230
  * exhausted) whose last transition is older than the retention window.
@@ -20077,6 +20252,7 @@ function parseExceptionRow(row) {
20077
20252
  valueFingerprint: row.value_fingerprint,
20078
20253
  keyVersion: row.key_version,
20079
20254
  maskedValue: row.masked_value,
20255
+ capability: row.capability,
20080
20256
  scope: row.scope,
20081
20257
  expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
20082
20258
  maxUses: row.max_uses,
@@ -22242,6 +22418,287 @@ var SqliteScanLedgerRepository = class {
22242
22418
  }
22243
22419
  };
22244
22420
 
22421
+ // ../../packages/persistence/src/repositories/secret-vault.ts
22422
+ import { randomUUID as randomUUID7 } from "crypto";
22423
+ var SELECT_COLUMNS = `
22424
+ pointer_id AS pointerId,
22425
+ value_fingerprint AS valueFingerprint,
22426
+ fingerprint_key_version AS fingerprintKeyVersion,
22427
+ key_version AS keyVersion,
22428
+ format_version AS formatVersion,
22429
+ category,
22430
+ rule_id AS ruleId,
22431
+ masked_match AS maskedMatch,
22432
+ provider,
22433
+ ciphertext,
22434
+ nonce,
22435
+ auth_tag AS authTag,
22436
+ occurrence_count AS occurrenceCount,
22437
+ first_seen AS firstSeen,
22438
+ last_seen AS lastSeen`;
22439
+ function toRow(raw) {
22440
+ const { provider, ...rest } = raw;
22441
+ return provider === null ? rest : { ...rest, provider };
22442
+ }
22443
+ var SqliteSecretVaultRepository = class {
22444
+ constructor(db) {
22445
+ this.db = db;
22446
+ this.insertStmt = db.prepare(
22447
+ `INSERT INTO secret_vault (
22448
+ pointer_id, value_fingerprint, fingerprint_key_version, key_version,
22449
+ format_version, category, rule_id, masked_match, provider,
22450
+ ciphertext, nonce, auth_tag,
22451
+ occurrence_count, first_seen, last_seen
22452
+ ) VALUES (
22453
+ :pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
22454
+ :formatVersion, :category, :ruleId, :maskedMatch, :provider,
22455
+ :ciphertext, :nonce, :authTag,
22456
+ 1, :now, :now
22457
+ )`
22458
+ );
22459
+ this.bumpStmt = db.prepare(
22460
+ `UPDATE secret_vault
22461
+ SET occurrence_count = occurrence_count + 1, last_seen = :now
22462
+ WHERE value_fingerprint = :valueFingerprint`
22463
+ );
22464
+ this.byPointerStmt = db.prepare(
22465
+ `SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
22466
+ );
22467
+ this.byFingerprintStmt = db.prepare(
22468
+ `SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
22469
+ );
22470
+ this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
22471
+ this.replaceCiphertextStmt = db.prepare(
22472
+ `UPDATE secret_vault
22473
+ SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
22474
+ WHERE pointer_id = :pointerId`
22475
+ );
22476
+ this.refreshFingerprintStmt = db.prepare(
22477
+ `UPDATE secret_vault
22478
+ SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
22479
+ WHERE pointer_id = :pointerId`
22480
+ );
22481
+ this.derefStmt = db.prepare(
22482
+ `INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
22483
+ VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
22484
+ );
22485
+ }
22486
+ db;
22487
+ insertStmt;
22488
+ bumpStmt;
22489
+ byPointerStmt;
22490
+ byFingerprintStmt;
22491
+ listStmt;
22492
+ replaceCiphertextStmt;
22493
+ refreshFingerprintStmt;
22494
+ derefStmt;
22495
+ /**
22496
+ * Vault a value, or record another sighting of one already vaulted. Keyed on
22497
+ * `valueFingerprint`, never on the caller's pointer id: a value seen again
22498
+ * bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
22499
+ * pointer, category and ciphertext, so the same secret always resolves to one
22500
+ * wire token. `minted` is true only when this call created the row.
22501
+ *
22502
+ * The read-then-write runs in one IMMEDIATE transaction so two concurrent
22503
+ * writers cannot both decide they are minting.
22504
+ */
22505
+ upsert(input, now) {
22506
+ let minted = false;
22507
+ withTransaction(
22508
+ this.db,
22509
+ () => {
22510
+ const existing = getRow(this.byFingerprintStmt, {
22511
+ valueFingerprint: input.valueFingerprint
22512
+ });
22513
+ if (existing === void 0) {
22514
+ this.insertStmt.run(
22515
+ bindParams({
22516
+ pointerId: input.pointerId,
22517
+ valueFingerprint: input.valueFingerprint,
22518
+ fingerprintKeyVersion: input.fingerprintKeyVersion,
22519
+ keyVersion: input.keyVersion,
22520
+ formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
22521
+ category: input.category,
22522
+ ruleId: input.ruleId,
22523
+ maskedMatch: input.maskedMatch,
22524
+ provider: input.provider,
22525
+ ciphertext: input.ciphertext,
22526
+ nonce: input.nonce,
22527
+ authTag: input.authTag,
22528
+ now
22529
+ })
22530
+ );
22531
+ minted = true;
22532
+ return;
22533
+ }
22534
+ this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
22535
+ },
22536
+ "IMMEDIATE"
22537
+ );
22538
+ const row = getRow(this.byFingerprintStmt, {
22539
+ valueFingerprint: input.valueFingerprint
22540
+ });
22541
+ if (row === void 0) throw new Error("vault: row vanished immediately after write");
22542
+ return { row: toRow(row), minted };
22543
+ }
22544
+ byPointerId(pointerId) {
22545
+ const raw = getRow(this.byPointerStmt, { pointerId });
22546
+ return raw === void 0 ? null : toRow(raw);
22547
+ }
22548
+ byValueFingerprint(fingerprint) {
22549
+ const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
22550
+ return raw === void 0 ? null : toRow(raw);
22551
+ }
22552
+ /** Append one audit row. Carries no raw value and no ciphertext, by shape. */
22553
+ recordDeref(entry) {
22554
+ this.derefStmt.run(
22555
+ bindParams({
22556
+ id: entry.id,
22557
+ pointerId: entry.pointerId,
22558
+ at: entry.at,
22559
+ target: entry.target,
22560
+ reason: entry.reason,
22561
+ outcome: entry.outcome,
22562
+ grantId: entry.grantId,
22563
+ pointerCount: entry.pointerCount ?? 1
22564
+ })
22565
+ );
22566
+ }
22567
+ listAll() {
22568
+ return allRows(this.listStmt).map(toRow);
22569
+ }
22570
+ /** Re-seal an entry under a new key epoch, leaving its identity untouched. */
22571
+ replaceCiphertext(pointerId, next) {
22572
+ this.replaceCiphertextStmt.run({ pointerId, ...next });
22573
+ }
22574
+ /** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
22575
+ refreshFingerprint(pointerId, next) {
22576
+ this.refreshFingerprintStmt.run({ pointerId, ...next });
22577
+ }
22578
+ /**
22579
+ * Destroy every vaulted value and report how many were destroyed. The deref
22580
+ * audit is left alone on purpose — see the table note above.
22581
+ */
22582
+ purgeAll() {
22583
+ let destroyed = 0;
22584
+ withTransaction(
22585
+ this.db,
22586
+ () => {
22587
+ destroyed = this.countEntries();
22588
+ this.db.exec("DELETE FROM secret_vault");
22589
+ },
22590
+ "IMMEDIATE"
22591
+ );
22592
+ return destroyed;
22593
+ }
22594
+ /**
22595
+ * Record (or re-stamp) one place a pointer has been written. One row per
22596
+ * (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
22597
+ * on hook paths — a failure must never affect the rewrite that triggered it,
22598
+ * so callers wrap this, not the other way around.
22599
+ */
22600
+ recordSighting(entry, now) {
22601
+ this.db.prepare(
22602
+ `INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
22603
+ VALUES (:id, :pointerId, :location, :kind, :now, :now)
22604
+ ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
22605
+ ).run({
22606
+ id: randomUUID7(),
22607
+ pointerId: entry.pointerId,
22608
+ location: entry.location,
22609
+ kind: entry.kind,
22610
+ now
22611
+ });
22612
+ }
22613
+ listSightings(pointerId) {
22614
+ const rows = allRows(
22615
+ this.db.prepare(
22616
+ `SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
22617
+ WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
22618
+ ),
22619
+ { pointerId }
22620
+ );
22621
+ return rows.map((r) => ({
22622
+ location: r.location,
22623
+ kind: r.kind,
22624
+ firstSeen: new Date(r.first_seen).toISOString(),
22625
+ lastSeen: new Date(r.last_seen).toISOString()
22626
+ }));
22627
+ }
22628
+ /**
22629
+ * The dashboard inventory: every vaulted value's descriptor data joined with
22630
+ * its sightings and the active reveal-to-model grant when one exists.
22631
+ * Raw-free by construction — neither the fingerprint nor the ciphertext
22632
+ * columns are selected.
22633
+ */
22634
+ listInventory(now = Date.now()) {
22635
+ const rows = allRows(
22636
+ this.db.prepare(
22637
+ `SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
22638
+ v.occurrence_count, v.first_seen, v.last_seen,
22639
+ (SELECT e.id FROM exceptions e
22640
+ WHERE e.rule_id = v.rule_id
22641
+ AND e.value_fingerprint = v.value_fingerprint
22642
+ AND e.key_version = v.fingerprint_key_version
22643
+ AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
22644
+ LIMIT 1) AS grant_id
22645
+ FROM secret_vault v
22646
+ ORDER BY v.last_seen DESC`
22647
+ ),
22648
+ { now }
22649
+ );
22650
+ return rows.map((r) => ({
22651
+ pointerId: r.pointer_id,
22652
+ category: r.category,
22653
+ ...r.provider === null ? {} : { provider: r.provider },
22654
+ maskedMatch: r.masked_match,
22655
+ occurrences: r.occurrence_count,
22656
+ firstSeen: new Date(r.first_seen).toISOString(),
22657
+ lastSeen: new Date(r.last_seen).toISOString(),
22658
+ revealGrantId: r.grant_id,
22659
+ sightings: this.listSightings(r.pointer_id)
22660
+ }));
22661
+ }
22662
+ /**
22663
+ * The de-reference trail, newest first. By default the batched, high-volume
22664
+ * reasons (display, view-render) are hidden and counted instead — the rows
22665
+ * that matter as a signal are the model crossings, and burying them under
22666
+ * render noise would defeat the audit's purpose.
22667
+ */
22668
+ listDerefs(opts) {
22669
+ const limit = opts?.limit ?? 200;
22670
+ const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
22671
+ const rows = allRows(
22672
+ this.db.prepare(
22673
+ `SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
22674
+ FROM secret_vault_deref ${where}
22675
+ ORDER BY at DESC, rowid DESC LIMIT :limit`
22676
+ ),
22677
+ { limit }
22678
+ );
22679
+ const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
22680
+ this.db,
22681
+ `SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
22682
+ );
22683
+ return {
22684
+ rows: rows.map((r) => ({
22685
+ id: r.id,
22686
+ pointerId: r.pointer_id,
22687
+ at: new Date(r.at).toISOString(),
22688
+ target: r.target,
22689
+ reason: r.reason,
22690
+ outcome: r.outcome,
22691
+ ...r.grant_id === null ? {} : { grantId: r.grant_id },
22692
+ pointerCount: r.pointer_count
22693
+ })),
22694
+ hiddenBatched
22695
+ };
22696
+ }
22697
+ countEntries() {
22698
+ return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
22699
+ }
22700
+ };
22701
+
22245
22702
  // ../../packages/persistence/src/repositories/security.ts
22246
22703
  var DAY_MS4 = 864e5;
22247
22704
  var SEVERITIES = ["critical", "high", "medium", "low"];
@@ -22587,7 +23044,7 @@ var SqliteSecurityRepository = class {
22587
23044
  };
22588
23045
 
22589
23046
  // ../../packages/persistence/src/repositories/shares.ts
22590
- import { randomUUID as randomUUID7 } from "crypto";
23047
+ import { randomUUID as randomUUID8 } from "crypto";
22591
23048
  var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
22592
23049
  var IN_CHUNK = 500;
22593
23050
  var KIND_ORDER = ["provider", "internal", "external", "ip"];
@@ -22843,7 +23300,7 @@ var SqliteSharesRepository = class {
22843
23300
  (id, destination_id, host, decision, created_at, updated_at)
22844
23301
  VALUES (:id, :destinationId, :host, :decision, :now, :now)`
22845
23302
  ).run({
22846
- id: randomUUID7(),
23303
+ id: randomUUID8(),
22847
23304
  destinationId,
22848
23305
  host: dest.host,
22849
23306
  decision,
@@ -22992,7 +23449,7 @@ var SqliteSharesRepository = class {
22992
23449
  let destinationId = destIds.get(hit.host);
22993
23450
  if (destinationId === void 0) {
22994
23451
  destStmt.run({
22995
- id: randomUUID7(),
23452
+ id: randomUUID8(),
22996
23453
  kind: hit.kind,
22997
23454
  name: hit.name,
22998
23455
  host: hit.host,
@@ -23008,7 +23465,7 @@ var SqliteSharesRepository = class {
23008
23465
  let endpointId = endpointIds.get(endpointKey);
23009
23466
  if (endpointId === void 0) {
23010
23467
  endpointStmt.run({
23011
- id: randomUUID7(),
23468
+ id: randomUUID8(),
23012
23469
  destinationId,
23013
23470
  method: hit.method,
23014
23471
  transport: hit.transport,
@@ -23021,7 +23478,7 @@ var SqliteSharesRepository = class {
23021
23478
  endpointIds.set(endpointKey, endpointId);
23022
23479
  }
23023
23480
  siteStmt.run({
23024
- id: randomUUID7(),
23481
+ id: randomUUID8(),
23025
23482
  endpointId,
23026
23483
  project: input.project,
23027
23484
  projectKey: input.projectKey,
@@ -23437,6 +23894,7 @@ function openAndInitialize(file2) {
23437
23894
  policies,
23438
23895
  installedPacks,
23439
23896
  scanLedger: new SqliteScanLedgerRepository(db),
23897
+ secretVault: new SqliteSecretVaultRepository(db),
23440
23898
  exceptions: new SqliteExceptionsRepository(db),
23441
23899
  resolutions: new SqliteResolutionsRepository(db),
23442
23900
  ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
@@ -23472,6 +23930,7 @@ function openLocalDatabase(dir) {
23472
23930
  policies,
23473
23931
  installedPacks,
23474
23932
  scanLedger,
23933
+ secretVault,
23475
23934
  exceptions,
23476
23935
  resolutions,
23477
23936
  ruleProbeCache,
@@ -23580,7 +24039,7 @@ function openLocalDatabase(dir) {
23580
24039
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
23581
24040
  if (!definitionId) continue;
23582
24041
  inspectionFindings.insertFinding({
23583
- id: randomUUID8(),
24042
+ id: randomUUID9(),
23584
24043
  auditEventId: record2.scanEvent.id,
23585
24044
  inspectionDefinitionId: definitionId,
23586
24045
  span: finding.span,
@@ -23657,6 +24116,7 @@ function openLocalDatabase(dir) {
23657
24116
  policies,
23658
24117
  installedPacks,
23659
24118
  scanLedger,
24119
+ secretVault,
23660
24120
  exceptions,
23661
24121
  resolutions,
23662
24122
  ruleProbeCache,
@@ -23727,7 +24187,11 @@ function parseKeyFile(raw) {
23727
24187
  }
23728
24188
  return { version: version2, material: bytes };
23729
24189
  }
23730
- var KEY_VERSION_TABLES = ["exceptions", "blocked_detections"];
24190
+ var KEY_VERSION_COLUMNS = {
24191
+ exceptions: "key_version",
24192
+ blocked_detections: "key_version",
24193
+ secret_vault: "fingerprint_key_version"
24194
+ };
23731
24195
  var SQLITE_ERROR = 1;
23732
24196
  var FLOOR_BUSY_TIMEOUT_MS = 250;
23733
24197
  var FloorUnreadableError = class extends Error {
@@ -23748,10 +24212,10 @@ function storedKeyVersionFloor(dataDir2) {
23748
24212
  db = new DatabaseSync2(file2, { readOnly: true });
23749
24213
  db.exec(`PRAGMA busy_timeout = ${String(FLOOR_BUSY_TIMEOUT_MS)}`);
23750
24214
  let floor = 0;
23751
- for (const table2 of KEY_VERSION_TABLES) {
24215
+ for (const [table2, column] of Object.entries(KEY_VERSION_COLUMNS)) {
23752
24216
  try {
23753
24217
  const row = getRow(
23754
- db.prepare(`SELECT MAX(key_version) AS v FROM ${table2}`)
24218
+ db.prepare(`SELECT MAX(${column}) AS v FROM ${table2}`)
23755
24219
  );
23756
24220
  floor = Math.max(floor, row?.v ?? 0);
23757
24221
  } catch (err) {
@@ -23858,16 +24322,42 @@ function readJson(file2) {
23858
24322
  return parseJsonObject(text) ?? null;
23859
24323
  }
23860
24324
 
23861
- // ../../packages/persistence/src/warn-era-cap.ts
23862
- import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
24325
+ // ../../packages/persistence/src/vault/crypto.ts
24326
+ import {
24327
+ createCipheriv,
24328
+ createDecipheriv,
24329
+ createHmac as createHmac2,
24330
+ hkdfSync,
24331
+ timingSafeEqual
24332
+ } from "crypto";
24333
+
24334
+ // ../../packages/persistence/src/vault/key-provider.ts
24335
+ import { execFileSync } from "child_process";
24336
+ import { randomBytes as randomBytes2 } from "crypto";
24337
+ import {
24338
+ chmodSync as chmodSync2,
24339
+ mkdirSync as mkdirSync2,
24340
+ readFileSync as readFileSync3,
24341
+ renameSync as renameSync4,
24342
+ rmSync as rmSync3,
24343
+ statSync,
24344
+ writeFileSync as writeFileSync2
24345
+ } from "fs";
23863
24346
  import { join as join5 } from "path";
24347
+
24348
+ // ../../packages/persistence/src/vault/vault.ts
24349
+ import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
24350
+
24351
+ // ../../packages/persistence/src/warn-era-cap.ts
24352
+ import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
24353
+ import { join as join6 } from "path";
23864
24354
  var MARKER = "warn-era-capped";
23865
24355
  function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
23866
24356
  if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
23867
- const marker = join5(dataDir2, MARKER);
24357
+ const marker = join6(dataDir2, MARKER);
23868
24358
  if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
23869
24359
  const capped = db.policies.capCategoryActions();
23870
- writeFileSync2(marker, `${new Date(Date.now()).toISOString()}
24360
+ writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
23871
24361
  `, { mode: DATA_FILE_MODE });
23872
24362
  return { capped };
23873
24363
  }
@@ -23924,7 +24414,7 @@ function resolveProvider() {
23924
24414
  function loadConfig(base = defaultDataDir()) {
23925
24415
  try {
23926
24416
  ensureLayoutDirSync(base);
23927
- const settingsFile = join6(settingsDir(base), "settings.json");
24417
+ const settingsFile = join7(settingsDir(base), "settings.json");
23928
24418
  if (existsSync4(settingsFile)) tightenFile(settingsFile);
23929
24419
  } catch {
23930
24420
  }
@@ -23948,9 +24438,9 @@ function resolveProviderSafe() {
23948
24438
  }
23949
24439
 
23950
24440
  // ../../packages/plugin-sdk/src/config-inventory.ts
23951
- import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as statSync2 } from "fs";
24441
+ import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
23952
24442
  import { homedir as homedir2 } from "os";
23953
- import { basename as basename2, join as join8 } from "path";
24443
+ import { basename as basename2, join as join9 } from "path";
23954
24444
 
23955
24445
  // ../../packages/detections/src/egress/registry.ts
23956
24446
  var EXTRACTOR_VERSION = "1";
@@ -25053,9 +25543,9 @@ function extractGoMod(text) {
25053
25543
  let blockKeyword = null;
25054
25544
  eachLine(text, (rawLine, lineNumber) => {
25055
25545
  if (blockKeyword === null) {
25056
- const open = GO_BLOCK_OPEN.exec(rawLine)?.[1];
25057
- if (open !== void 0) {
25058
- blockKeyword = open;
25546
+ const open2 = GO_BLOCK_OPEN.exec(rawLine)?.[1];
25547
+ if (open2 !== void 0) {
25548
+ blockKeyword = open2;
25059
25549
  return;
25060
25550
  }
25061
25551
  const path = GO_REQUIRE_SINGLE_LINE.exec(rawLine)?.[1];
@@ -25573,12 +26063,12 @@ function redact(text, findings) {
25573
26063
  const regions = [];
25574
26064
  for (const f of sorted) {
25575
26065
  const rank = SEVERITY_RANK2[f.severity];
25576
- const open = regions[regions.length - 1];
25577
- if (open && f.span.start < open.end) {
25578
- open.end = Math.max(open.end, f.span.end);
25579
- if (rank > open.rank) {
25580
- open.rank = rank;
25581
- open.category = f.category;
26066
+ const open2 = regions[regions.length - 1];
26067
+ if (open2 && f.span.start < open2.end) {
26068
+ open2.end = Math.max(open2.end, f.span.end);
26069
+ if (rank > open2.rank) {
26070
+ open2.rank = rank;
26071
+ open2.category = f.category;
25582
26072
  }
25583
26073
  } else {
25584
26074
  regions.push({ start: f.span.start, end: f.span.end, category: f.category, rank });
@@ -25607,6 +26097,24 @@ function maskMatch(raw) {
25607
26097
  return `${raw.charAt(0)}${"*".repeat(6)}${raw.charAt(raw.length - 1)}`;
25608
26098
  }
25609
26099
 
26100
+ // ../../packages/detections/src/pointer-shield.ts
26101
+ function shieldPointers(text) {
26102
+ const spans = [];
26103
+ let out = null;
26104
+ for (const match of text.matchAll(pointerTokenScanner())) {
26105
+ spans.push({ start: match.index, end: match.index + match[0].length });
26106
+ out ??= text;
26107
+ out = out.slice(0, match.index) + " ".repeat(match[0].length) + out.slice(match.index + match[0].length);
26108
+ }
26109
+ return { text: out ?? text, spans };
26110
+ }
26111
+ function dropShieldedFindings(findings, spans) {
26112
+ if (spans.length === 0) return findings;
26113
+ return findings.filter(
26114
+ (finding) => !spans.some((s) => finding.span.start < s.end && finding.span.end > s.start)
26115
+ );
26116
+ }
26117
+
25610
26118
  // ../../packages/detections/src/posture/config-posture.ts
25611
26119
  var RULE_VERSION = "1";
25612
26120
  var KNOWN_TOOLS = /* @__PURE__ */ new Set([
@@ -27803,8 +28311,8 @@ function bundledDetections() {
27803
28311
  }
27804
28312
 
27805
28313
  // ../../packages/plugin-sdk/src/repo.ts
27806
- import { existsSync as existsSync5, readFileSync as readFileSync3, statSync } from "fs";
27807
- import { basename, dirname, isAbsolute, join as join7, sep as sep2 } from "path";
28314
+ import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
28315
+ import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
27808
28316
  function resolveRepoIdentity(cwd) {
27809
28317
  try {
27810
28318
  const root = findGitRoot(cwd);
@@ -27833,36 +28341,36 @@ function resolveWorktreeRoot(cwd) {
27833
28341
  function findGitRoot(start) {
27834
28342
  let dir = start;
27835
28343
  for (; ; ) {
27836
- if (existsSync5(join7(dir, ".git"))) return dir;
28344
+ if (existsSync5(join8(dir, ".git"))) return dir;
27837
28345
  const parent = dirname(dir);
27838
28346
  if (parent === dir) return void 0;
27839
28347
  dir = parent;
27840
28348
  }
27841
28349
  }
27842
28350
  function resolveGitContext(root) {
27843
- const dotGit = join7(root, ".git");
28351
+ const dotGit = join8(root, ".git");
27844
28352
  try {
27845
- if (statSync(dotGit).isDirectory()) {
27846
- return { configPath: join7(dotGit, "config"), headRoot: root };
28353
+ if (statSync2(dotGit).isDirectory()) {
28354
+ return { configPath: join8(dotGit, "config"), headRoot: root };
27847
28355
  }
27848
28356
  } catch {
27849
28357
  return void 0;
27850
28358
  }
27851
28359
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
27852
28360
  if (!target) return void 0;
27853
- const gitdir = isAbsolute(target) ? target : join7(root, target);
27854
- if (existsSync5(join7(gitdir, "config"))) {
27855
- return { configPath: join7(gitdir, "config"), headRoot: root };
28361
+ const gitdir = isAbsolute(target) ? target : join8(root, target);
28362
+ if (existsSync5(join8(gitdir, "config"))) {
28363
+ return { configPath: join8(gitdir, "config"), headRoot: root };
27856
28364
  }
27857
- const commonRaw = safeRead(join7(gitdir, "commondir"))?.trim();
28365
+ const commonRaw = safeRead(join8(gitdir, "commondir"))?.trim();
27858
28366
  if (!commonRaw) return void 0;
27859
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join7(gitdir, commonRaw);
28367
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join8(gitdir, commonRaw);
27860
28368
  const headRoot = basename(commonGitDir) === ".git" ? dirname(commonGitDir) : root;
27861
- return { configPath: join7(commonGitDir, "config"), headRoot };
28369
+ return { configPath: join8(commonGitDir, "config"), headRoot };
27862
28370
  }
27863
28371
  function safeRead(path) {
27864
28372
  try {
27865
- return readFileSync3(path, "utf8");
28373
+ return readFileSync4(path, "utf8");
27866
28374
  } catch {
27867
28375
  return void 0;
27868
28376
  }
@@ -27900,13 +28408,13 @@ function slugFromUrl(url2) {
27900
28408
  }
27901
28409
 
27902
28410
  // ../../packages/plugin-sdk/src/events.ts
27903
- import { createHash as createHash4, randomUUID as randomUUID9 } from "crypto";
28411
+ import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
27904
28412
  function contentHashOf(text) {
27905
28413
  return createHash4("sha256").update(text).digest("hex");
27906
28414
  }
27907
28415
  function buildIngestEvent(input) {
27908
28416
  return {
27909
- id: randomUUID9(),
28417
+ id: randomUUID11(),
27910
28418
  sourceTool: input.sourceTool,
27911
28419
  kind: input.kind,
27912
28420
  occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -27917,7 +28425,7 @@ function buildIngestEvent(input) {
27917
28425
  // SDK boot in the fail-open hook path). Preserve any id the caller already set.
27918
28426
  metadata: {
27919
28427
  ...input.metadata,
27920
- correlationId: input.metadata?.correlationId ?? randomUUID9()
28428
+ correlationId: input.metadata?.correlationId ?? randomUUID11()
27921
28429
  }
27922
28430
  };
27923
28431
  }
@@ -27926,8 +28434,8 @@ function buildIngestEvent(input) {
27926
28434
  import { arch, hostname as hostname3, platform, release } from "os";
27927
28435
 
27928
28436
  // ../../packages/plugin-sdk/src/nudge.ts
27929
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
27930
- import { join as join9 } from "path";
28437
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
28438
+ import { join as join10 } from "path";
27931
28439
 
27932
28440
  // ../../packages/plugin-sdk/src/paths.ts
27933
28441
  import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
@@ -27965,8 +28473,8 @@ function resolveNonGitProject(startDir, recognizeMarker) {
27965
28473
 
27966
28474
  // ../../packages/plugin-sdk/src/project-files.ts
27967
28475
  var import_ignore = __toESM(require_ignore(), 1);
27968
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
27969
- import { basename as basename4, join as join10, relative, sep as sep4 } from "path";
28476
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
28477
+ import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
27970
28478
 
27971
28479
  // ../../packages/plugin-sdk/src/rule-quarantine.ts
27972
28480
  var PASS_BUDGET_MS = 2e3;
@@ -28022,7 +28530,7 @@ async function filterUnsafeRules(rules, gateway, opts) {
28022
28530
  }
28023
28531
 
28024
28532
  // ../../packages/plugin-sdk/src/runtime.ts
28025
- import { randomUUID as randomUUID10 } from "crypto";
28533
+ import { randomUUID as randomUUID12 } from "crypto";
28026
28534
  var ENFORCEMENT_CEILING_ENABLED = false;
28027
28535
  var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
28028
28536
  function entryIsActive(entry, now) {
@@ -28125,7 +28633,12 @@ function createPluginRuntime(gateway, settings, opts) {
28125
28633
  if (worst === "block") return { action: "block", text: null, findings };
28126
28634
  if (worst === "redact") {
28127
28635
  const redactFindings = findings.filter((f) => actionFor(f) === "redact");
28128
- return { action: "redact", text: redact(text, redactFindings), findings };
28636
+ return {
28637
+ action: "redact",
28638
+ text: redact(text, redactFindings),
28639
+ findings,
28640
+ enforcedFindings: redactFindings
28641
+ };
28129
28642
  }
28130
28643
  return { action: worst, text, findings };
28131
28644
  }
@@ -28166,9 +28679,17 @@ function createPluginRuntime(gateway, settings, opts) {
28166
28679
  else groups.set(pair, [finding]);
28167
28680
  }
28168
28681
  const now = Date.now();
28682
+ const preAuthorized = new Set(ctx.preAuthorizedGrantIds ?? []);
28169
28683
  for (const [pair, group] of groups) {
28170
28684
  const entry = entries.get(pair);
28171
- if (!entry || !entryIsActive(entry, now) || !conditionsMatch(entry.conditions, ctx)) {
28685
+ if (!entry) continue;
28686
+ if (preAuthorized.has(entry.id)) {
28687
+ if (!conditionsMatch(entry.conditions, ctx)) continue;
28688
+ for (const finding of group) excepted.add(finding);
28689
+ exceptionIds.push(entry.id);
28690
+ continue;
28691
+ }
28692
+ if (!entryIsActive(entry, now) || !conditionsMatch(entry.conditions, ctx)) {
28172
28693
  continue;
28173
28694
  }
28174
28695
  let consumed = false;
@@ -28200,7 +28721,7 @@ function createPluginRuntime(gateway, settings, opts) {
28200
28721
  const pair = `${finding.ruleId}:${fp}`;
28201
28722
  if (seen.has(pair)) continue;
28202
28723
  seen.add(pair);
28203
- const reference = randomUUID10().replaceAll("-", "").slice(0, 6);
28724
+ const reference = randomUUID12().replaceAll("-", "").slice(0, 6);
28204
28725
  const maskedValue = maskMatch(finding.rawMatch);
28205
28726
  try {
28206
28727
  await gateway.recordBlockedDetection({
@@ -28224,7 +28745,8 @@ function createPluginRuntime(gateway, settings, opts) {
28224
28745
  async function evaluate2(text, context, ctx) {
28225
28746
  try {
28226
28747
  await ensureInitialized();
28227
- const findings = scan(text, rules, context);
28748
+ const shielded = shieldPointers(text);
28749
+ const findings = dropShieldedFindings(scan(shielded.text, rules, context), shielded.spans);
28228
28750
  const fpCache = /* @__PURE__ */ new Map();
28229
28751
  const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
28230
28752
  const decision = decide(findings, text, excepted);
@@ -28247,7 +28769,11 @@ function createPluginRuntime(gateway, settings, opts) {
28247
28769
  const { decision, excepted, exceptionIds } = await evaluate2(
28248
28770
  input.text,
28249
28771
  filePath ? { filePath } : void 0,
28250
- { sourceTool: input.sourceTool, metadata: input.metadata }
28772
+ {
28773
+ sourceTool: input.sourceTool,
28774
+ metadata: input.metadata,
28775
+ preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
28776
+ }
28251
28777
  );
28252
28778
  if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
28253
28779
  try {
@@ -28277,7 +28803,7 @@ function createPluginRuntime(gateway, settings, opts) {
28277
28803
  valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
28278
28804
  }) : void 0;
28279
28805
  return {
28280
- id: randomUUID10(),
28806
+ id: randomUUID12(),
28281
28807
  eventId: event.id,
28282
28808
  ruleId: match.ruleId,
28283
28809
  category: match.category,
@@ -28303,7 +28829,7 @@ function createPluginRuntime(gateway, settings, opts) {
28303
28829
  const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
28304
28830
  return contentHashOf(JSON.stringify(sorted));
28305
28831
  } catch {
28306
- return `unresolved-${randomUUID10()}`;
28832
+ return `unresolved-${randomUUID12()}`;
28307
28833
  }
28308
28834
  }
28309
28835
  async function close() {
@@ -28316,12 +28842,12 @@ function createPluginRuntime(gateway, settings, opts) {
28316
28842
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
28317
28843
 
28318
28844
  // ../../packages/plugin-sdk/src/throttle.ts
28319
- import { mkdirSync as mkdirSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
28320
- import { join as join11 } from "path";
28845
+ import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
28846
+ import { join as join12 } from "path";
28321
28847
 
28322
28848
  // ../../packages/scanner/src/discover.ts
28323
28849
  import { readdirSync as readdirSync4 } from "fs";
28324
- import { join as join12 } from "path";
28850
+ import { join as join13 } from "path";
28325
28851
 
28326
28852
  // ../../packages/scanner/src/constants.ts
28327
28853
  var COMMON_SKIP_DIRS = ["node_modules", "__pycache__", ".venv", "venv", ".cache"];
@@ -28366,7 +28892,7 @@ function discoverGitRepos(opts) {
28366
28892
  if (!entry.isDirectory()) continue;
28367
28893
  if (DISCOVER_SKIP.has(entry.name)) continue;
28368
28894
  if (entry.name.startsWith(".")) continue;
28369
- visit(join12(dir, entry.name), depth + 1);
28895
+ visit(join13(dir, entry.name), depth + 1);
28370
28896
  }
28371
28897
  }
28372
28898
  for (const root of searchRoots) {
@@ -28469,11 +28995,11 @@ function renderMultiRepoSummary(summary, opts = {}) {
28469
28995
  }
28470
28996
 
28471
28997
  // ../../packages/scanner/src/scan.ts
28472
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
28998
+ import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
28473
28999
  import { extname as extname2, isAbsolute as isAbsolute2, relative as relative4 } from "path";
28474
29000
 
28475
29001
  // ../../packages/plugin-runtime/src/standalone-gateway.ts
28476
- import { randomUUID as randomUUID11 } from "crypto";
29002
+ import { randomUUID as randomUUID13 } from "crypto";
28477
29003
 
28478
29004
  // ../../packages/plugin-runtime/src/recorder.ts
28479
29005
  var PLUGIN_RECORDER_BINARY = "plugin";
@@ -28635,7 +29161,7 @@ var StandaloneDataGateway = class {
28635
29161
  const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
28636
29162
  const installed = this.installedScanRules();
28637
29163
  const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
28638
- id: randomUUID11(),
29164
+ id: randomUUID13(),
28639
29165
  scope: "global",
28640
29166
  target: { ruleId },
28641
29167
  action,
@@ -28788,16 +29314,16 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
28788
29314
  }
28789
29315
 
28790
29316
  // ../../packages/plugin-runtime/src/handle-session-start.ts
28791
- import { randomUUID as randomUUID12 } from "crypto";
29317
+ import { randomUUID as randomUUID14 } from "crypto";
28792
29318
  var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
28793
29319
 
28794
29320
  // ../../packages/scanner/src/manifests.ts
28795
- import { statSync as statSync5 } from "fs";
29321
+ import { statSync as statSync6 } from "fs";
28796
29322
 
28797
29323
  // ../../packages/scanner/src/walk.ts
28798
29324
  var import_ignore2 = __toESM(require_ignore(), 1);
28799
- import { readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
28800
- import { extname, join as join13, relative as relative3, sep as sep5 } from "path";
29325
+ import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
29326
+ import { extname, join as join14, relative as relative3, sep as sep5 } from "path";
28801
29327
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
28802
29328
  ".ts",
28803
29329
  ".tsx",
@@ -28829,7 +29355,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
28829
29355
  var DEFAULT_MAX_BYTES = 512 * 1024;
28830
29356
  function readIgnoreLayer(dir, filename) {
28831
29357
  try {
28832
- const content = readFileSync7(join13(dir, filename), "utf8");
29358
+ const content = readFileSync8(join14(dir, filename), "utf8");
28833
29359
  return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
28834
29360
  } catch {
28835
29361
  return void 0;
@@ -28861,7 +29387,7 @@ function* walkTree(rootDir, opts = {}) {
28861
29387
  const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
28862
29388
  for (const entry of dirents) {
28863
29389
  const name = entry.name;
28864
- const fullPath = join13(dir, name);
29390
+ const fullPath = join14(dir, name);
28865
29391
  if (entry.isDirectory()) {
28866
29392
  const skipState = evaluate(dirSkipLayers, fullPath, true);
28867
29393
  if (skipState !== "unignored" && (SKIP_DIRS.has(name) || skipState === "ignored")) {
@@ -28895,7 +29421,7 @@ function* walkSourceFiles(opts = {}) {
28895
29421
  let size;
28896
29422
  let mtime;
28897
29423
  try {
28898
- const st = statSync4(file2.path);
29424
+ const st = statSync5(file2.path);
28899
29425
  size = st.size;
28900
29426
  mtime = st.mtime;
28901
29427
  } catch {
@@ -28915,7 +29441,7 @@ function* walkSourceFiles(opts = {}) {
28915
29441
  if (opts.shouldRead && !opts.shouldRead(meta3)) continue;
28916
29442
  let content;
28917
29443
  try {
28918
- content = readFileSync7(file2.path, "utf8");
29444
+ content = readFileSync8(file2.path, "utf8");
28919
29445
  } catch {
28920
29446
  continue;
28921
29447
  }
@@ -28937,7 +29463,7 @@ function collectManifests(rootDir, maxFileSizeBytes = MAX_MANIFEST_BYTES) {
28937
29463
  const kind = manifestKindOf(file2.name);
28938
29464
  if (kind === null) continue;
28939
29465
  try {
28940
- const st = statSync5(file2.path);
29466
+ const st = statSync6(file2.path);
28941
29467
  if (st.size > maxFileSizeBytes) continue;
28942
29468
  found.push({ path: file2.path, kind, mtime: st.mtime.toISOString(), size: st.size });
28943
29469
  } catch {
@@ -29139,7 +29665,7 @@ function scanManifests(egress, ledger, updates, rootDir) {
29139
29665
  if (prev?.mtime === manifest.mtime) continue;
29140
29666
  let content;
29141
29667
  try {
29142
- content = readFileSync8(manifest.path, "utf8");
29668
+ content = readFileSync9(manifest.path, "utf8");
29143
29669
  } catch {
29144
29670
  continue;
29145
29671
  }