@akasecurity/ai-tc-claude-code 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +670 -109
- package/scripts/backfill.js +2050 -151
- package/scripts/filescan.js +734 -116
- package/scripts/firstrun.js +607 -75
- package/scripts/intro.js +179 -22
- package/scripts/message-display.js +28945 -0
- package/scripts/onboard.js +632 -71
- package/scripts/post-tool-use.js +2035 -139
- package/scripts/pre-tool-use.js +2206 -163
- package/scripts/query.js +612 -76
- package/scripts/reconcile.js +2075 -186
- package/scripts/remediate.js +2025 -165
- package/scripts/session-start.js +770 -153
- package/scripts/start-light.js +177 -20
- package/scripts/statusline.js +607 -75
- package/scripts/stop.js +189 -32
- package/scripts/user-prompt-submit.js +2002 -152
|
@@ -492,13 +492,13 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/apply-suppressions.ts
|
|
495
|
-
import { existsSync as
|
|
495
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
|
|
496
496
|
import { userInfo } from "os";
|
|
497
|
-
import { dirname as dirname5, join as
|
|
497
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
498
498
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
499
499
|
|
|
500
500
|
// ../../packages/persistence/src/database.ts
|
|
501
|
-
import { randomUUID as
|
|
501
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
502
502
|
import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
503
503
|
import { join, sep } from "path";
|
|
504
504
|
import { DatabaseSync } from "node:sqlite";
|
|
@@ -564,6 +564,22 @@ var SQLITE_MIGRATIONS = [
|
|
|
564
564
|
{
|
|
565
565
|
tag: "0014_drop_legacy_events_findings",
|
|
566
566
|
sql: "-- Custom migration: drops the frozen legacy `events`/`findings` tables\n-- (superseded by audit_events/inspection_definitions/inspection_findings) and\n-- replaces them with read-only views of the same name.\n--\n-- persistence's migrations.ts applies this migration ONLY once the batched\n-- history backfill (see runLegacyHistoryBackfill) has fully drained both\n-- tables, and only after copying the live file aside \u2014 see\n-- backupBeforeLegacyDrop in packages/persistence/src/migrations.ts. A store\n-- still mid-copy keeps its real tables and this migration stays pending.\n--\n-- The views exist for skew: the `aka` CLI and the Claude Code plugin update\n-- independently against one shared store, so an older, already-installed\n-- binary can open a store a newer binary already dropped these tables on.\n-- Every already-shipped repository constructor prepares its SQL eagerly at\n-- open time, so a bare \"table not found\" would fail the WHOLE open, not just\n-- a findings-specific read \u2014 these views keep `prepare()` succeeding so an\n-- old binary's unrelated features keep working; its own reads stay truthful,\n-- and its rare (already fail-open) writes fail at run time instead.\n--\n-- The 0009/0010 expression indexes existed only over the legacy `events`\n-- table; DROP TABLE below would remove them implicitly, but they are\n-- dropped explicitly first so the intent reads clearly. IF EXISTS so a store\n-- that reached here with an index missing out of band (an adopted-tag store\n-- whose physical index was never built) does not throw a deterministic \"no\n-- such index\" out of the fail-open drop path.\nDROP INDEX IF EXISTS `idx_events_code_change_path`;\n--> statement-breakpoint\nDROP INDEX IF EXISTS `idx_events_session_id`;\n--> statement-breakpoint\n-- `findings.event_id` references `events.id` \u2014 drop the child first.\nDROP TABLE `findings`;\n--> statement-breakpoint\nDROP TABLE `events`;\n--> statement-breakpoint\n-- Legacy `events` shape, projected from audit_events: `kind`/`occurred_at`/\n-- `source_tool` become real columns again (source_tool round-trips through\n-- the attributes bag, the only place a capture-typed audit row keeps it);\n-- `metadata` is reconstructed as the old camelCase JSON object from the new\n-- snake_case attributes bag, with `root_session_id` folded back in as\n-- `sessionId` (the one legacy metadata key that became a column, never an\n-- attribute, on the new table). Constrained to the four capture kinds so\n-- structural rows (session/run/tool_call/llm_call/source_lookup/config_scan)\n-- never leak into a legacy reader's result set \u2014 the old `events` table\n-- never held them either.\nCREATE VIEW `events` AS\nSELECT\n id,\n json_extract(attributes, '$.source_tool') AS source_tool,\n event_type AS kind,\n started_at AS occurred_at,\n content_hash,\n content,\n -- Plugin-local bookkeeping column that `ensureSyncedAtColumn` adds to the\n -- real `events` table at open time. A pre-cutover binary runs that probe on\n -- EVERY open; without this projection its `columnNames('events')` check\n -- misses `synced_at` and issues `ALTER TABLE events ADD COLUMN` against this\n -- view, which SQLite rejects (\"Cannot add a column to a view\") \u2014 a hard,\n -- non-fail-open crash of the whole open, the exact skew failure these views\n -- exist to prevent. Projecting it (always NULL; no reader consumes it)\n -- short-circuits that ALTER.\n NULL AS synced_at,\n json_object(\n 'sessionId', root_session_id,\n 'repo', json_extract(attributes, '$.repo'),\n 'filePath', json_extract(attributes, '$.file_path'),\n 'toolName', json_extract(attributes, '$.tool_name'),\n 'gitignored', json_extract(attributes, '$.gitignored'),\n 'wholeFile', json_extract(attributes, '$.whole_file'),\n 'model', json_extract(attributes, '$.model'),\n 'turnIndex', json_extract(attributes, '$.turn_index'),\n 'correlationId', json_extract(attributes, '$.correlation_id'),\n 'traceId', json_extract(attributes, '$.trace_id'),\n 'exceptionIds', json_extract(attributes, '$.exception_ids')\n ) AS metadata\nFROM audit_events\nWHERE event_type IN ('prompt', 'response', 'code_change', 'tool_use');\n--> statement-breakpoint\n-- A plain single-row INSERT (the old writer's shape \u2014 no ON CONFLICT) is\n-- accepted by prepare() against a view once an INSTEAD OF INSERT trigger\n-- exists, so it fails at run time instead of at open \u2014 landing in the same\n-- fail-open path the caller already wraps its write in.\nCREATE TRIGGER `trg_events_ro`\nINSTEAD OF INSERT ON `events`\nBEGIN SELECT RAISE(ABORT, 'events is read-only; write to audit_events instead'); END;\n--> statement-breakpoint\n-- Legacy `findings` shape: rule_id/category/severity come from the joined\n-- inspection_definitions row (the legacy table inlined them per-row; the\n-- generalized schema normalizes them out into the shared definition). A view\n-- has no rowid of its own, so the underlying table's rowid is re-exposed\n-- explicitly under that name \u2014 a legacy reader ordering by `f.rowid` would\n-- otherwise fail to resolve the column at all. Joined to audit_events for the\n-- same four-capture-kind constraint as the events view above (a finding\n-- attached to a tool_call/config_scan row never existed in the legacy table\n-- either \u2014 see the persistence findings repository's identical predicate).\nCREATE VIEW `findings` AS\nSELECT\n f.id AS id,\n f.rowid AS rowid,\n f.audit_event_id AS event_id,\n d.rule_id AS rule_id,\n d.category AS category,\n d.severity AS severity,\n f.span_start AS span_start,\n f.span_end AS span_end,\n f.masked_match AS masked_match,\n f.action_taken AS action_taken,\n f.confidence AS confidence,\n f.finding_key AS finding_key,\n f.first_detected_at AS first_detected_at\nFROM inspection_findings f\nJOIN audit_events e ON e.id = f.audit_event_id\nJOIN inspection_definitions d ON d.id = f.inspection_definition_id\nWHERE e.event_type IN ('prompt', 'response', 'code_change', 'tool_use');\n--> statement-breakpoint\n-- Defense in depth only: SQLite refuses to plan ANY upsert against a view\n-- (\"cannot UPSERT a view\") no matter what trigger exists, so this can never\n-- rescue the real legacy writer, which always used\n-- `ON CONFLICT (finding_key) DO UPDATE` \u2014 that statement still fails at\n-- prepare() on an old binary, same as it would with no trigger at all. This\n-- only covers a plain-INSERT shape, should one ever exist.\nCREATE TRIGGER `trg_findings_ro`\nINSTEAD OF INSERT ON `findings`\nBEGIN SELECT RAISE(ABORT, 'findings is read-only; write to inspection_findings instead'); END;\n"
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
tag: "0015_busy_vengeance",
|
|
570
|
+
sql: "CREATE TABLE `secret_vault` (\n `pointer_id` text PRIMARY KEY NOT NULL,\n `value_fingerprint` text NOT NULL,\n `fingerprint_key_version` integer NOT NULL,\n `key_version` integer NOT NULL,\n `category` text NOT NULL,\n `rule_id` text NOT NULL,\n `masked_match` text NOT NULL,\n `provider` text,\n `ciphertext` text NOT NULL,\n `nonce` text NOT NULL,\n `auth_tag` text NOT NULL,\n `occurrence_count` integer DEFAULT 1 NOT NULL,\n `first_seen` integer NOT NULL,\n `last_seen` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_secret_vault_value` ON `secret_vault` (`value_fingerprint`);--> statement-breakpoint\nCREATE TABLE `secret_vault_deref` (\n `id` text PRIMARY KEY NOT NULL,\n `pointer_id` text NOT NULL,\n `at` integer NOT NULL,\n `target` text NOT NULL,\n `reason` text NOT NULL,\n `outcome` text NOT NULL,\n `grant_id` text,\n `pointer_count` integer DEFAULT 1 NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_pointer` ON `secret_vault_deref` (`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_reason_at` ON `secret_vault_deref` (`reason`,`at`);"
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
tag: "0016_breezy_zodiak",
|
|
574
|
+
sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
tag: "0017_rainy_kat_farrell",
|
|
578
|
+
sql: "CREATE TABLE `secret_vault_sighting` (\n `id` text PRIMARY KEY NOT NULL,\n `pointer_id` text NOT NULL,\n `location` text NOT NULL,\n `kind` text NOT NULL,\n `first_seen` integer NOT NULL,\n `last_seen` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_secret_vault_sighting` ON `secret_vault_sighting` (`pointer_id`,`location`);"
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
tag: "0018_serious_tana_nile",
|
|
582
|
+
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
567
583
|
}
|
|
568
584
|
];
|
|
569
585
|
|
|
@@ -16220,6 +16236,7 @@ var ExceptionConditions = external_exports.object({
|
|
|
16220
16236
|
sourceTool: external_exports.string().optional(),
|
|
16221
16237
|
provider: external_exports.string().optional()
|
|
16222
16238
|
}).strict();
|
|
16239
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16223
16240
|
var DetectionException = external_exports.object({
|
|
16224
16241
|
id: external_exports.guid(),
|
|
16225
16242
|
ruleId: external_exports.string(),
|
|
@@ -16236,6 +16253,7 @@ var DetectionException = external_exports.object({
|
|
|
16236
16253
|
keyVersion: external_exports.number().int().positive(),
|
|
16237
16254
|
// maskMatch() preview of the approved value — never the raw value.
|
|
16238
16255
|
maskedValue: external_exports.string(),
|
|
16256
|
+
capability: ExceptionCapability.default("suppress"),
|
|
16239
16257
|
scope: ExceptionScope,
|
|
16240
16258
|
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16241
16259
|
maxUses: external_exports.number().int().positive().nullable(),
|
|
@@ -16259,6 +16277,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16259
16277
|
ruleId: true,
|
|
16260
16278
|
valueFingerprint: true,
|
|
16261
16279
|
keyVersion: true,
|
|
16280
|
+
capability: true,
|
|
16262
16281
|
expiresAt: true,
|
|
16263
16282
|
maxUses: true,
|
|
16264
16283
|
useCount: true,
|
|
@@ -17368,8 +17387,120 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17368
17387
|
message: "At least one field must be provided"
|
|
17369
17388
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17370
17389
|
|
|
17390
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17391
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17392
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17393
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17394
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17395
|
+
);
|
|
17396
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17397
|
+
function pointerTokenScanner() {
|
|
17398
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
17399
|
+
}
|
|
17400
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17401
|
+
var ParsedPointer = external_exports.object({
|
|
17402
|
+
category: DetectionCategory,
|
|
17403
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17404
|
+
pointerId: external_exports.string(),
|
|
17405
|
+
tag: external_exports.string()
|
|
17406
|
+
});
|
|
17407
|
+
var VaultEntry = external_exports.object({
|
|
17408
|
+
pointerId: external_exports.string(),
|
|
17409
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17410
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17411
|
+
// independently of the vault encryption key below.
|
|
17412
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17413
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17414
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17415
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17416
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17417
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17418
|
+
// value always produces exactly one wire token.
|
|
17419
|
+
category: DetectionCategory,
|
|
17420
|
+
ruleId: external_exports.string(),
|
|
17421
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17422
|
+
maskedMatch: external_exports.string(),
|
|
17423
|
+
provider: external_exports.string().optional(),
|
|
17424
|
+
ciphertext: external_exports.string(),
|
|
17425
|
+
nonce: external_exports.string(),
|
|
17426
|
+
authTag: external_exports.string(),
|
|
17427
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17428
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17429
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17430
|
+
firstSeen: external_exports.string(),
|
|
17431
|
+
lastSeen: external_exports.string()
|
|
17432
|
+
});
|
|
17433
|
+
var PointerDescriptor = external_exports.object({
|
|
17434
|
+
category: DetectionCategory,
|
|
17435
|
+
provider: external_exports.string().optional(),
|
|
17436
|
+
maskedMatch: external_exports.string(),
|
|
17437
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17438
|
+
firstSeen: external_exports.string(),
|
|
17439
|
+
lastSeen: external_exports.string()
|
|
17440
|
+
});
|
|
17441
|
+
var PointerIdentity = external_exports.object({
|
|
17442
|
+
ruleId: external_exports.string(),
|
|
17443
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17444
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17445
|
+
});
|
|
17446
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17447
|
+
var VaultDerefReason = external_exports.enum([
|
|
17448
|
+
"display",
|
|
17449
|
+
"explicit-reveal",
|
|
17450
|
+
"view-render",
|
|
17451
|
+
"model-input",
|
|
17452
|
+
"remediation",
|
|
17453
|
+
"purge"
|
|
17454
|
+
]);
|
|
17455
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17456
|
+
var VaultDeref = external_exports.object({
|
|
17457
|
+
id: external_exports.guid(),
|
|
17458
|
+
pointerId: external_exports.string(),
|
|
17459
|
+
at: external_exports.string(),
|
|
17460
|
+
target: DetokenizeTarget,
|
|
17461
|
+
reason: VaultDerefReason,
|
|
17462
|
+
outcome: VaultDerefOutcome,
|
|
17463
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17464
|
+
grantId: external_exports.string().optional(),
|
|
17465
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17466
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17467
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17468
|
+
});
|
|
17469
|
+
var VaultSightingKind = external_exports.enum([
|
|
17470
|
+
"prompt",
|
|
17471
|
+
"tool-input",
|
|
17472
|
+
"tool-output",
|
|
17473
|
+
"file",
|
|
17474
|
+
"transcript"
|
|
17475
|
+
]);
|
|
17476
|
+
var VaultSighting = external_exports.object({
|
|
17477
|
+
location: external_exports.string(),
|
|
17478
|
+
kind: VaultSightingKind,
|
|
17479
|
+
firstSeen: external_exports.string(),
|
|
17480
|
+
lastSeen: external_exports.string()
|
|
17481
|
+
});
|
|
17482
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17483
|
+
pointerId: external_exports.string(),
|
|
17484
|
+
category: DetectionCategory,
|
|
17485
|
+
provider: external_exports.string().optional(),
|
|
17486
|
+
maskedMatch: external_exports.string(),
|
|
17487
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17488
|
+
firstSeen: external_exports.string(),
|
|
17489
|
+
lastSeen: external_exports.string(),
|
|
17490
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17491
|
+
// the inventory badges it, the row links to revocation.
|
|
17492
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17493
|
+
sightings: external_exports.array(VaultSighting)
|
|
17494
|
+
});
|
|
17495
|
+
var VaultKeyCustody = external_exports.string();
|
|
17496
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17497
|
+
var VaultConsent = external_exports.object({
|
|
17498
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17499
|
+
version: external_exports.number().int().positive()
|
|
17500
|
+
});
|
|
17501
|
+
|
|
17371
17502
|
// ../../packages/schema/src/zod/local.ts
|
|
17372
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17503
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17373
17504
|
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
17374
17505
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17375
17506
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
@@ -17395,6 +17526,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17395
17526
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17396
17527
|
// Shares writes.
|
|
17397
17528
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17529
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17530
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17531
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17532
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17533
|
+
// purging the vault is the eraser.
|
|
17534
|
+
vaultConsent: VaultConsent.optional(),
|
|
17535
|
+
// Where the vault master key lives.
|
|
17536
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17537
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17538
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17398
17539
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17399
17540
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17400
17541
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -19781,6 +19922,9 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19781
19922
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19782
19923
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19783
19924
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
19925
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
19926
|
+
AND conditions IS NULL
|
|
19927
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19784
19928
|
var SqliteExceptionsRepository = class {
|
|
19785
19929
|
constructor(db) {
|
|
19786
19930
|
this.db = db;
|
|
@@ -19872,11 +20016,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19872
20016
|
this.db.prepare(
|
|
19873
20017
|
`INSERT INTO exceptions (
|
|
19874
20018
|
id, rule_id, category, value_fingerprint, key_version, masked_value,
|
|
19875
|
-
scope, expires_at, max_uses, use_count, last_used_at,
|
|
19876
|
-
conditions, created_by, created_via, created_at, updated_at
|
|
20019
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20020
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19877
20021
|
) VALUES (
|
|
19878
20022
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19879
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20023
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19880
20024
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19881
20025
|
)`
|
|
19882
20026
|
).run({
|
|
@@ -19886,6 +20030,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19886
20030
|
valueFingerprint: input.valueFingerprint,
|
|
19887
20031
|
keyVersion: input.keyVersion,
|
|
19888
20032
|
maskedValue: input.maskedValue,
|
|
20033
|
+
capability: input.capability ?? "suppress",
|
|
19889
20034
|
scope: input.scope,
|
|
19890
20035
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19891
20036
|
maxUses: input.maxUses,
|
|
@@ -19979,6 +20124,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19979
20124
|
ruleId: row.rule_id,
|
|
19980
20125
|
valueFingerprint: row.value_fingerprint,
|
|
19981
20126
|
keyVersion: row.key_version,
|
|
20127
|
+
capability: row.capability,
|
|
19982
20128
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
19983
20129
|
maxUses: row.max_uses,
|
|
19984
20130
|
useCount: row.use_count,
|
|
@@ -20033,6 +20179,35 @@ var SqliteExceptionsRepository = class {
|
|
|
20033
20179
|
}))
|
|
20034
20180
|
);
|
|
20035
20181
|
}
|
|
20182
|
+
/**
|
|
20183
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20184
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20185
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20186
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20187
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20188
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20189
|
+
*
|
|
20190
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20191
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20192
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20193
|
+
*/
|
|
20194
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
|
|
20195
|
+
try {
|
|
20196
|
+
const row = getRow(
|
|
20197
|
+
this.db.prepare(
|
|
20198
|
+
`SELECT id FROM exceptions
|
|
20199
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20200
|
+
AND key_version = :keyVersion
|
|
20201
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20202
|
+
LIMIT 1`
|
|
20203
|
+
),
|
|
20204
|
+
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20205
|
+
);
|
|
20206
|
+
return Promise.resolve(row ?? null);
|
|
20207
|
+
} catch (err) {
|
|
20208
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20209
|
+
}
|
|
20210
|
+
}
|
|
20036
20211
|
/**
|
|
20037
20212
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20038
20213
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20060,6 +20235,7 @@ function parseExceptionRow(row) {
|
|
|
20060
20235
|
valueFingerprint: row.value_fingerprint,
|
|
20061
20236
|
keyVersion: row.key_version,
|
|
20062
20237
|
maskedValue: row.masked_value,
|
|
20238
|
+
capability: row.capability,
|
|
20063
20239
|
scope: row.scope,
|
|
20064
20240
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20065
20241
|
maxUses: row.max_uses,
|
|
@@ -22225,6 +22401,287 @@ var SqliteScanLedgerRepository = class {
|
|
|
22225
22401
|
}
|
|
22226
22402
|
};
|
|
22227
22403
|
|
|
22404
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22405
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22406
|
+
var SELECT_COLUMNS = `
|
|
22407
|
+
pointer_id AS pointerId,
|
|
22408
|
+
value_fingerprint AS valueFingerprint,
|
|
22409
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
22410
|
+
key_version AS keyVersion,
|
|
22411
|
+
format_version AS formatVersion,
|
|
22412
|
+
category,
|
|
22413
|
+
rule_id AS ruleId,
|
|
22414
|
+
masked_match AS maskedMatch,
|
|
22415
|
+
provider,
|
|
22416
|
+
ciphertext,
|
|
22417
|
+
nonce,
|
|
22418
|
+
auth_tag AS authTag,
|
|
22419
|
+
occurrence_count AS occurrenceCount,
|
|
22420
|
+
first_seen AS firstSeen,
|
|
22421
|
+
last_seen AS lastSeen`;
|
|
22422
|
+
function toRow(raw) {
|
|
22423
|
+
const { provider, ...rest } = raw;
|
|
22424
|
+
return provider === null ? rest : { ...rest, provider };
|
|
22425
|
+
}
|
|
22426
|
+
var SqliteSecretVaultRepository = class {
|
|
22427
|
+
constructor(db) {
|
|
22428
|
+
this.db = db;
|
|
22429
|
+
this.insertStmt = db.prepare(
|
|
22430
|
+
`INSERT INTO secret_vault (
|
|
22431
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
22432
|
+
format_version, category, rule_id, masked_match, provider,
|
|
22433
|
+
ciphertext, nonce, auth_tag,
|
|
22434
|
+
occurrence_count, first_seen, last_seen
|
|
22435
|
+
) VALUES (
|
|
22436
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
22437
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
22438
|
+
:ciphertext, :nonce, :authTag,
|
|
22439
|
+
1, :now, :now
|
|
22440
|
+
)`
|
|
22441
|
+
);
|
|
22442
|
+
this.bumpStmt = db.prepare(
|
|
22443
|
+
`UPDATE secret_vault
|
|
22444
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
22445
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
22446
|
+
);
|
|
22447
|
+
this.byPointerStmt = db.prepare(
|
|
22448
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
22449
|
+
);
|
|
22450
|
+
this.byFingerprintStmt = db.prepare(
|
|
22451
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
22452
|
+
);
|
|
22453
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
22454
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
22455
|
+
`UPDATE secret_vault
|
|
22456
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
22457
|
+
WHERE pointer_id = :pointerId`
|
|
22458
|
+
);
|
|
22459
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
22460
|
+
`UPDATE secret_vault
|
|
22461
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
22462
|
+
WHERE pointer_id = :pointerId`
|
|
22463
|
+
);
|
|
22464
|
+
this.derefStmt = db.prepare(
|
|
22465
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
22466
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
22467
|
+
);
|
|
22468
|
+
}
|
|
22469
|
+
db;
|
|
22470
|
+
insertStmt;
|
|
22471
|
+
bumpStmt;
|
|
22472
|
+
byPointerStmt;
|
|
22473
|
+
byFingerprintStmt;
|
|
22474
|
+
listStmt;
|
|
22475
|
+
replaceCiphertextStmt;
|
|
22476
|
+
refreshFingerprintStmt;
|
|
22477
|
+
derefStmt;
|
|
22478
|
+
/**
|
|
22479
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
22480
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
22481
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
22482
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
22483
|
+
* wire token. `minted` is true only when this call created the row.
|
|
22484
|
+
*
|
|
22485
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
22486
|
+
* writers cannot both decide they are minting.
|
|
22487
|
+
*/
|
|
22488
|
+
upsert(input, now) {
|
|
22489
|
+
let minted = false;
|
|
22490
|
+
withTransaction(
|
|
22491
|
+
this.db,
|
|
22492
|
+
() => {
|
|
22493
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
22494
|
+
valueFingerprint: input.valueFingerprint
|
|
22495
|
+
});
|
|
22496
|
+
if (existing === void 0) {
|
|
22497
|
+
this.insertStmt.run(
|
|
22498
|
+
bindParams({
|
|
22499
|
+
pointerId: input.pointerId,
|
|
22500
|
+
valueFingerprint: input.valueFingerprint,
|
|
22501
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
22502
|
+
keyVersion: input.keyVersion,
|
|
22503
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
22504
|
+
category: input.category,
|
|
22505
|
+
ruleId: input.ruleId,
|
|
22506
|
+
maskedMatch: input.maskedMatch,
|
|
22507
|
+
provider: input.provider,
|
|
22508
|
+
ciphertext: input.ciphertext,
|
|
22509
|
+
nonce: input.nonce,
|
|
22510
|
+
authTag: input.authTag,
|
|
22511
|
+
now
|
|
22512
|
+
})
|
|
22513
|
+
);
|
|
22514
|
+
minted = true;
|
|
22515
|
+
return;
|
|
22516
|
+
}
|
|
22517
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
22518
|
+
},
|
|
22519
|
+
"IMMEDIATE"
|
|
22520
|
+
);
|
|
22521
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
22522
|
+
valueFingerprint: input.valueFingerprint
|
|
22523
|
+
});
|
|
22524
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
22525
|
+
return { row: toRow(row), minted };
|
|
22526
|
+
}
|
|
22527
|
+
byPointerId(pointerId) {
|
|
22528
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
22529
|
+
return raw === void 0 ? null : toRow(raw);
|
|
22530
|
+
}
|
|
22531
|
+
byValueFingerprint(fingerprint) {
|
|
22532
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
22533
|
+
return raw === void 0 ? null : toRow(raw);
|
|
22534
|
+
}
|
|
22535
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
22536
|
+
recordDeref(entry) {
|
|
22537
|
+
this.derefStmt.run(
|
|
22538
|
+
bindParams({
|
|
22539
|
+
id: entry.id,
|
|
22540
|
+
pointerId: entry.pointerId,
|
|
22541
|
+
at: entry.at,
|
|
22542
|
+
target: entry.target,
|
|
22543
|
+
reason: entry.reason,
|
|
22544
|
+
outcome: entry.outcome,
|
|
22545
|
+
grantId: entry.grantId,
|
|
22546
|
+
pointerCount: entry.pointerCount ?? 1
|
|
22547
|
+
})
|
|
22548
|
+
);
|
|
22549
|
+
}
|
|
22550
|
+
listAll() {
|
|
22551
|
+
return allRows(this.listStmt).map(toRow);
|
|
22552
|
+
}
|
|
22553
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
22554
|
+
replaceCiphertext(pointerId, next) {
|
|
22555
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
22556
|
+
}
|
|
22557
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
22558
|
+
refreshFingerprint(pointerId, next) {
|
|
22559
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
22560
|
+
}
|
|
22561
|
+
/**
|
|
22562
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
22563
|
+
* audit is left alone on purpose — see the table note above.
|
|
22564
|
+
*/
|
|
22565
|
+
purgeAll() {
|
|
22566
|
+
let destroyed = 0;
|
|
22567
|
+
withTransaction(
|
|
22568
|
+
this.db,
|
|
22569
|
+
() => {
|
|
22570
|
+
destroyed = this.countEntries();
|
|
22571
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
22572
|
+
},
|
|
22573
|
+
"IMMEDIATE"
|
|
22574
|
+
);
|
|
22575
|
+
return destroyed;
|
|
22576
|
+
}
|
|
22577
|
+
/**
|
|
22578
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
22579
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
22580
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
22581
|
+
* so callers wrap this, not the other way around.
|
|
22582
|
+
*/
|
|
22583
|
+
recordSighting(entry, now) {
|
|
22584
|
+
this.db.prepare(
|
|
22585
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
22586
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22587
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22588
|
+
).run({
|
|
22589
|
+
id: randomUUID7(),
|
|
22590
|
+
pointerId: entry.pointerId,
|
|
22591
|
+
location: entry.location,
|
|
22592
|
+
kind: entry.kind,
|
|
22593
|
+
now
|
|
22594
|
+
});
|
|
22595
|
+
}
|
|
22596
|
+
listSightings(pointerId) {
|
|
22597
|
+
const rows = allRows(
|
|
22598
|
+
this.db.prepare(
|
|
22599
|
+
`SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
|
|
22600
|
+
WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
|
|
22601
|
+
),
|
|
22602
|
+
{ pointerId }
|
|
22603
|
+
);
|
|
22604
|
+
return rows.map((r) => ({
|
|
22605
|
+
location: r.location,
|
|
22606
|
+
kind: r.kind,
|
|
22607
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22608
|
+
lastSeen: new Date(r.last_seen).toISOString()
|
|
22609
|
+
}));
|
|
22610
|
+
}
|
|
22611
|
+
/**
|
|
22612
|
+
* The dashboard inventory: every vaulted value's descriptor data joined with
|
|
22613
|
+
* its sightings and the active reveal-to-model grant when one exists.
|
|
22614
|
+
* Raw-free by construction — neither the fingerprint nor the ciphertext
|
|
22615
|
+
* columns are selected.
|
|
22616
|
+
*/
|
|
22617
|
+
listInventory(now = Date.now()) {
|
|
22618
|
+
const rows = allRows(
|
|
22619
|
+
this.db.prepare(
|
|
22620
|
+
`SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
22621
|
+
v.occurrence_count, v.first_seen, v.last_seen,
|
|
22622
|
+
(SELECT e.id FROM exceptions e
|
|
22623
|
+
WHERE e.rule_id = v.rule_id
|
|
22624
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
22625
|
+
AND e.key_version = v.fingerprint_key_version
|
|
22626
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22627
|
+
LIMIT 1) AS grant_id
|
|
22628
|
+
FROM secret_vault v
|
|
22629
|
+
ORDER BY v.last_seen DESC`
|
|
22630
|
+
),
|
|
22631
|
+
{ now }
|
|
22632
|
+
);
|
|
22633
|
+
return rows.map((r) => ({
|
|
22634
|
+
pointerId: r.pointer_id,
|
|
22635
|
+
category: r.category,
|
|
22636
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
22637
|
+
maskedMatch: r.masked_match,
|
|
22638
|
+
occurrences: r.occurrence_count,
|
|
22639
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22640
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
22641
|
+
revealGrantId: r.grant_id,
|
|
22642
|
+
sightings: this.listSightings(r.pointer_id)
|
|
22643
|
+
}));
|
|
22644
|
+
}
|
|
22645
|
+
/**
|
|
22646
|
+
* The de-reference trail, newest first. By default the batched, high-volume
|
|
22647
|
+
* reasons (display, view-render) are hidden and counted instead — the rows
|
|
22648
|
+
* that matter as a signal are the model crossings, and burying them under
|
|
22649
|
+
* render noise would defeat the audit's purpose.
|
|
22650
|
+
*/
|
|
22651
|
+
listDerefs(opts) {
|
|
22652
|
+
const limit = opts?.limit ?? 200;
|
|
22653
|
+
const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
|
|
22654
|
+
const rows = allRows(
|
|
22655
|
+
this.db.prepare(
|
|
22656
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22657
|
+
FROM secret_vault_deref ${where}
|
|
22658
|
+
ORDER BY at DESC, rowid DESC LIMIT :limit`
|
|
22659
|
+
),
|
|
22660
|
+
{ limit }
|
|
22661
|
+
);
|
|
22662
|
+
const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
|
|
22663
|
+
this.db,
|
|
22664
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22665
|
+
);
|
|
22666
|
+
return {
|
|
22667
|
+
rows: rows.map((r) => ({
|
|
22668
|
+
id: r.id,
|
|
22669
|
+
pointerId: r.pointer_id,
|
|
22670
|
+
at: new Date(r.at).toISOString(),
|
|
22671
|
+
target: r.target,
|
|
22672
|
+
reason: r.reason,
|
|
22673
|
+
outcome: r.outcome,
|
|
22674
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22675
|
+
pointerCount: r.pointer_count
|
|
22676
|
+
})),
|
|
22677
|
+
hiddenBatched
|
|
22678
|
+
};
|
|
22679
|
+
}
|
|
22680
|
+
countEntries() {
|
|
22681
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22682
|
+
}
|
|
22683
|
+
};
|
|
22684
|
+
|
|
22228
22685
|
// ../../packages/persistence/src/repositories/security.ts
|
|
22229
22686
|
var DAY_MS4 = 864e5;
|
|
22230
22687
|
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
@@ -22570,7 +23027,7 @@ var SqliteSecurityRepository = class {
|
|
|
22570
23027
|
};
|
|
22571
23028
|
|
|
22572
23029
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22573
|
-
import { randomUUID as
|
|
23030
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
22574
23031
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22575
23032
|
var IN_CHUNK = 500;
|
|
22576
23033
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22826,7 +23283,7 @@ var SqliteSharesRepository = class {
|
|
|
22826
23283
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22827
23284
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22828
23285
|
).run({
|
|
22829
|
-
id:
|
|
23286
|
+
id: randomUUID8(),
|
|
22830
23287
|
destinationId,
|
|
22831
23288
|
host: dest.host,
|
|
22832
23289
|
decision,
|
|
@@ -22975,7 +23432,7 @@ var SqliteSharesRepository = class {
|
|
|
22975
23432
|
let destinationId = destIds.get(hit.host);
|
|
22976
23433
|
if (destinationId === void 0) {
|
|
22977
23434
|
destStmt.run({
|
|
22978
|
-
id:
|
|
23435
|
+
id: randomUUID8(),
|
|
22979
23436
|
kind: hit.kind,
|
|
22980
23437
|
name: hit.name,
|
|
22981
23438
|
host: hit.host,
|
|
@@ -22991,7 +23448,7 @@ var SqliteSharesRepository = class {
|
|
|
22991
23448
|
let endpointId = endpointIds.get(endpointKey);
|
|
22992
23449
|
if (endpointId === void 0) {
|
|
22993
23450
|
endpointStmt.run({
|
|
22994
|
-
id:
|
|
23451
|
+
id: randomUUID8(),
|
|
22995
23452
|
destinationId,
|
|
22996
23453
|
method: hit.method,
|
|
22997
23454
|
transport: hit.transport,
|
|
@@ -23004,7 +23461,7 @@ var SqliteSharesRepository = class {
|
|
|
23004
23461
|
endpointIds.set(endpointKey, endpointId);
|
|
23005
23462
|
}
|
|
23006
23463
|
siteStmt.run({
|
|
23007
|
-
id:
|
|
23464
|
+
id: randomUUID8(),
|
|
23008
23465
|
endpointId,
|
|
23009
23466
|
project: input.project,
|
|
23010
23467
|
projectKey: input.projectKey,
|
|
@@ -23372,11 +23829,22 @@ function purgeSampleData(db) {
|
|
|
23372
23829
|
function linkHost(input, hostId) {
|
|
23373
23830
|
return hostId ? { ...input, hostId } : input;
|
|
23374
23831
|
}
|
|
23832
|
+
function closeQuietly(db) {
|
|
23833
|
+
try {
|
|
23834
|
+
db.close();
|
|
23835
|
+
} catch {
|
|
23836
|
+
}
|
|
23837
|
+
}
|
|
23375
23838
|
function openWithPragmas(file2) {
|
|
23376
23839
|
const db = new DatabaseSync(file2);
|
|
23377
|
-
|
|
23378
|
-
|
|
23379
|
-
|
|
23840
|
+
try {
|
|
23841
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
23842
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
23843
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
23844
|
+
} catch (err) {
|
|
23845
|
+
closeQuietly(db);
|
|
23846
|
+
throw err;
|
|
23847
|
+
}
|
|
23380
23848
|
return db;
|
|
23381
23849
|
}
|
|
23382
23850
|
function backupLegacyStore(file2) {
|
|
@@ -23388,43 +23856,82 @@ function backupLegacyStore(file2) {
|
|
|
23388
23856
|
}
|
|
23389
23857
|
return backup;
|
|
23390
23858
|
}
|
|
23859
|
+
function openAndInitialize(file2) {
|
|
23860
|
+
let db = openWithPragmas(file2);
|
|
23861
|
+
try {
|
|
23862
|
+
if (isForeignSqliteLineage(db)) {
|
|
23863
|
+
db.close();
|
|
23864
|
+
const backup = backupLegacyStore(file2);
|
|
23865
|
+
db = openWithPragmas(file2);
|
|
23866
|
+
akaWarn(
|
|
23867
|
+
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
23868
|
+
);
|
|
23869
|
+
}
|
|
23870
|
+
applyMigrations(db, file2);
|
|
23871
|
+
tightenPerms(file2);
|
|
23872
|
+
const policies = new SqlitePoliciesRepository(db);
|
|
23873
|
+
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
23874
|
+
const repositories = {
|
|
23875
|
+
events: new SqliteEventsRepository(db),
|
|
23876
|
+
findings: new SqliteFindingsRepository(db),
|
|
23877
|
+
policies,
|
|
23878
|
+
installedPacks,
|
|
23879
|
+
scanLedger: new SqliteScanLedgerRepository(db),
|
|
23880
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23881
|
+
exceptions: new SqliteExceptionsRepository(db),
|
|
23882
|
+
resolutions: new SqliteResolutionsRepository(db),
|
|
23883
|
+
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
23884
|
+
security: new SqliteSecurityRepository(db),
|
|
23885
|
+
detections: new SqliteDetectionsRepository(db),
|
|
23886
|
+
shares: new SqliteSharesRepository(db),
|
|
23887
|
+
policyCatalog: new SqlitePolicyCatalogRepository(installedPacks),
|
|
23888
|
+
inventory: new SqliteInventoryRepository(db),
|
|
23889
|
+
inventoryAssets: new SqliteInventoryAssetsRepository(db),
|
|
23890
|
+
projectFiles: new SqliteProjectFilesRepository(db),
|
|
23891
|
+
activity: new SqliteActivityRepository(db),
|
|
23892
|
+
sourceProject: new SqliteSourceProjectRepository(db),
|
|
23893
|
+
auditEvents: new SqliteAuditEventsRepository(db),
|
|
23894
|
+
classifiedData: new SqliteClassifiedDataRepository(db),
|
|
23895
|
+
inspectionDefinitions: new SqliteInspectionDefinitionsRepository(db),
|
|
23896
|
+
inspectionFindings: new SqliteInspectionFindingsRepository(db),
|
|
23897
|
+
configInventory: new SqliteConfigInventoryRepository(db)
|
|
23898
|
+
};
|
|
23899
|
+
policies.seedDefaults();
|
|
23900
|
+
return { db, ...repositories };
|
|
23901
|
+
} catch (err) {
|
|
23902
|
+
closeQuietly(db);
|
|
23903
|
+
throw err;
|
|
23904
|
+
}
|
|
23905
|
+
}
|
|
23391
23906
|
function openLocalDatabase(dir) {
|
|
23392
23907
|
ensureDataDirSync(dir);
|
|
23393
23908
|
const file2 = join(dir, DB_FILENAME);
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
|
|
23399
|
-
|
|
23400
|
-
|
|
23401
|
-
|
|
23402
|
-
|
|
23403
|
-
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
|
|
23413
|
-
|
|
23414
|
-
|
|
23415
|
-
|
|
23416
|
-
|
|
23417
|
-
|
|
23418
|
-
|
|
23419
|
-
|
|
23420
|
-
const activity = new SqliteActivityRepository(db);
|
|
23421
|
-
const sourceProject = new SqliteSourceProjectRepository(db);
|
|
23422
|
-
const auditEvents = new SqliteAuditEventsRepository(db);
|
|
23423
|
-
const classifiedData = new SqliteClassifiedDataRepository(db);
|
|
23424
|
-
const inspectionDefinitions = new SqliteInspectionDefinitionsRepository(db);
|
|
23425
|
-
const inspectionFindings = new SqliteInspectionFindingsRepository(db);
|
|
23426
|
-
const configInventory = new SqliteConfigInventoryRepository(db);
|
|
23427
|
-
policies.seedDefaults();
|
|
23909
|
+
const {
|
|
23910
|
+
db,
|
|
23911
|
+
events,
|
|
23912
|
+
findings,
|
|
23913
|
+
policies,
|
|
23914
|
+
installedPacks,
|
|
23915
|
+
scanLedger,
|
|
23916
|
+
secretVault,
|
|
23917
|
+
exceptions,
|
|
23918
|
+
resolutions,
|
|
23919
|
+
ruleProbeCache,
|
|
23920
|
+
security,
|
|
23921
|
+
detections,
|
|
23922
|
+
shares,
|
|
23923
|
+
policyCatalog,
|
|
23924
|
+
inventory,
|
|
23925
|
+
inventoryAssets,
|
|
23926
|
+
projectFiles,
|
|
23927
|
+
activity,
|
|
23928
|
+
sourceProject,
|
|
23929
|
+
auditEvents,
|
|
23930
|
+
classifiedData,
|
|
23931
|
+
inspectionDefinitions,
|
|
23932
|
+
inspectionFindings,
|
|
23933
|
+
configInventory
|
|
23934
|
+
} = openAndInitialize(file2);
|
|
23428
23935
|
function recordCapture(event, detected) {
|
|
23429
23936
|
failOpenTransaction(db, () => {
|
|
23430
23937
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -23515,7 +24022,7 @@ function openLocalDatabase(dir) {
|
|
|
23515
24022
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23516
24023
|
if (!definitionId) continue;
|
|
23517
24024
|
inspectionFindings.insertFinding({
|
|
23518
|
-
id:
|
|
24025
|
+
id: randomUUID9(),
|
|
23519
24026
|
auditEventId: record2.scanEvent.id,
|
|
23520
24027
|
inspectionDefinitionId: definitionId,
|
|
23521
24028
|
span: finding.span,
|
|
@@ -23592,6 +24099,7 @@ function openLocalDatabase(dir) {
|
|
|
23592
24099
|
policies,
|
|
23593
24100
|
installedPacks,
|
|
23594
24101
|
scanLedger,
|
|
24102
|
+
secretVault,
|
|
23595
24103
|
exceptions,
|
|
23596
24104
|
resolutions,
|
|
23597
24105
|
ruleProbeCache,
|
|
@@ -23629,8 +24137,9 @@ import { createHash as createHash3 } from "crypto";
|
|
|
23629
24137
|
|
|
23630
24138
|
// ../../packages/persistence/src/fingerprint.ts
|
|
23631
24139
|
import { createHmac, randomBytes } from "crypto";
|
|
23632
|
-
import { readFileSync } from "fs";
|
|
24140
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
23633
24141
|
import { join as join2 } from "path";
|
|
24142
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23634
24143
|
|
|
23635
24144
|
// ../../packages/persistence/src/local-layout.ts
|
|
23636
24145
|
import { renameSync as renameSync3 } from "fs";
|
|
@@ -23690,14 +24199,40 @@ function readJson(file2) {
|
|
|
23690
24199
|
return parseJsonObject(text) ?? null;
|
|
23691
24200
|
}
|
|
23692
24201
|
|
|
23693
|
-
// ../../packages/persistence/src/
|
|
23694
|
-
import {
|
|
24202
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
24203
|
+
import {
|
|
24204
|
+
createCipheriv,
|
|
24205
|
+
createDecipheriv,
|
|
24206
|
+
createHmac as createHmac2,
|
|
24207
|
+
hkdfSync,
|
|
24208
|
+
timingSafeEqual
|
|
24209
|
+
} from "crypto";
|
|
24210
|
+
|
|
24211
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
24212
|
+
import { execFileSync } from "child_process";
|
|
24213
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
24214
|
+
import {
|
|
24215
|
+
chmodSync as chmodSync2,
|
|
24216
|
+
mkdirSync as mkdirSync2,
|
|
24217
|
+
readFileSync as readFileSync3,
|
|
24218
|
+
renameSync as renameSync4,
|
|
24219
|
+
rmSync as rmSync3,
|
|
24220
|
+
statSync,
|
|
24221
|
+
writeFileSync as writeFileSync2
|
|
24222
|
+
} from "fs";
|
|
23695
24223
|
import { join as join5 } from "path";
|
|
23696
24224
|
|
|
23697
|
-
// ../../packages/
|
|
23698
|
-
import {
|
|
24225
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
24226
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
|
|
24227
|
+
|
|
24228
|
+
// ../../packages/persistence/src/warn-era-cap.ts
|
|
24229
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
23699
24230
|
import { join as join6 } from "path";
|
|
23700
24231
|
|
|
24232
|
+
// ../../packages/plugin-sdk/src/config.ts
|
|
24233
|
+
import { existsSync as existsSync4 } from "fs";
|
|
24234
|
+
import { join as join7 } from "path";
|
|
24235
|
+
|
|
23701
24236
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
23702
24237
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
23703
24238
|
var booleanish = external_exports.string().optional().transform((v) => {
|
|
@@ -23750,8 +24285,8 @@ function resolveProvider() {
|
|
|
23750
24285
|
function loadConfig(base = defaultDataDir()) {
|
|
23751
24286
|
try {
|
|
23752
24287
|
ensureLayoutDirSync(base);
|
|
23753
|
-
const settingsFile =
|
|
23754
|
-
if (
|
|
24288
|
+
const settingsFile = join7(settingsDir(base), "settings.json");
|
|
24289
|
+
if (existsSync4(settingsFile)) tightenFile(settingsFile);
|
|
23755
24290
|
} catch {
|
|
23756
24291
|
}
|
|
23757
24292
|
migrateLegacyLayout(base);
|
|
@@ -23774,9 +24309,9 @@ function resolveProviderSafe() {
|
|
|
23774
24309
|
}
|
|
23775
24310
|
|
|
23776
24311
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23777
|
-
import { readdirSync, readFileSync as
|
|
24312
|
+
import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
23778
24313
|
import { homedir as homedir2 } from "os";
|
|
23779
|
-
import { basename as basename2, join as
|
|
24314
|
+
import { basename as basename2, join as join9 } from "path";
|
|
23780
24315
|
|
|
23781
24316
|
// ../../packages/detections/src/egress/registry.ts
|
|
23782
24317
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -24564,12 +25099,12 @@ function redact(text, findings) {
|
|
|
24564
25099
|
const regions = [];
|
|
24565
25100
|
for (const f of sorted) {
|
|
24566
25101
|
const rank = SEVERITY_RANK2[f.severity];
|
|
24567
|
-
const
|
|
24568
|
-
if (
|
|
24569
|
-
|
|
24570
|
-
if (rank >
|
|
24571
|
-
|
|
24572
|
-
|
|
25102
|
+
const open2 = regions[regions.length - 1];
|
|
25103
|
+
if (open2 && f.span.start < open2.end) {
|
|
25104
|
+
open2.end = Math.max(open2.end, f.span.end);
|
|
25105
|
+
if (rank > open2.rank) {
|
|
25106
|
+
open2.rank = rank;
|
|
25107
|
+
open2.category = f.category;
|
|
24573
25108
|
}
|
|
24574
25109
|
} else {
|
|
24575
25110
|
regions.push({ start: f.span.start, end: f.span.end, category: f.category, rank });
|
|
@@ -24598,6 +25133,24 @@ function maskMatch(raw) {
|
|
|
24598
25133
|
return `${raw.charAt(0)}${"*".repeat(6)}${raw.charAt(raw.length - 1)}`;
|
|
24599
25134
|
}
|
|
24600
25135
|
|
|
25136
|
+
// ../../packages/detections/src/pointer-shield.ts
|
|
25137
|
+
function shieldPointers(text) {
|
|
25138
|
+
const spans = [];
|
|
25139
|
+
let out = null;
|
|
25140
|
+
for (const match of text.matchAll(pointerTokenScanner())) {
|
|
25141
|
+
spans.push({ start: match.index, end: match.index + match[0].length });
|
|
25142
|
+
out ??= text;
|
|
25143
|
+
out = out.slice(0, match.index) + " ".repeat(match[0].length) + out.slice(match.index + match[0].length);
|
|
25144
|
+
}
|
|
25145
|
+
return { text: out ?? text, spans };
|
|
25146
|
+
}
|
|
25147
|
+
function dropShieldedFindings(findings, spans) {
|
|
25148
|
+
if (spans.length === 0) return findings;
|
|
25149
|
+
return findings.filter(
|
|
25150
|
+
(finding) => !spans.some((s) => finding.span.start < s.end && finding.span.end > s.start)
|
|
25151
|
+
);
|
|
25152
|
+
}
|
|
25153
|
+
|
|
24601
25154
|
// ../../packages/detections/src/posture/config-posture.ts
|
|
24602
25155
|
var RULE_VERSION = "1";
|
|
24603
25156
|
var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -26719,7 +27272,8 @@ function scanText(text, ruleVersions) {
|
|
|
26719
27272
|
if (!ensureBundledPacks()) return { masked: "[REDACTED]", findings: [] };
|
|
26720
27273
|
try {
|
|
26721
27274
|
const rules = getLoadedRules();
|
|
26722
|
-
const
|
|
27275
|
+
const shielded = shieldPointers(text);
|
|
27276
|
+
const matches = dropShieldedFindings(scan(shielded.text, rules), shielded.spans);
|
|
26723
27277
|
if (matches.length === 0) return { masked: text, findings: [] };
|
|
26724
27278
|
const byId = new Map(rules.map((r) => [r.id, r]));
|
|
26725
27279
|
const findings = matches.map((m) => {
|
|
@@ -26745,18 +27299,18 @@ function maskText(text) {
|
|
|
26745
27299
|
}
|
|
26746
27300
|
|
|
26747
27301
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
26748
|
-
import { existsSync as
|
|
26749
|
-
import { basename, dirname, isAbsolute, join as
|
|
27302
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
27303
|
+
import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
|
|
26750
27304
|
|
|
26751
27305
|
// ../../packages/plugin-sdk/src/events.ts
|
|
26752
|
-
import { createHash as createHash4, randomUUID as
|
|
27306
|
+
import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
|
|
26753
27307
|
|
|
26754
27308
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
26755
27309
|
import { arch, hostname as hostname3, platform, release } from "os";
|
|
26756
27310
|
|
|
26757
27311
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
26758
|
-
import { mkdirSync as
|
|
26759
|
-
import { join as
|
|
27312
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
27313
|
+
import { join as join10 } from "path";
|
|
26760
27314
|
|
|
26761
27315
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
26762
27316
|
import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
|
|
@@ -26774,8 +27328,8 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
26774
27328
|
|
|
26775
27329
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
26776
27330
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
26777
|
-
import { existsSync as
|
|
26778
|
-
import { basename as basename4, join as
|
|
27331
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
27332
|
+
import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
|
|
26779
27333
|
|
|
26780
27334
|
// ../../packages/plugin-sdk/src/raw-egress.ts
|
|
26781
27335
|
var RawEgressError = class extends Error {
|
|
@@ -26826,7 +27380,7 @@ function assertRawFree(text, rawValues) {
|
|
|
26826
27380
|
}
|
|
26827
27381
|
|
|
26828
27382
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
26829
|
-
import { randomUUID as
|
|
27383
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
26830
27384
|
|
|
26831
27385
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
26832
27386
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -26866,8 +27420,8 @@ async function applySetupTriageSuppressions(entries, writer, opts) {
|
|
|
26866
27420
|
}
|
|
26867
27421
|
|
|
26868
27422
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
26869
|
-
import { mkdirSync as
|
|
26870
|
-
import { join as
|
|
27423
|
+
import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
27424
|
+
import { join as join12 } from "path";
|
|
26871
27425
|
|
|
26872
27426
|
// src/command-registry.ts
|
|
26873
27427
|
import { readdirSync as readdirSync4 } from "fs";
|
|
@@ -26982,12 +27536,12 @@ function show(body) {
|
|
|
26982
27536
|
}
|
|
26983
27537
|
|
|
26984
27538
|
// src/triage/gate-display.ts
|
|
26985
|
-
function findContext(entry,
|
|
26986
|
-
const byFingerprint =
|
|
27539
|
+
function findContext(entry, join16) {
|
|
27540
|
+
const byFingerprint = join16.find(
|
|
26987
27541
|
(j) => j.valueFingerprint !== void 0 && j.valueFingerprint === entry.valueFingerprint
|
|
26988
27542
|
);
|
|
26989
27543
|
if (byFingerprint) return byFingerprint.maskedContext;
|
|
26990
|
-
const byRuleAndMask =
|
|
27544
|
+
const byRuleAndMask = join16.find(
|
|
26991
27545
|
(j) => j.ruleId === entry.ruleId && j.maskedMatch === entry.maskedValue
|
|
26992
27546
|
);
|
|
26993
27547
|
return byRuleAndMask?.maskedContext;
|
|
@@ -27058,13 +27612,13 @@ function renderShowcase(showcase) {
|
|
|
27058
27612
|
|
|
27059
27613
|
${blocks.join("\n\n")}`;
|
|
27060
27614
|
}
|
|
27061
|
-
function renderSuppressionGate(entries,
|
|
27615
|
+
function renderSuppressionGate(entries, join16) {
|
|
27062
27616
|
if (entries.length === 0) {
|
|
27063
27617
|
return "No false-positive suppressions to confirm \u2014 nothing will be written.";
|
|
27064
27618
|
}
|
|
27065
27619
|
const header = entries.length === 1 ? "This looks like a false positive \u2014 take a look before I suppress it:" : `These ${String(entries.length)} look like false positives \u2014 take a look before I suppress them:`;
|
|
27066
27620
|
const blocks = entries.map((entry, i) => {
|
|
27067
|
-
const context = findContext(entry,
|
|
27621
|
+
const context = findContext(entry, join16);
|
|
27068
27622
|
const lines = [
|
|
27069
27623
|
`${String(i + 1)}. ${entry.ruleId} [${entry.category}]`,
|
|
27070
27624
|
` value: ${entry.maskedValue}`,
|
|
@@ -27294,9 +27848,9 @@ function mergeRecommendations(verdicts) {
|
|
|
27294
27848
|
}
|
|
27295
27849
|
|
|
27296
27850
|
// src/triage/plan-file.ts
|
|
27297
|
-
import { mkdtempSync, readFileSync as
|
|
27851
|
+
import { mkdtempSync, readFileSync as readFileSync8, rmdirSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
27298
27852
|
import { tmpdir } from "os";
|
|
27299
|
-
import { basename as basename5, dirname as dirname3, join as
|
|
27853
|
+
import { basename as basename5, dirname as dirname3, join as join13 } from "path";
|
|
27300
27854
|
var SuppressionEntrySchema = external_exports.object({
|
|
27301
27855
|
ruleId: external_exports.string(),
|
|
27302
27856
|
category: DetectionCategory,
|
|
@@ -27351,18 +27905,18 @@ function serializePlan(plan, current) {
|
|
|
27351
27905
|
function writePlanFile(plan, current, rawValues, deps = {}) {
|
|
27352
27906
|
const serialized = serializePlan(plan, current);
|
|
27353
27907
|
assertRawFree(serialized, rawValues);
|
|
27354
|
-
const dir = (deps.mkTempDir ?? (() => mkdtempSync(
|
|
27355
|
-
const path =
|
|
27356
|
-
|
|
27908
|
+
const dir = (deps.mkTempDir ?? (() => mkdtempSync(join13(tmpdir(), "aka-plan-"))))();
|
|
27909
|
+
const path = join13(dir, "setup-plan.json");
|
|
27910
|
+
writeFileSync6(path, serialized, { encoding: "utf8", mode: 384 });
|
|
27357
27911
|
return path;
|
|
27358
27912
|
}
|
|
27359
27913
|
function readPlanFile(path) {
|
|
27360
|
-
const text =
|
|
27914
|
+
const text = readFileSync8(path, "utf8");
|
|
27361
27915
|
const json2 = JSON.parse(text);
|
|
27362
27916
|
return PersistedPlanSchema.parse(json2);
|
|
27363
27917
|
}
|
|
27364
27918
|
function deletePlanFile(path) {
|
|
27365
|
-
|
|
27919
|
+
rmSync4(path, { force: true });
|
|
27366
27920
|
const dir = dirname3(path);
|
|
27367
27921
|
if (!basename5(dir).startsWith("aka-plan-")) return;
|
|
27368
27922
|
try {
|
|
@@ -27431,8 +27985,8 @@ function buildJoinEntries(hits) {
|
|
|
27431
27985
|
}
|
|
27432
27986
|
|
|
27433
27987
|
// src/triage/resolve.ts
|
|
27434
|
-
function resolveSuppressions(rec,
|
|
27435
|
-
const byId = new Map(
|
|
27988
|
+
function resolveSuppressions(rec, join16) {
|
|
27989
|
+
const byId = new Map(join16.map((e) => [e.id, e]));
|
|
27436
27990
|
const entries = [];
|
|
27437
27991
|
const skipped = [];
|
|
27438
27992
|
for (const cat of rec.perCategory) {
|
|
@@ -27534,7 +28088,7 @@ function parseTriageStream(text) {
|
|
|
27534
28088
|
return { hits, status: "complete" };
|
|
27535
28089
|
}
|
|
27536
28090
|
function planTriageWriteback(hits, rec) {
|
|
27537
|
-
const
|
|
28091
|
+
const join16 = buildJoinEntries(hits);
|
|
27538
28092
|
const rawValues = hits.map((h) => h.rawMatch);
|
|
27539
28093
|
const skipped = [];
|
|
27540
28094
|
const posture = {};
|
|
@@ -27574,7 +28128,7 @@ function planTriageWriteback(hits, rec) {
|
|
|
27574
28128
|
}
|
|
27575
28129
|
const { entries, skipped: resolveSkips } = resolveSuppressions(
|
|
27576
28130
|
{ perCategory: safeCategories, notes: rec.notes },
|
|
27577
|
-
|
|
28131
|
+
join16
|
|
27578
28132
|
);
|
|
27579
28133
|
skipped.push(...resolveSkips);
|
|
27580
28134
|
let notes = rec.notes;
|
|
@@ -27584,7 +28138,7 @@ function planTriageWriteback(hits, rec) {
|
|
|
27584
28138
|
if (err instanceof RawEgressError) notes = SCRUBBED_NOTES;
|
|
27585
28139
|
else throw err;
|
|
27586
28140
|
}
|
|
27587
|
-
return { entries, posture, showcase, join:
|
|
28141
|
+
return { entries, posture, showcase, join: join16, notes, skipped };
|
|
27588
28142
|
}
|
|
27589
28143
|
function recommendedPosture(evidence) {
|
|
27590
28144
|
return { ...severityFloorPosture(), ...evidence };
|
|
@@ -27840,10 +28394,10 @@ async function runConfirm(deps, planIO) {
|
|
|
27840
28394
|
}
|
|
27841
28395
|
|
|
27842
28396
|
// src/triage/judge.ts
|
|
27843
|
-
import { execFileSync } from "child_process";
|
|
27844
|
-
import { mkdtempSync as mkdtempSync2, readFileSync as
|
|
28397
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
28398
|
+
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync9, rmSync as rmSync5 } from "fs";
|
|
27845
28399
|
import { tmpdir as tmpdir2 } from "os";
|
|
27846
|
-
import { dirname as dirname4, join as
|
|
28400
|
+
import { dirname as dirname4, join as join14 } from "path";
|
|
27847
28401
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
27848
28402
|
|
|
27849
28403
|
// src/triage/parse-verdict.ts
|
|
@@ -27857,7 +28411,7 @@ function parseRecommendation(text) {
|
|
|
27857
28411
|
|
|
27858
28412
|
// src/triage/judge.ts
|
|
27859
28413
|
var TRIAGE_DIR = dirname4(fileURLToPath2(import.meta.url));
|
|
27860
|
-
var DEFAULT_RUBRIC_PATH =
|
|
28414
|
+
var DEFAULT_RUBRIC_PATH = join14(TRIAGE_DIR, "..", "..", "eval", "prompt.md");
|
|
27861
28415
|
function parseVerdict(stdout) {
|
|
27862
28416
|
let envelope;
|
|
27863
28417
|
try {
|
|
@@ -27876,20 +28430,20 @@ function parseVerdict(stdout) {
|
|
|
27876
28430
|
throw new Error("claude -p returned an unparseable TriageRecommendation");
|
|
27877
28431
|
}
|
|
27878
28432
|
}
|
|
27879
|
-
function judgeEnv() {
|
|
28433
|
+
function judgeEnv(platform2 = process.platform) {
|
|
27880
28434
|
const env = {
|
|
27881
28435
|
// eslint-disable-next-line n/no-process-env -- subprocess must inherit PATH/auth
|
|
27882
28436
|
...process.env,
|
|
27883
28437
|
CLAUDE_CODE_SKIP_PROMPT_HISTORY: "1",
|
|
27884
28438
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
27885
28439
|
};
|
|
27886
|
-
if (
|
|
27887
|
-
env.CLAUDE_CONFIG_DIR = mkdtempSync2(
|
|
28440
|
+
if (platform2 === "darwin") {
|
|
28441
|
+
env.CLAUDE_CONFIG_DIR = mkdtempSync2(join14(tmpdir2(), "aka-judge-cfg-"));
|
|
27888
28442
|
}
|
|
27889
28443
|
return env;
|
|
27890
28444
|
}
|
|
27891
28445
|
function spawnClaude(argv, env, stdin) {
|
|
27892
|
-
return
|
|
28446
|
+
return execFileSync2("claude", [...argv], {
|
|
27893
28447
|
env,
|
|
27894
28448
|
input: stdin,
|
|
27895
28449
|
encoding: "utf8",
|
|
@@ -27913,7 +28467,10 @@ function toJudgePayload(hit) {
|
|
|
27913
28467
|
return payload;
|
|
27914
28468
|
}
|
|
27915
28469
|
function runJudge(hits, deps) {
|
|
27916
|
-
|
|
28470
|
+
if (typeof deps.spawn !== "function") {
|
|
28471
|
+
throw new TypeError("runJudge requires deps.spawn \u2014 there is no live-spawn fallback");
|
|
28472
|
+
}
|
|
28473
|
+
const rubric = deps.loadRubric?.() ?? readFileSync9(DEFAULT_RUBRIC_PATH, "utf8");
|
|
27917
28474
|
const hitsJsonl = hits.map((h) => JSON.stringify(toJudgePayload(h))).join("\n");
|
|
27918
28475
|
const fullPrompt = `${rubric}
|
|
27919
28476
|
|
|
@@ -27924,7 +28481,8 @@ ${hitsJsonl}
|
|
|
27924
28481
|
\`\`\`
|
|
27925
28482
|
`;
|
|
27926
28483
|
const argv = ["-p", "--no-session-persistence", "--output-format", "json"];
|
|
27927
|
-
const
|
|
28484
|
+
const platform2 = deps.platform ?? process.platform;
|
|
28485
|
+
const env = judgeEnv(platform2);
|
|
27928
28486
|
try {
|
|
27929
28487
|
let stdout;
|
|
27930
28488
|
try {
|
|
@@ -27934,8 +28492,11 @@ ${hitsJsonl}
|
|
|
27934
28492
|
}
|
|
27935
28493
|
return parseVerdict(stdout);
|
|
27936
28494
|
} finally {
|
|
27937
|
-
if (
|
|
27938
|
-
|
|
28495
|
+
if (platform2 === "darwin" && env.CLAUDE_CONFIG_DIR) {
|
|
28496
|
+
try {
|
|
28497
|
+
rmSync5(env.CLAUDE_CONFIG_DIR, { recursive: true, force: true });
|
|
28498
|
+
} catch {
|
|
28499
|
+
}
|
|
27939
28500
|
}
|
|
27940
28501
|
}
|
|
27941
28502
|
}
|
|
@@ -27955,9 +28516,9 @@ function resolveCreatedBy() {
|
|
|
27955
28516
|
}
|
|
27956
28517
|
function loadRubric() {
|
|
27957
28518
|
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
27958
|
-
const shipped =
|
|
27959
|
-
if (
|
|
27960
|
-
return
|
|
28519
|
+
const shipped = join15(here, "triage-rubric.md");
|
|
28520
|
+
if (existsSync7(shipped)) return readFileSync10(shipped, "utf8");
|
|
28521
|
+
return readFileSync10(join15(here, "..", "eval", "prompt.md"), "utf8");
|
|
27961
28522
|
}
|
|
27962
28523
|
async function main() {
|
|
27963
28524
|
const argv = process.argv.slice(2);
|
|
@@ -27965,7 +28526,7 @@ async function main() {
|
|
|
27965
28526
|
argv,
|
|
27966
28527
|
// fd 0 = stdin; the wizard pipes `backfill.js --triage` into this on preview.
|
|
27967
28528
|
// Called only on the preview path — the confirm path never reads a stream.
|
|
27968
|
-
readStream: (streamPath) => streamPath !== void 0 ?
|
|
28529
|
+
readStream: (streamPath) => streamPath !== void 0 ? readFileSync10(streamPath, "utf8") : readFileSync10(0, "utf8"),
|
|
27969
28530
|
runJudge: (hits) => runJudge(hits, { spawn: spawnClaude, loadRubric }),
|
|
27970
28531
|
// The distinct model-judge egress consent, read from settings.json. When it
|
|
27971
28532
|
// is absent or stale the preview skips the judge instead of sending findings
|