@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/commands/setup.md +123 -35
- package/hooks/hooks.json +11 -0
- package/package.json +4 -4
- package/scripts/apply-suppressions.js +581 -69
- package/scripts/backfill.js +1954 -147
- package/scripts/filescan.js +602 -76
- package/scripts/firstrun.js +517 -34
- package/scripts/intro.js +176 -20
- package/scripts/message-display.js +28945 -0
- package/scripts/onboard.js +542 -30
- package/scripts/post-tool-use.js +1943 -139
- package/scripts/pre-tool-use.js +2114 -163
- package/scripts/query.js +522 -35
- package/scripts/reconcile.js +2082 -242
- package/scripts/remediate.js +1872 -104
- package/scripts/session-start.js +680 -112
- package/scripts/start-light.js +174 -18
- package/scripts/statusline.js +517 -34
- package/scripts/stop.js +185 -29
- package/scripts/user-prompt-submit.js +1910 -152
package/scripts/query.js
CHANGED
|
@@ -492,7 +492,7 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/persistence/src/database.ts
|
|
495
|
-
import { randomUUID as
|
|
495
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
496
496
|
import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
497
497
|
import { join, sep } from "path";
|
|
498
498
|
import { DatabaseSync } from "node:sqlite";
|
|
@@ -558,6 +558,22 @@ var SQLITE_MIGRATIONS = [
|
|
|
558
558
|
{
|
|
559
559
|
tag: "0014_drop_legacy_events_findings",
|
|
560
560
|
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"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
tag: "0015_busy_vengeance",
|
|
564
|
+
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`);"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
tag: "0016_breezy_zodiak",
|
|
568
|
+
sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
tag: "0017_rainy_kat_farrell",
|
|
572
|
+
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`);"
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
tag: "0018_serious_tana_nile",
|
|
576
|
+
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
561
577
|
}
|
|
562
578
|
];
|
|
563
579
|
|
|
@@ -16274,6 +16290,7 @@ var ExceptionConditions = external_exports.object({
|
|
|
16274
16290
|
sourceTool: external_exports.string().optional(),
|
|
16275
16291
|
provider: external_exports.string().optional()
|
|
16276
16292
|
}).strict();
|
|
16293
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16277
16294
|
var DetectionException = external_exports.object({
|
|
16278
16295
|
id: external_exports.guid(),
|
|
16279
16296
|
ruleId: external_exports.string(),
|
|
@@ -16290,6 +16307,7 @@ var DetectionException = external_exports.object({
|
|
|
16290
16307
|
keyVersion: external_exports.number().int().positive(),
|
|
16291
16308
|
// maskMatch() preview of the approved value — never the raw value.
|
|
16292
16309
|
maskedValue: external_exports.string(),
|
|
16310
|
+
capability: ExceptionCapability.default("suppress"),
|
|
16293
16311
|
scope: ExceptionScope,
|
|
16294
16312
|
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16295
16313
|
maxUses: external_exports.number().int().positive().nullable(),
|
|
@@ -16313,6 +16331,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16313
16331
|
ruleId: true,
|
|
16314
16332
|
valueFingerprint: true,
|
|
16315
16333
|
keyVersion: true,
|
|
16334
|
+
capability: true,
|
|
16316
16335
|
expiresAt: true,
|
|
16317
16336
|
maxUses: true,
|
|
16318
16337
|
useCount: true,
|
|
@@ -17417,8 +17436,117 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17417
17436
|
message: "At least one field must be provided"
|
|
17418
17437
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17419
17438
|
|
|
17439
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17440
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17441
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17442
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17443
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17444
|
+
);
|
|
17445
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17446
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17447
|
+
var ParsedPointer = external_exports.object({
|
|
17448
|
+
category: DetectionCategory,
|
|
17449
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17450
|
+
pointerId: external_exports.string(),
|
|
17451
|
+
tag: external_exports.string()
|
|
17452
|
+
});
|
|
17453
|
+
var VaultEntry = external_exports.object({
|
|
17454
|
+
pointerId: external_exports.string(),
|
|
17455
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17456
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17457
|
+
// independently of the vault encryption key below.
|
|
17458
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17459
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17460
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17461
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17462
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17463
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17464
|
+
// value always produces exactly one wire token.
|
|
17465
|
+
category: DetectionCategory,
|
|
17466
|
+
ruleId: external_exports.string(),
|
|
17467
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17468
|
+
maskedMatch: external_exports.string(),
|
|
17469
|
+
provider: external_exports.string().optional(),
|
|
17470
|
+
ciphertext: external_exports.string(),
|
|
17471
|
+
nonce: external_exports.string(),
|
|
17472
|
+
authTag: external_exports.string(),
|
|
17473
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17474
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17475
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17476
|
+
firstSeen: external_exports.string(),
|
|
17477
|
+
lastSeen: external_exports.string()
|
|
17478
|
+
});
|
|
17479
|
+
var PointerDescriptor = external_exports.object({
|
|
17480
|
+
category: DetectionCategory,
|
|
17481
|
+
provider: external_exports.string().optional(),
|
|
17482
|
+
maskedMatch: external_exports.string(),
|
|
17483
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17484
|
+
firstSeen: external_exports.string(),
|
|
17485
|
+
lastSeen: external_exports.string()
|
|
17486
|
+
});
|
|
17487
|
+
var PointerIdentity = external_exports.object({
|
|
17488
|
+
ruleId: external_exports.string(),
|
|
17489
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17490
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17491
|
+
});
|
|
17492
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17493
|
+
var VaultDerefReason = external_exports.enum([
|
|
17494
|
+
"display",
|
|
17495
|
+
"explicit-reveal",
|
|
17496
|
+
"view-render",
|
|
17497
|
+
"model-input",
|
|
17498
|
+
"remediation",
|
|
17499
|
+
"purge"
|
|
17500
|
+
]);
|
|
17501
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17502
|
+
var VaultDeref = external_exports.object({
|
|
17503
|
+
id: external_exports.guid(),
|
|
17504
|
+
pointerId: external_exports.string(),
|
|
17505
|
+
at: external_exports.string(),
|
|
17506
|
+
target: DetokenizeTarget,
|
|
17507
|
+
reason: VaultDerefReason,
|
|
17508
|
+
outcome: VaultDerefOutcome,
|
|
17509
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17510
|
+
grantId: external_exports.string().optional(),
|
|
17511
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17512
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17513
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17514
|
+
});
|
|
17515
|
+
var VaultSightingKind = external_exports.enum([
|
|
17516
|
+
"prompt",
|
|
17517
|
+
"tool-input",
|
|
17518
|
+
"tool-output",
|
|
17519
|
+
"file",
|
|
17520
|
+
"transcript"
|
|
17521
|
+
]);
|
|
17522
|
+
var VaultSighting = external_exports.object({
|
|
17523
|
+
location: external_exports.string(),
|
|
17524
|
+
kind: VaultSightingKind,
|
|
17525
|
+
firstSeen: external_exports.string(),
|
|
17526
|
+
lastSeen: external_exports.string()
|
|
17527
|
+
});
|
|
17528
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17529
|
+
pointerId: external_exports.string(),
|
|
17530
|
+
category: DetectionCategory,
|
|
17531
|
+
provider: external_exports.string().optional(),
|
|
17532
|
+
maskedMatch: external_exports.string(),
|
|
17533
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17534
|
+
firstSeen: external_exports.string(),
|
|
17535
|
+
lastSeen: external_exports.string(),
|
|
17536
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17537
|
+
// the inventory badges it, the row links to revocation.
|
|
17538
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17539
|
+
sightings: external_exports.array(VaultSighting)
|
|
17540
|
+
});
|
|
17541
|
+
var VaultKeyCustody = external_exports.string();
|
|
17542
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17543
|
+
var VaultConsent = external_exports.object({
|
|
17544
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17545
|
+
version: external_exports.number().int().positive()
|
|
17546
|
+
});
|
|
17547
|
+
|
|
17420
17548
|
// ../../packages/schema/src/zod/local.ts
|
|
17421
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17549
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17422
17550
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17423
17551
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17424
17552
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
@@ -17440,6 +17568,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17440
17568
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17441
17569
|
// Shares writes.
|
|
17442
17570
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17571
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17572
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17573
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17574
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17575
|
+
// purging the vault is the eraser.
|
|
17576
|
+
vaultConsent: VaultConsent.optional(),
|
|
17577
|
+
// Where the vault master key lives.
|
|
17578
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17579
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17580
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17443
17581
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17444
17582
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17445
17583
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -19837,6 +19975,9 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19837
19975
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19838
19976
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19839
19977
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
19978
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
19979
|
+
AND conditions IS NULL
|
|
19980
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19840
19981
|
var SqliteExceptionsRepository = class {
|
|
19841
19982
|
constructor(db) {
|
|
19842
19983
|
this.db = db;
|
|
@@ -19928,11 +20069,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19928
20069
|
this.db.prepare(
|
|
19929
20070
|
`INSERT INTO exceptions (
|
|
19930
20071
|
id, rule_id, category, value_fingerprint, key_version, masked_value,
|
|
19931
|
-
scope, expires_at, max_uses, use_count, last_used_at,
|
|
19932
|
-
conditions, created_by, created_via, created_at, updated_at
|
|
20072
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20073
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19933
20074
|
) VALUES (
|
|
19934
20075
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19935
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20076
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19936
20077
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19937
20078
|
)`
|
|
19938
20079
|
).run({
|
|
@@ -19942,6 +20083,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19942
20083
|
valueFingerprint: input.valueFingerprint,
|
|
19943
20084
|
keyVersion: input.keyVersion,
|
|
19944
20085
|
maskedValue: input.maskedValue,
|
|
20086
|
+
capability: input.capability ?? "suppress",
|
|
19945
20087
|
scope: input.scope,
|
|
19946
20088
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19947
20089
|
maxUses: input.maxUses,
|
|
@@ -20035,6 +20177,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20035
20177
|
ruleId: row.rule_id,
|
|
20036
20178
|
valueFingerprint: row.value_fingerprint,
|
|
20037
20179
|
keyVersion: row.key_version,
|
|
20180
|
+
capability: row.capability,
|
|
20038
20181
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20039
20182
|
maxUses: row.max_uses,
|
|
20040
20183
|
useCount: row.use_count,
|
|
@@ -20089,6 +20232,35 @@ var SqliteExceptionsRepository = class {
|
|
|
20089
20232
|
}))
|
|
20090
20233
|
);
|
|
20091
20234
|
}
|
|
20235
|
+
/**
|
|
20236
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20237
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20238
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20239
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20240
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20241
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20242
|
+
*
|
|
20243
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20244
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20245
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20246
|
+
*/
|
|
20247
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
|
|
20248
|
+
try {
|
|
20249
|
+
const row = getRow(
|
|
20250
|
+
this.db.prepare(
|
|
20251
|
+
`SELECT id FROM exceptions
|
|
20252
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20253
|
+
AND key_version = :keyVersion
|
|
20254
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20255
|
+
LIMIT 1`
|
|
20256
|
+
),
|
|
20257
|
+
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20258
|
+
);
|
|
20259
|
+
return Promise.resolve(row ?? null);
|
|
20260
|
+
} catch (err) {
|
|
20261
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20262
|
+
}
|
|
20263
|
+
}
|
|
20092
20264
|
/**
|
|
20093
20265
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20094
20266
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20116,6 +20288,7 @@ function parseExceptionRow(row) {
|
|
|
20116
20288
|
valueFingerprint: row.value_fingerprint,
|
|
20117
20289
|
keyVersion: row.key_version,
|
|
20118
20290
|
maskedValue: row.masked_value,
|
|
20291
|
+
capability: row.capability,
|
|
20119
20292
|
scope: row.scope,
|
|
20120
20293
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20121
20294
|
maxUses: row.max_uses,
|
|
@@ -22281,6 +22454,287 @@ var SqliteScanLedgerRepository = class {
|
|
|
22281
22454
|
}
|
|
22282
22455
|
};
|
|
22283
22456
|
|
|
22457
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22458
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22459
|
+
var SELECT_COLUMNS = `
|
|
22460
|
+
pointer_id AS pointerId,
|
|
22461
|
+
value_fingerprint AS valueFingerprint,
|
|
22462
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
22463
|
+
key_version AS keyVersion,
|
|
22464
|
+
format_version AS formatVersion,
|
|
22465
|
+
category,
|
|
22466
|
+
rule_id AS ruleId,
|
|
22467
|
+
masked_match AS maskedMatch,
|
|
22468
|
+
provider,
|
|
22469
|
+
ciphertext,
|
|
22470
|
+
nonce,
|
|
22471
|
+
auth_tag AS authTag,
|
|
22472
|
+
occurrence_count AS occurrenceCount,
|
|
22473
|
+
first_seen AS firstSeen,
|
|
22474
|
+
last_seen AS lastSeen`;
|
|
22475
|
+
function toRow(raw) {
|
|
22476
|
+
const { provider, ...rest } = raw;
|
|
22477
|
+
return provider === null ? rest : { ...rest, provider };
|
|
22478
|
+
}
|
|
22479
|
+
var SqliteSecretVaultRepository = class {
|
|
22480
|
+
constructor(db) {
|
|
22481
|
+
this.db = db;
|
|
22482
|
+
this.insertStmt = db.prepare(
|
|
22483
|
+
`INSERT INTO secret_vault (
|
|
22484
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
22485
|
+
format_version, category, rule_id, masked_match, provider,
|
|
22486
|
+
ciphertext, nonce, auth_tag,
|
|
22487
|
+
occurrence_count, first_seen, last_seen
|
|
22488
|
+
) VALUES (
|
|
22489
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
22490
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
22491
|
+
:ciphertext, :nonce, :authTag,
|
|
22492
|
+
1, :now, :now
|
|
22493
|
+
)`
|
|
22494
|
+
);
|
|
22495
|
+
this.bumpStmt = db.prepare(
|
|
22496
|
+
`UPDATE secret_vault
|
|
22497
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
22498
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
22499
|
+
);
|
|
22500
|
+
this.byPointerStmt = db.prepare(
|
|
22501
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
22502
|
+
);
|
|
22503
|
+
this.byFingerprintStmt = db.prepare(
|
|
22504
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
22505
|
+
);
|
|
22506
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
22507
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
22508
|
+
`UPDATE secret_vault
|
|
22509
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
22510
|
+
WHERE pointer_id = :pointerId`
|
|
22511
|
+
);
|
|
22512
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
22513
|
+
`UPDATE secret_vault
|
|
22514
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
22515
|
+
WHERE pointer_id = :pointerId`
|
|
22516
|
+
);
|
|
22517
|
+
this.derefStmt = db.prepare(
|
|
22518
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
22519
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
22520
|
+
);
|
|
22521
|
+
}
|
|
22522
|
+
db;
|
|
22523
|
+
insertStmt;
|
|
22524
|
+
bumpStmt;
|
|
22525
|
+
byPointerStmt;
|
|
22526
|
+
byFingerprintStmt;
|
|
22527
|
+
listStmt;
|
|
22528
|
+
replaceCiphertextStmt;
|
|
22529
|
+
refreshFingerprintStmt;
|
|
22530
|
+
derefStmt;
|
|
22531
|
+
/**
|
|
22532
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
22533
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
22534
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
22535
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
22536
|
+
* wire token. `minted` is true only when this call created the row.
|
|
22537
|
+
*
|
|
22538
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
22539
|
+
* writers cannot both decide they are minting.
|
|
22540
|
+
*/
|
|
22541
|
+
upsert(input, now) {
|
|
22542
|
+
let minted = false;
|
|
22543
|
+
withTransaction(
|
|
22544
|
+
this.db,
|
|
22545
|
+
() => {
|
|
22546
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
22547
|
+
valueFingerprint: input.valueFingerprint
|
|
22548
|
+
});
|
|
22549
|
+
if (existing === void 0) {
|
|
22550
|
+
this.insertStmt.run(
|
|
22551
|
+
bindParams({
|
|
22552
|
+
pointerId: input.pointerId,
|
|
22553
|
+
valueFingerprint: input.valueFingerprint,
|
|
22554
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
22555
|
+
keyVersion: input.keyVersion,
|
|
22556
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
22557
|
+
category: input.category,
|
|
22558
|
+
ruleId: input.ruleId,
|
|
22559
|
+
maskedMatch: input.maskedMatch,
|
|
22560
|
+
provider: input.provider,
|
|
22561
|
+
ciphertext: input.ciphertext,
|
|
22562
|
+
nonce: input.nonce,
|
|
22563
|
+
authTag: input.authTag,
|
|
22564
|
+
now
|
|
22565
|
+
})
|
|
22566
|
+
);
|
|
22567
|
+
minted = true;
|
|
22568
|
+
return;
|
|
22569
|
+
}
|
|
22570
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
22571
|
+
},
|
|
22572
|
+
"IMMEDIATE"
|
|
22573
|
+
);
|
|
22574
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
22575
|
+
valueFingerprint: input.valueFingerprint
|
|
22576
|
+
});
|
|
22577
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
22578
|
+
return { row: toRow(row), minted };
|
|
22579
|
+
}
|
|
22580
|
+
byPointerId(pointerId) {
|
|
22581
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
22582
|
+
return raw === void 0 ? null : toRow(raw);
|
|
22583
|
+
}
|
|
22584
|
+
byValueFingerprint(fingerprint) {
|
|
22585
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
22586
|
+
return raw === void 0 ? null : toRow(raw);
|
|
22587
|
+
}
|
|
22588
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
22589
|
+
recordDeref(entry) {
|
|
22590
|
+
this.derefStmt.run(
|
|
22591
|
+
bindParams({
|
|
22592
|
+
id: entry.id,
|
|
22593
|
+
pointerId: entry.pointerId,
|
|
22594
|
+
at: entry.at,
|
|
22595
|
+
target: entry.target,
|
|
22596
|
+
reason: entry.reason,
|
|
22597
|
+
outcome: entry.outcome,
|
|
22598
|
+
grantId: entry.grantId,
|
|
22599
|
+
pointerCount: entry.pointerCount ?? 1
|
|
22600
|
+
})
|
|
22601
|
+
);
|
|
22602
|
+
}
|
|
22603
|
+
listAll() {
|
|
22604
|
+
return allRows(this.listStmt).map(toRow);
|
|
22605
|
+
}
|
|
22606
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
22607
|
+
replaceCiphertext(pointerId, next) {
|
|
22608
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
22609
|
+
}
|
|
22610
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
22611
|
+
refreshFingerprint(pointerId, next) {
|
|
22612
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
22613
|
+
}
|
|
22614
|
+
/**
|
|
22615
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
22616
|
+
* audit is left alone on purpose — see the table note above.
|
|
22617
|
+
*/
|
|
22618
|
+
purgeAll() {
|
|
22619
|
+
let destroyed = 0;
|
|
22620
|
+
withTransaction(
|
|
22621
|
+
this.db,
|
|
22622
|
+
() => {
|
|
22623
|
+
destroyed = this.countEntries();
|
|
22624
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
22625
|
+
},
|
|
22626
|
+
"IMMEDIATE"
|
|
22627
|
+
);
|
|
22628
|
+
return destroyed;
|
|
22629
|
+
}
|
|
22630
|
+
/**
|
|
22631
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
22632
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
22633
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
22634
|
+
* so callers wrap this, not the other way around.
|
|
22635
|
+
*/
|
|
22636
|
+
recordSighting(entry, now) {
|
|
22637
|
+
this.db.prepare(
|
|
22638
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
22639
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22640
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22641
|
+
).run({
|
|
22642
|
+
id: randomUUID7(),
|
|
22643
|
+
pointerId: entry.pointerId,
|
|
22644
|
+
location: entry.location,
|
|
22645
|
+
kind: entry.kind,
|
|
22646
|
+
now
|
|
22647
|
+
});
|
|
22648
|
+
}
|
|
22649
|
+
listSightings(pointerId) {
|
|
22650
|
+
const rows = allRows(
|
|
22651
|
+
this.db.prepare(
|
|
22652
|
+
`SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
|
|
22653
|
+
WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
|
|
22654
|
+
),
|
|
22655
|
+
{ pointerId }
|
|
22656
|
+
);
|
|
22657
|
+
return rows.map((r) => ({
|
|
22658
|
+
location: r.location,
|
|
22659
|
+
kind: r.kind,
|
|
22660
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22661
|
+
lastSeen: new Date(r.last_seen).toISOString()
|
|
22662
|
+
}));
|
|
22663
|
+
}
|
|
22664
|
+
/**
|
|
22665
|
+
* The dashboard inventory: every vaulted value's descriptor data joined with
|
|
22666
|
+
* its sightings and the active reveal-to-model grant when one exists.
|
|
22667
|
+
* Raw-free by construction — neither the fingerprint nor the ciphertext
|
|
22668
|
+
* columns are selected.
|
|
22669
|
+
*/
|
|
22670
|
+
listInventory(now = Date.now()) {
|
|
22671
|
+
const rows = allRows(
|
|
22672
|
+
this.db.prepare(
|
|
22673
|
+
`SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
22674
|
+
v.occurrence_count, v.first_seen, v.last_seen,
|
|
22675
|
+
(SELECT e.id FROM exceptions e
|
|
22676
|
+
WHERE e.rule_id = v.rule_id
|
|
22677
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
22678
|
+
AND e.key_version = v.fingerprint_key_version
|
|
22679
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22680
|
+
LIMIT 1) AS grant_id
|
|
22681
|
+
FROM secret_vault v
|
|
22682
|
+
ORDER BY v.last_seen DESC`
|
|
22683
|
+
),
|
|
22684
|
+
{ now }
|
|
22685
|
+
);
|
|
22686
|
+
return rows.map((r) => ({
|
|
22687
|
+
pointerId: r.pointer_id,
|
|
22688
|
+
category: r.category,
|
|
22689
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
22690
|
+
maskedMatch: r.masked_match,
|
|
22691
|
+
occurrences: r.occurrence_count,
|
|
22692
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22693
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
22694
|
+
revealGrantId: r.grant_id,
|
|
22695
|
+
sightings: this.listSightings(r.pointer_id)
|
|
22696
|
+
}));
|
|
22697
|
+
}
|
|
22698
|
+
/**
|
|
22699
|
+
* The de-reference trail, newest first. By default the batched, high-volume
|
|
22700
|
+
* reasons (display, view-render) are hidden and counted instead — the rows
|
|
22701
|
+
* that matter as a signal are the model crossings, and burying them under
|
|
22702
|
+
* render noise would defeat the audit's purpose.
|
|
22703
|
+
*/
|
|
22704
|
+
listDerefs(opts) {
|
|
22705
|
+
const limit = opts?.limit ?? 200;
|
|
22706
|
+
const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
|
|
22707
|
+
const rows = allRows(
|
|
22708
|
+
this.db.prepare(
|
|
22709
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22710
|
+
FROM secret_vault_deref ${where}
|
|
22711
|
+
ORDER BY at DESC, rowid DESC LIMIT :limit`
|
|
22712
|
+
),
|
|
22713
|
+
{ limit }
|
|
22714
|
+
);
|
|
22715
|
+
const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
|
|
22716
|
+
this.db,
|
|
22717
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22718
|
+
);
|
|
22719
|
+
return {
|
|
22720
|
+
rows: rows.map((r) => ({
|
|
22721
|
+
id: r.id,
|
|
22722
|
+
pointerId: r.pointer_id,
|
|
22723
|
+
at: new Date(r.at).toISOString(),
|
|
22724
|
+
target: r.target,
|
|
22725
|
+
reason: r.reason,
|
|
22726
|
+
outcome: r.outcome,
|
|
22727
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22728
|
+
pointerCount: r.pointer_count
|
|
22729
|
+
})),
|
|
22730
|
+
hiddenBatched
|
|
22731
|
+
};
|
|
22732
|
+
}
|
|
22733
|
+
countEntries() {
|
|
22734
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22735
|
+
}
|
|
22736
|
+
};
|
|
22737
|
+
|
|
22284
22738
|
// ../../packages/persistence/src/repositories/security.ts
|
|
22285
22739
|
var DAY_MS4 = 864e5;
|
|
22286
22740
|
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
@@ -22626,7 +23080,7 @@ var SqliteSecurityRepository = class {
|
|
|
22626
23080
|
};
|
|
22627
23081
|
|
|
22628
23082
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22629
|
-
import { randomUUID as
|
|
23083
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
22630
23084
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22631
23085
|
var IN_CHUNK = 500;
|
|
22632
23086
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22882,7 +23336,7 @@ var SqliteSharesRepository = class {
|
|
|
22882
23336
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22883
23337
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22884
23338
|
).run({
|
|
22885
|
-
id:
|
|
23339
|
+
id: randomUUID8(),
|
|
22886
23340
|
destinationId,
|
|
22887
23341
|
host: dest.host,
|
|
22888
23342
|
decision,
|
|
@@ -23031,7 +23485,7 @@ var SqliteSharesRepository = class {
|
|
|
23031
23485
|
let destinationId = destIds.get(hit.host);
|
|
23032
23486
|
if (destinationId === void 0) {
|
|
23033
23487
|
destStmt.run({
|
|
23034
|
-
id:
|
|
23488
|
+
id: randomUUID8(),
|
|
23035
23489
|
kind: hit.kind,
|
|
23036
23490
|
name: hit.name,
|
|
23037
23491
|
host: hit.host,
|
|
@@ -23047,7 +23501,7 @@ var SqliteSharesRepository = class {
|
|
|
23047
23501
|
let endpointId = endpointIds.get(endpointKey);
|
|
23048
23502
|
if (endpointId === void 0) {
|
|
23049
23503
|
endpointStmt.run({
|
|
23050
|
-
id:
|
|
23504
|
+
id: randomUUID8(),
|
|
23051
23505
|
destinationId,
|
|
23052
23506
|
method: hit.method,
|
|
23053
23507
|
transport: hit.transport,
|
|
@@ -23060,7 +23514,7 @@ var SqliteSharesRepository = class {
|
|
|
23060
23514
|
endpointIds.set(endpointKey, endpointId);
|
|
23061
23515
|
}
|
|
23062
23516
|
siteStmt.run({
|
|
23063
|
-
id:
|
|
23517
|
+
id: randomUUID8(),
|
|
23064
23518
|
endpointId,
|
|
23065
23519
|
project: input.project,
|
|
23066
23520
|
projectKey: input.projectKey,
|
|
@@ -23476,6 +23930,7 @@ function openAndInitialize(file2) {
|
|
|
23476
23930
|
policies,
|
|
23477
23931
|
installedPacks,
|
|
23478
23932
|
scanLedger: new SqliteScanLedgerRepository(db),
|
|
23933
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23479
23934
|
exceptions: new SqliteExceptionsRepository(db),
|
|
23480
23935
|
resolutions: new SqliteResolutionsRepository(db),
|
|
23481
23936
|
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
@@ -23511,6 +23966,7 @@ function openLocalDatabase(dir) {
|
|
|
23511
23966
|
policies,
|
|
23512
23967
|
installedPacks,
|
|
23513
23968
|
scanLedger,
|
|
23969
|
+
secretVault,
|
|
23514
23970
|
exceptions,
|
|
23515
23971
|
resolutions,
|
|
23516
23972
|
ruleProbeCache,
|
|
@@ -23619,7 +24075,7 @@ function openLocalDatabase(dir) {
|
|
|
23619
24075
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23620
24076
|
if (!definitionId) continue;
|
|
23621
24077
|
inspectionFindings.insertFinding({
|
|
23622
|
-
id:
|
|
24078
|
+
id: randomUUID9(),
|
|
23623
24079
|
auditEventId: record2.scanEvent.id,
|
|
23624
24080
|
inspectionDefinitionId: definitionId,
|
|
23625
24081
|
span: finding.span,
|
|
@@ -23696,6 +24152,7 @@ function openLocalDatabase(dir) {
|
|
|
23696
24152
|
policies,
|
|
23697
24153
|
installedPacks,
|
|
23698
24154
|
scanLedger,
|
|
24155
|
+
secretVault,
|
|
23699
24156
|
exceptions,
|
|
23700
24157
|
resolutions,
|
|
23701
24158
|
ruleProbeCache,
|
|
@@ -23828,23 +24285,49 @@ function readJson(file2) {
|
|
|
23828
24285
|
return parseJsonObject(text) ?? null;
|
|
23829
24286
|
}
|
|
23830
24287
|
|
|
23831
|
-
// ../../packages/persistence/src/
|
|
23832
|
-
import {
|
|
24288
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
24289
|
+
import {
|
|
24290
|
+
createCipheriv,
|
|
24291
|
+
createDecipheriv,
|
|
24292
|
+
createHmac as createHmac2,
|
|
24293
|
+
hkdfSync,
|
|
24294
|
+
timingSafeEqual
|
|
24295
|
+
} from "crypto";
|
|
24296
|
+
|
|
24297
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
24298
|
+
import { execFileSync } from "child_process";
|
|
24299
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
24300
|
+
import {
|
|
24301
|
+
chmodSync as chmodSync2,
|
|
24302
|
+
mkdirSync as mkdirSync2,
|
|
24303
|
+
readFileSync as readFileSync3,
|
|
24304
|
+
renameSync as renameSync4,
|
|
24305
|
+
rmSync as rmSync3,
|
|
24306
|
+
statSync,
|
|
24307
|
+
writeFileSync as writeFileSync2
|
|
24308
|
+
} from "fs";
|
|
23833
24309
|
import { join as join5 } from "path";
|
|
24310
|
+
|
|
24311
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
24312
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
|
|
24313
|
+
|
|
24314
|
+
// ../../packages/persistence/src/warn-era-cap.ts
|
|
24315
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
24316
|
+
import { join as join6 } from "path";
|
|
23834
24317
|
var MARKER = "warn-era-capped";
|
|
23835
24318
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
23836
24319
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
23837
|
-
const marker =
|
|
24320
|
+
const marker = join6(dataDir2, MARKER);
|
|
23838
24321
|
if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
|
|
23839
24322
|
const capped = db.policies.capCategoryActions();
|
|
23840
|
-
|
|
24323
|
+
writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
|
|
23841
24324
|
`, { mode: DATA_FILE_MODE });
|
|
23842
24325
|
return { capped };
|
|
23843
24326
|
}
|
|
23844
24327
|
|
|
23845
24328
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23846
24329
|
import { existsSync as existsSync4 } from "fs";
|
|
23847
|
-
import { join as
|
|
24330
|
+
import { join as join7 } from "path";
|
|
23848
24331
|
|
|
23849
24332
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
23850
24333
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -23898,7 +24381,7 @@ function resolveProvider() {
|
|
|
23898
24381
|
function loadConfig(base = defaultDataDir()) {
|
|
23899
24382
|
try {
|
|
23900
24383
|
ensureLayoutDirSync(base);
|
|
23901
|
-
const settingsFile =
|
|
24384
|
+
const settingsFile = join7(settingsDir(base), "settings.json");
|
|
23902
24385
|
if (existsSync4(settingsFile)) tightenFile(settingsFile);
|
|
23903
24386
|
} catch {
|
|
23904
24387
|
}
|
|
@@ -23922,9 +24405,9 @@ function resolveProviderSafe() {
|
|
|
23922
24405
|
}
|
|
23923
24406
|
|
|
23924
24407
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23925
|
-
import { readdirSync, readFileSync as
|
|
24408
|
+
import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
23926
24409
|
import { homedir as homedir2 } from "os";
|
|
23927
|
-
import { basename as basename2, join as
|
|
24410
|
+
import { basename as basename2, join as join9 } from "path";
|
|
23928
24411
|
|
|
23929
24412
|
// ../../packages/detections/src/egress/registry.ts
|
|
23930
24413
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -26660,18 +27143,18 @@ function bundledDetections() {
|
|
|
26660
27143
|
}
|
|
26661
27144
|
|
|
26662
27145
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
26663
|
-
import { existsSync as existsSync5, readFileSync as
|
|
26664
|
-
import { basename, dirname, isAbsolute, join as
|
|
27146
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
27147
|
+
import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
|
|
26665
27148
|
|
|
26666
27149
|
// ../../packages/plugin-sdk/src/events.ts
|
|
26667
|
-
import { createHash as createHash4, randomUUID as
|
|
27150
|
+
import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
|
|
26668
27151
|
|
|
26669
27152
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
26670
27153
|
import { arch, hostname as hostname3, platform, release } from "os";
|
|
26671
27154
|
|
|
26672
27155
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
26673
|
-
import { mkdirSync as
|
|
26674
|
-
import { join as
|
|
27156
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
27157
|
+
import { join as join10 } from "path";
|
|
26675
27158
|
|
|
26676
27159
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
26677
27160
|
import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
|
|
@@ -26679,21 +27162,21 @@ import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
|
|
|
26679
27162
|
|
|
26680
27163
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
26681
27164
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
26682
|
-
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as
|
|
26683
|
-
import { basename as basename4, join as
|
|
27165
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
27166
|
+
import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
|
|
26684
27167
|
|
|
26685
27168
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
26686
|
-
import { randomUUID as
|
|
27169
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
26687
27170
|
|
|
26688
27171
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
26689
27172
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
26690
27173
|
|
|
26691
27174
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
26692
|
-
import { mkdirSync as
|
|
26693
|
-
import { join as
|
|
27175
|
+
import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
27176
|
+
import { join as join12 } from "path";
|
|
26694
27177
|
|
|
26695
27178
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
26696
|
-
import { randomUUID as
|
|
27179
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
26697
27180
|
|
|
26698
27181
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
26699
27182
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -26855,7 +27338,7 @@ var StandaloneDataGateway = class {
|
|
|
26855
27338
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
26856
27339
|
const installed = this.installedScanRules();
|
|
26857
27340
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
26858
|
-
id:
|
|
27341
|
+
id: randomUUID13(),
|
|
26859
27342
|
scope: "global",
|
|
26860
27343
|
target: { ruleId },
|
|
26861
27344
|
action,
|
|
@@ -27008,7 +27491,7 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
27008
27491
|
}
|
|
27009
27492
|
|
|
27010
27493
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
27011
|
-
import { randomUUID as
|
|
27494
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
27012
27495
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
27013
27496
|
|
|
27014
27497
|
// src/present.ts
|
|
@@ -27217,8 +27700,8 @@ function renderStatusBar(s, opts = {}) {
|
|
|
27217
27700
|
const score = `${dot} health ${paint.bold(String(s.score))}${paint.dim("/100")}`;
|
|
27218
27701
|
const tally = `${paint.dim("unreviewed")} ${paint.critical(sq)}${String(u.critical)} ${paint.high(sq)}${String(u.high)} ${paint.medium(sq)}${String(u.medium)} ${paint.low(sq)}${String(u.low)}`;
|
|
27219
27702
|
const flag = s.openFindings > 0 ? paint.critical("\u2691") : paint.dim("\u2691");
|
|
27220
|
-
const
|
|
27221
|
-
return `${paint.brand("\u25B8\u25B8 AKA")}${sep5}${score}${sep5}${tally}${sep5}${
|
|
27703
|
+
const open2 = `${flag} ${String(s.openFindings)} open findings`;
|
|
27704
|
+
return `${paint.brand("\u25B8\u25B8 AKA")}${sep5}${score}${sep5}${tally}${sep5}${open2}`;
|
|
27222
27705
|
}
|
|
27223
27706
|
function findingStatus(summary) {
|
|
27224
27707
|
return {
|
|
@@ -27444,7 +27927,11 @@ function renderExceptions(exceptions, nowMs = Date.now()) {
|
|
|
27444
27927
|
}
|
|
27445
27928
|
const rows = exceptions.map((e) => [
|
|
27446
27929
|
e.id.slice(0, 8),
|
|
27447
|
-
|
|
27930
|
+
// A reveal grant is strictly stronger than a plain suppression: while it is
|
|
27931
|
+
// active the model can receive this value's RAW form at tool boundaries.
|
|
27932
|
+
// Tag the row so it can never be mistaken for a suppress-only grant. The
|
|
27933
|
+
// value itself stays masked — this list shows metadata, never raw values.
|
|
27934
|
+
e.capability === "reveal_to_model" ? `${e.maskedValue} \xB7 REVEALS-TO-MODEL` : e.maskedValue,
|
|
27448
27935
|
e.ruleId,
|
|
27449
27936
|
e.scope,
|
|
27450
27937
|
relativeExpiry(e.expiresAt, nowMs),
|