@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
package/scripts/filescan.js
CHANGED
|
@@ -492,11 +492,11 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
495
|
+
import { existsSync as existsSync4 } from "fs";
|
|
496
|
+
import { join as join7 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/database.ts
|
|
499
|
-
import { randomUUID as
|
|
499
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
500
500
|
import { existsSync, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
501
501
|
import { join, sep } from "path";
|
|
502
502
|
import { DatabaseSync } from "node:sqlite";
|
|
@@ -562,6 +562,22 @@ var SQLITE_MIGRATIONS = [
|
|
|
562
562
|
{
|
|
563
563
|
tag: "0014_drop_legacy_events_findings",
|
|
564
564
|
sql: "-- Custom migration: drops the frozen legacy `events`/`findings` tables\n-- (superseded by audit_events/inspection_definitions/inspection_findings) and\n-- replaces them with read-only views of the same name.\n--\n-- persistence's migrations.ts applies this migration ONLY once the batched\n-- history backfill (see runLegacyHistoryBackfill) has fully drained both\n-- tables, and only after copying the live file aside \u2014 see\n-- backupBeforeLegacyDrop in packages/persistence/src/migrations.ts. A store\n-- still mid-copy keeps its real tables and this migration stays pending.\n--\n-- The views exist for skew: the `aka` CLI and the Claude Code plugin update\n-- independently against one shared store, so an older, already-installed\n-- binary can open a store a newer binary already dropped these tables on.\n-- Every already-shipped repository constructor prepares its SQL eagerly at\n-- open time, so a bare \"table not found\" would fail the WHOLE open, not just\n-- a findings-specific read \u2014 these views keep `prepare()` succeeding so an\n-- old binary's unrelated features keep working; its own reads stay truthful,\n-- and its rare (already fail-open) writes fail at run time instead.\n--\n-- The 0009/0010 expression indexes existed only over the legacy `events`\n-- table; DROP TABLE below would remove them implicitly, but they are\n-- dropped explicitly first so the intent reads clearly. IF EXISTS so a store\n-- that reached here with an index missing out of band (an adopted-tag store\n-- whose physical index was never built) does not throw a deterministic \"no\n-- such index\" out of the fail-open drop path.\nDROP INDEX IF EXISTS `idx_events_code_change_path`;\n--> statement-breakpoint\nDROP INDEX IF EXISTS `idx_events_session_id`;\n--> statement-breakpoint\n-- `findings.event_id` references `events.id` \u2014 drop the child first.\nDROP TABLE `findings`;\n--> statement-breakpoint\nDROP TABLE `events`;\n--> statement-breakpoint\n-- Legacy `events` shape, projected from audit_events: `kind`/`occurred_at`/\n-- `source_tool` become real columns again (source_tool round-trips through\n-- the attributes bag, the only place a capture-typed audit row keeps it);\n-- `metadata` is reconstructed as the old camelCase JSON object from the new\n-- snake_case attributes bag, with `root_session_id` folded back in as\n-- `sessionId` (the one legacy metadata key that became a column, never an\n-- attribute, on the new table). Constrained to the four capture kinds so\n-- structural rows (session/run/tool_call/llm_call/source_lookup/config_scan)\n-- never leak into a legacy reader's result set \u2014 the old `events` table\n-- never held them either.\nCREATE VIEW `events` AS\nSELECT\n id,\n json_extract(attributes, '$.source_tool') AS source_tool,\n event_type AS kind,\n started_at AS occurred_at,\n content_hash,\n content,\n -- Plugin-local bookkeeping column that `ensureSyncedAtColumn` adds to the\n -- real `events` table at open time. A pre-cutover binary runs that probe on\n -- EVERY open; without this projection its `columnNames('events')` check\n -- misses `synced_at` and issues `ALTER TABLE events ADD COLUMN` against this\n -- view, which SQLite rejects (\"Cannot add a column to a view\") \u2014 a hard,\n -- non-fail-open crash of the whole open, the exact skew failure these views\n -- exist to prevent. Projecting it (always NULL; no reader consumes it)\n -- short-circuits that ALTER.\n NULL AS synced_at,\n json_object(\n 'sessionId', root_session_id,\n 'repo', json_extract(attributes, '$.repo'),\n 'filePath', json_extract(attributes, '$.file_path'),\n 'toolName', json_extract(attributes, '$.tool_name'),\n 'gitignored', json_extract(attributes, '$.gitignored'),\n 'wholeFile', json_extract(attributes, '$.whole_file'),\n 'model', json_extract(attributes, '$.model'),\n 'turnIndex', json_extract(attributes, '$.turn_index'),\n 'correlationId', json_extract(attributes, '$.correlation_id'),\n 'traceId', json_extract(attributes, '$.trace_id'),\n 'exceptionIds', json_extract(attributes, '$.exception_ids')\n ) AS metadata\nFROM audit_events\nWHERE event_type IN ('prompt', 'response', 'code_change', 'tool_use');\n--> statement-breakpoint\n-- A plain single-row INSERT (the old writer's shape \u2014 no ON CONFLICT) is\n-- accepted by prepare() against a view once an INSTEAD OF INSERT trigger\n-- exists, so it fails at run time instead of at open \u2014 landing in the same\n-- fail-open path the caller already wraps its write in.\nCREATE TRIGGER `trg_events_ro`\nINSTEAD OF INSERT ON `events`\nBEGIN SELECT RAISE(ABORT, 'events is read-only; write to audit_events instead'); END;\n--> statement-breakpoint\n-- Legacy `findings` shape: rule_id/category/severity come from the joined\n-- inspection_definitions row (the legacy table inlined them per-row; the\n-- generalized schema normalizes them out into the shared definition). A view\n-- has no rowid of its own, so the underlying table's rowid is re-exposed\n-- explicitly under that name \u2014 a legacy reader ordering by `f.rowid` would\n-- otherwise fail to resolve the column at all. Joined to audit_events for the\n-- same four-capture-kind constraint as the events view above (a finding\n-- attached to a tool_call/config_scan row never existed in the legacy table\n-- either \u2014 see the persistence findings repository's identical predicate).\nCREATE VIEW `findings` AS\nSELECT\n f.id AS id,\n f.rowid AS rowid,\n f.audit_event_id AS event_id,\n d.rule_id AS rule_id,\n d.category AS category,\n d.severity AS severity,\n f.span_start AS span_start,\n f.span_end AS span_end,\n f.masked_match AS masked_match,\n f.action_taken AS action_taken,\n f.confidence AS confidence,\n f.finding_key AS finding_key,\n f.first_detected_at AS first_detected_at\nFROM inspection_findings f\nJOIN audit_events e ON e.id = f.audit_event_id\nJOIN inspection_definitions d ON d.id = f.inspection_definition_id\nWHERE e.event_type IN ('prompt', 'response', 'code_change', 'tool_use');\n--> statement-breakpoint\n-- Defense in depth only: SQLite refuses to plan ANY upsert against a view\n-- (\"cannot UPSERT a view\") no matter what trigger exists, so this can never\n-- rescue the real legacy writer, which always used\n-- `ON CONFLICT (finding_key) DO UPDATE` \u2014 that statement still fails at\n-- prepare() on an old binary, same as it would with no trigger at all. This\n-- only covers a plain-INSERT shape, should one ever exist.\nCREATE TRIGGER `trg_findings_ro`\nINSTEAD OF INSERT ON `findings`\nBEGIN SELECT RAISE(ABORT, 'findings is read-only; write to inspection_findings instead'); END;\n"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
tag: "0015_busy_vengeance",
|
|
568
|
+
sql: "CREATE TABLE `secret_vault` (\n `pointer_id` text PRIMARY KEY NOT NULL,\n `value_fingerprint` text NOT NULL,\n `fingerprint_key_version` integer NOT NULL,\n `key_version` integer NOT NULL,\n `category` text NOT NULL,\n `rule_id` text NOT NULL,\n `masked_match` text NOT NULL,\n `provider` text,\n `ciphertext` text NOT NULL,\n `nonce` text NOT NULL,\n `auth_tag` text NOT NULL,\n `occurrence_count` integer DEFAULT 1 NOT NULL,\n `first_seen` integer NOT NULL,\n `last_seen` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_secret_vault_value` ON `secret_vault` (`value_fingerprint`);--> statement-breakpoint\nCREATE TABLE `secret_vault_deref` (\n `id` text PRIMARY KEY NOT NULL,\n `pointer_id` text NOT NULL,\n `at` integer NOT NULL,\n `target` text NOT NULL,\n `reason` text NOT NULL,\n `outcome` text NOT NULL,\n `grant_id` text,\n `pointer_count` integer DEFAULT 1 NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_pointer` ON `secret_vault_deref` (`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_reason_at` ON `secret_vault_deref` (`reason`,`at`);"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
tag: "0016_breezy_zodiak",
|
|
572
|
+
sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
tag: "0017_rainy_kat_farrell",
|
|
576
|
+
sql: "CREATE TABLE `secret_vault_sighting` (\n `id` text PRIMARY KEY NOT NULL,\n `pointer_id` text NOT NULL,\n `location` text NOT NULL,\n `kind` text NOT NULL,\n `first_seen` integer NOT NULL,\n `last_seen` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_secret_vault_sighting` ON `secret_vault_sighting` (`pointer_id`,`location`);"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
tag: "0018_serious_tana_nile",
|
|
580
|
+
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
565
581
|
}
|
|
566
582
|
];
|
|
567
583
|
|
|
@@ -16218,6 +16234,7 @@ var ExceptionConditions = external_exports.object({
|
|
|
16218
16234
|
sourceTool: external_exports.string().optional(),
|
|
16219
16235
|
provider: external_exports.string().optional()
|
|
16220
16236
|
}).strict();
|
|
16237
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16221
16238
|
var DetectionException = external_exports.object({
|
|
16222
16239
|
id: external_exports.guid(),
|
|
16223
16240
|
ruleId: external_exports.string(),
|
|
@@ -16234,6 +16251,7 @@ var DetectionException = external_exports.object({
|
|
|
16234
16251
|
keyVersion: external_exports.number().int().positive(),
|
|
16235
16252
|
// maskMatch() preview of the approved value — never the raw value.
|
|
16236
16253
|
maskedValue: external_exports.string(),
|
|
16254
|
+
capability: ExceptionCapability.default("suppress"),
|
|
16237
16255
|
scope: ExceptionScope,
|
|
16238
16256
|
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16239
16257
|
maxUses: external_exports.number().int().positive().nullable(),
|
|
@@ -16257,6 +16275,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16257
16275
|
ruleId: true,
|
|
16258
16276
|
valueFingerprint: true,
|
|
16259
16277
|
keyVersion: true,
|
|
16278
|
+
capability: true,
|
|
16260
16279
|
expiresAt: true,
|
|
16261
16280
|
maxUses: true,
|
|
16262
16281
|
useCount: true,
|
|
@@ -17361,8 +17380,120 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17361
17380
|
message: "At least one field must be provided"
|
|
17362
17381
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17363
17382
|
|
|
17383
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17384
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17385
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17386
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17387
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17388
|
+
);
|
|
17389
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17390
|
+
function pointerTokenScanner() {
|
|
17391
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
17392
|
+
}
|
|
17393
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17394
|
+
var ParsedPointer = external_exports.object({
|
|
17395
|
+
category: DetectionCategory,
|
|
17396
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17397
|
+
pointerId: external_exports.string(),
|
|
17398
|
+
tag: external_exports.string()
|
|
17399
|
+
});
|
|
17400
|
+
var VaultEntry = external_exports.object({
|
|
17401
|
+
pointerId: external_exports.string(),
|
|
17402
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17403
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17404
|
+
// independently of the vault encryption key below.
|
|
17405
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17406
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17407
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17408
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17409
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17410
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17411
|
+
// value always produces exactly one wire token.
|
|
17412
|
+
category: DetectionCategory,
|
|
17413
|
+
ruleId: external_exports.string(),
|
|
17414
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17415
|
+
maskedMatch: external_exports.string(),
|
|
17416
|
+
provider: external_exports.string().optional(),
|
|
17417
|
+
ciphertext: external_exports.string(),
|
|
17418
|
+
nonce: external_exports.string(),
|
|
17419
|
+
authTag: external_exports.string(),
|
|
17420
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17421
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17422
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17423
|
+
firstSeen: external_exports.string(),
|
|
17424
|
+
lastSeen: external_exports.string()
|
|
17425
|
+
});
|
|
17426
|
+
var PointerDescriptor = external_exports.object({
|
|
17427
|
+
category: DetectionCategory,
|
|
17428
|
+
provider: external_exports.string().optional(),
|
|
17429
|
+
maskedMatch: external_exports.string(),
|
|
17430
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17431
|
+
firstSeen: external_exports.string(),
|
|
17432
|
+
lastSeen: external_exports.string()
|
|
17433
|
+
});
|
|
17434
|
+
var PointerIdentity = external_exports.object({
|
|
17435
|
+
ruleId: external_exports.string(),
|
|
17436
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17437
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17438
|
+
});
|
|
17439
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17440
|
+
var VaultDerefReason = external_exports.enum([
|
|
17441
|
+
"display",
|
|
17442
|
+
"explicit-reveal",
|
|
17443
|
+
"view-render",
|
|
17444
|
+
"model-input",
|
|
17445
|
+
"remediation",
|
|
17446
|
+
"purge"
|
|
17447
|
+
]);
|
|
17448
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17449
|
+
var VaultDeref = external_exports.object({
|
|
17450
|
+
id: external_exports.guid(),
|
|
17451
|
+
pointerId: external_exports.string(),
|
|
17452
|
+
at: external_exports.string(),
|
|
17453
|
+
target: DetokenizeTarget,
|
|
17454
|
+
reason: VaultDerefReason,
|
|
17455
|
+
outcome: VaultDerefOutcome,
|
|
17456
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17457
|
+
grantId: external_exports.string().optional(),
|
|
17458
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17459
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17460
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17461
|
+
});
|
|
17462
|
+
var VaultSightingKind = external_exports.enum([
|
|
17463
|
+
"prompt",
|
|
17464
|
+
"tool-input",
|
|
17465
|
+
"tool-output",
|
|
17466
|
+
"file",
|
|
17467
|
+
"transcript"
|
|
17468
|
+
]);
|
|
17469
|
+
var VaultSighting = external_exports.object({
|
|
17470
|
+
location: external_exports.string(),
|
|
17471
|
+
kind: VaultSightingKind,
|
|
17472
|
+
firstSeen: external_exports.string(),
|
|
17473
|
+
lastSeen: external_exports.string()
|
|
17474
|
+
});
|
|
17475
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17476
|
+
pointerId: external_exports.string(),
|
|
17477
|
+
category: DetectionCategory,
|
|
17478
|
+
provider: external_exports.string().optional(),
|
|
17479
|
+
maskedMatch: external_exports.string(),
|
|
17480
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17481
|
+
firstSeen: external_exports.string(),
|
|
17482
|
+
lastSeen: external_exports.string(),
|
|
17483
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17484
|
+
// the inventory badges it, the row links to revocation.
|
|
17485
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17486
|
+
sightings: external_exports.array(VaultSighting)
|
|
17487
|
+
});
|
|
17488
|
+
var VaultKeyCustody = external_exports.string();
|
|
17489
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17490
|
+
var VaultConsent = external_exports.object({
|
|
17491
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17492
|
+
version: external_exports.number().int().positive()
|
|
17493
|
+
});
|
|
17494
|
+
|
|
17364
17495
|
// ../../packages/schema/src/zod/local.ts
|
|
17365
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17496
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17366
17497
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17367
17498
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17368
17499
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
@@ -17384,6 +17515,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17384
17515
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17385
17516
|
// Shares writes.
|
|
17386
17517
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17518
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17519
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17520
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17521
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17522
|
+
// purging the vault is the eraser.
|
|
17523
|
+
vaultConsent: VaultConsent.optional(),
|
|
17524
|
+
// Where the vault master key lives.
|
|
17525
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17526
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17527
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17387
17528
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17388
17529
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17389
17530
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -19798,6 +19939,9 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19798
19939
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19799
19940
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19800
19941
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
19942
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
19943
|
+
AND conditions IS NULL
|
|
19944
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19801
19945
|
var SqliteExceptionsRepository = class {
|
|
19802
19946
|
constructor(db) {
|
|
19803
19947
|
this.db = db;
|
|
@@ -19889,11 +20033,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19889
20033
|
this.db.prepare(
|
|
19890
20034
|
`INSERT INTO exceptions (
|
|
19891
20035
|
id, rule_id, category, value_fingerprint, key_version, masked_value,
|
|
19892
|
-
scope, expires_at, max_uses, use_count, last_used_at,
|
|
19893
|
-
conditions, created_by, created_via, created_at, updated_at
|
|
20036
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20037
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19894
20038
|
) VALUES (
|
|
19895
20039
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19896
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20040
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19897
20041
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19898
20042
|
)`
|
|
19899
20043
|
).run({
|
|
@@ -19903,6 +20047,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19903
20047
|
valueFingerprint: input.valueFingerprint,
|
|
19904
20048
|
keyVersion: input.keyVersion,
|
|
19905
20049
|
maskedValue: input.maskedValue,
|
|
20050
|
+
capability: input.capability ?? "suppress",
|
|
19906
20051
|
scope: input.scope,
|
|
19907
20052
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19908
20053
|
maxUses: input.maxUses,
|
|
@@ -19996,6 +20141,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19996
20141
|
ruleId: row.rule_id,
|
|
19997
20142
|
valueFingerprint: row.value_fingerprint,
|
|
19998
20143
|
keyVersion: row.key_version,
|
|
20144
|
+
capability: row.capability,
|
|
19999
20145
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20000
20146
|
maxUses: row.max_uses,
|
|
20001
20147
|
useCount: row.use_count,
|
|
@@ -20050,6 +20196,35 @@ var SqliteExceptionsRepository = class {
|
|
|
20050
20196
|
}))
|
|
20051
20197
|
);
|
|
20052
20198
|
}
|
|
20199
|
+
/**
|
|
20200
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20201
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20202
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20203
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20204
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20205
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20206
|
+
*
|
|
20207
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20208
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20209
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20210
|
+
*/
|
|
20211
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now = Date.now()) {
|
|
20212
|
+
try {
|
|
20213
|
+
const row = getRow(
|
|
20214
|
+
this.db.prepare(
|
|
20215
|
+
`SELECT id FROM exceptions
|
|
20216
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20217
|
+
AND key_version = :keyVersion
|
|
20218
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20219
|
+
LIMIT 1`
|
|
20220
|
+
),
|
|
20221
|
+
{ ruleId, valueFingerprint, keyVersion, now }
|
|
20222
|
+
);
|
|
20223
|
+
return Promise.resolve(row ?? null);
|
|
20224
|
+
} catch (err) {
|
|
20225
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20226
|
+
}
|
|
20227
|
+
}
|
|
20053
20228
|
/**
|
|
20054
20229
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20055
20230
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20077,6 +20252,7 @@ function parseExceptionRow(row) {
|
|
|
20077
20252
|
valueFingerprint: row.value_fingerprint,
|
|
20078
20253
|
keyVersion: row.key_version,
|
|
20079
20254
|
maskedValue: row.masked_value,
|
|
20255
|
+
capability: row.capability,
|
|
20080
20256
|
scope: row.scope,
|
|
20081
20257
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20082
20258
|
maxUses: row.max_uses,
|
|
@@ -22242,6 +22418,287 @@ var SqliteScanLedgerRepository = class {
|
|
|
22242
22418
|
}
|
|
22243
22419
|
};
|
|
22244
22420
|
|
|
22421
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
22422
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22423
|
+
var SELECT_COLUMNS = `
|
|
22424
|
+
pointer_id AS pointerId,
|
|
22425
|
+
value_fingerprint AS valueFingerprint,
|
|
22426
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
22427
|
+
key_version AS keyVersion,
|
|
22428
|
+
format_version AS formatVersion,
|
|
22429
|
+
category,
|
|
22430
|
+
rule_id AS ruleId,
|
|
22431
|
+
masked_match AS maskedMatch,
|
|
22432
|
+
provider,
|
|
22433
|
+
ciphertext,
|
|
22434
|
+
nonce,
|
|
22435
|
+
auth_tag AS authTag,
|
|
22436
|
+
occurrence_count AS occurrenceCount,
|
|
22437
|
+
first_seen AS firstSeen,
|
|
22438
|
+
last_seen AS lastSeen`;
|
|
22439
|
+
function toRow(raw) {
|
|
22440
|
+
const { provider, ...rest } = raw;
|
|
22441
|
+
return provider === null ? rest : { ...rest, provider };
|
|
22442
|
+
}
|
|
22443
|
+
var SqliteSecretVaultRepository = class {
|
|
22444
|
+
constructor(db) {
|
|
22445
|
+
this.db = db;
|
|
22446
|
+
this.insertStmt = db.prepare(
|
|
22447
|
+
`INSERT INTO secret_vault (
|
|
22448
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
22449
|
+
format_version, category, rule_id, masked_match, provider,
|
|
22450
|
+
ciphertext, nonce, auth_tag,
|
|
22451
|
+
occurrence_count, first_seen, last_seen
|
|
22452
|
+
) VALUES (
|
|
22453
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
22454
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
22455
|
+
:ciphertext, :nonce, :authTag,
|
|
22456
|
+
1, :now, :now
|
|
22457
|
+
)`
|
|
22458
|
+
);
|
|
22459
|
+
this.bumpStmt = db.prepare(
|
|
22460
|
+
`UPDATE secret_vault
|
|
22461
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
22462
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
22463
|
+
);
|
|
22464
|
+
this.byPointerStmt = db.prepare(
|
|
22465
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
22466
|
+
);
|
|
22467
|
+
this.byFingerprintStmt = db.prepare(
|
|
22468
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
22469
|
+
);
|
|
22470
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
22471
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
22472
|
+
`UPDATE secret_vault
|
|
22473
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
22474
|
+
WHERE pointer_id = :pointerId`
|
|
22475
|
+
);
|
|
22476
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
22477
|
+
`UPDATE secret_vault
|
|
22478
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
22479
|
+
WHERE pointer_id = :pointerId`
|
|
22480
|
+
);
|
|
22481
|
+
this.derefStmt = db.prepare(
|
|
22482
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
22483
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
22484
|
+
);
|
|
22485
|
+
}
|
|
22486
|
+
db;
|
|
22487
|
+
insertStmt;
|
|
22488
|
+
bumpStmt;
|
|
22489
|
+
byPointerStmt;
|
|
22490
|
+
byFingerprintStmt;
|
|
22491
|
+
listStmt;
|
|
22492
|
+
replaceCiphertextStmt;
|
|
22493
|
+
refreshFingerprintStmt;
|
|
22494
|
+
derefStmt;
|
|
22495
|
+
/**
|
|
22496
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
22497
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
22498
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
22499
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
22500
|
+
* wire token. `minted` is true only when this call created the row.
|
|
22501
|
+
*
|
|
22502
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
22503
|
+
* writers cannot both decide they are minting.
|
|
22504
|
+
*/
|
|
22505
|
+
upsert(input, now) {
|
|
22506
|
+
let minted = false;
|
|
22507
|
+
withTransaction(
|
|
22508
|
+
this.db,
|
|
22509
|
+
() => {
|
|
22510
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
22511
|
+
valueFingerprint: input.valueFingerprint
|
|
22512
|
+
});
|
|
22513
|
+
if (existing === void 0) {
|
|
22514
|
+
this.insertStmt.run(
|
|
22515
|
+
bindParams({
|
|
22516
|
+
pointerId: input.pointerId,
|
|
22517
|
+
valueFingerprint: input.valueFingerprint,
|
|
22518
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
22519
|
+
keyVersion: input.keyVersion,
|
|
22520
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
22521
|
+
category: input.category,
|
|
22522
|
+
ruleId: input.ruleId,
|
|
22523
|
+
maskedMatch: input.maskedMatch,
|
|
22524
|
+
provider: input.provider,
|
|
22525
|
+
ciphertext: input.ciphertext,
|
|
22526
|
+
nonce: input.nonce,
|
|
22527
|
+
authTag: input.authTag,
|
|
22528
|
+
now
|
|
22529
|
+
})
|
|
22530
|
+
);
|
|
22531
|
+
minted = true;
|
|
22532
|
+
return;
|
|
22533
|
+
}
|
|
22534
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
22535
|
+
},
|
|
22536
|
+
"IMMEDIATE"
|
|
22537
|
+
);
|
|
22538
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
22539
|
+
valueFingerprint: input.valueFingerprint
|
|
22540
|
+
});
|
|
22541
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
22542
|
+
return { row: toRow(row), minted };
|
|
22543
|
+
}
|
|
22544
|
+
byPointerId(pointerId) {
|
|
22545
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
22546
|
+
return raw === void 0 ? null : toRow(raw);
|
|
22547
|
+
}
|
|
22548
|
+
byValueFingerprint(fingerprint) {
|
|
22549
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
22550
|
+
return raw === void 0 ? null : toRow(raw);
|
|
22551
|
+
}
|
|
22552
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
22553
|
+
recordDeref(entry) {
|
|
22554
|
+
this.derefStmt.run(
|
|
22555
|
+
bindParams({
|
|
22556
|
+
id: entry.id,
|
|
22557
|
+
pointerId: entry.pointerId,
|
|
22558
|
+
at: entry.at,
|
|
22559
|
+
target: entry.target,
|
|
22560
|
+
reason: entry.reason,
|
|
22561
|
+
outcome: entry.outcome,
|
|
22562
|
+
grantId: entry.grantId,
|
|
22563
|
+
pointerCount: entry.pointerCount ?? 1
|
|
22564
|
+
})
|
|
22565
|
+
);
|
|
22566
|
+
}
|
|
22567
|
+
listAll() {
|
|
22568
|
+
return allRows(this.listStmt).map(toRow);
|
|
22569
|
+
}
|
|
22570
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
22571
|
+
replaceCiphertext(pointerId, next) {
|
|
22572
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
22573
|
+
}
|
|
22574
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
22575
|
+
refreshFingerprint(pointerId, next) {
|
|
22576
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
22577
|
+
}
|
|
22578
|
+
/**
|
|
22579
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
22580
|
+
* audit is left alone on purpose — see the table note above.
|
|
22581
|
+
*/
|
|
22582
|
+
purgeAll() {
|
|
22583
|
+
let destroyed = 0;
|
|
22584
|
+
withTransaction(
|
|
22585
|
+
this.db,
|
|
22586
|
+
() => {
|
|
22587
|
+
destroyed = this.countEntries();
|
|
22588
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
22589
|
+
},
|
|
22590
|
+
"IMMEDIATE"
|
|
22591
|
+
);
|
|
22592
|
+
return destroyed;
|
|
22593
|
+
}
|
|
22594
|
+
/**
|
|
22595
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
22596
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
22597
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
22598
|
+
* so callers wrap this, not the other way around.
|
|
22599
|
+
*/
|
|
22600
|
+
recordSighting(entry, now) {
|
|
22601
|
+
this.db.prepare(
|
|
22602
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
22603
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
22604
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
22605
|
+
).run({
|
|
22606
|
+
id: randomUUID7(),
|
|
22607
|
+
pointerId: entry.pointerId,
|
|
22608
|
+
location: entry.location,
|
|
22609
|
+
kind: entry.kind,
|
|
22610
|
+
now
|
|
22611
|
+
});
|
|
22612
|
+
}
|
|
22613
|
+
listSightings(pointerId) {
|
|
22614
|
+
const rows = allRows(
|
|
22615
|
+
this.db.prepare(
|
|
22616
|
+
`SELECT location, kind, first_seen, last_seen FROM secret_vault_sighting
|
|
22617
|
+
WHERE pointer_id = :pointerId ORDER BY last_seen DESC`
|
|
22618
|
+
),
|
|
22619
|
+
{ pointerId }
|
|
22620
|
+
);
|
|
22621
|
+
return rows.map((r) => ({
|
|
22622
|
+
location: r.location,
|
|
22623
|
+
kind: r.kind,
|
|
22624
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22625
|
+
lastSeen: new Date(r.last_seen).toISOString()
|
|
22626
|
+
}));
|
|
22627
|
+
}
|
|
22628
|
+
/**
|
|
22629
|
+
* The dashboard inventory: every vaulted value's descriptor data joined with
|
|
22630
|
+
* its sightings and the active reveal-to-model grant when one exists.
|
|
22631
|
+
* Raw-free by construction — neither the fingerprint nor the ciphertext
|
|
22632
|
+
* columns are selected.
|
|
22633
|
+
*/
|
|
22634
|
+
listInventory(now = Date.now()) {
|
|
22635
|
+
const rows = allRows(
|
|
22636
|
+
this.db.prepare(
|
|
22637
|
+
`SELECT v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
22638
|
+
v.occurrence_count, v.first_seen, v.last_seen,
|
|
22639
|
+
(SELECT e.id FROM exceptions e
|
|
22640
|
+
WHERE e.rule_id = v.rule_id
|
|
22641
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
22642
|
+
AND e.key_version = v.fingerprint_key_version
|
|
22643
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
22644
|
+
LIMIT 1) AS grant_id
|
|
22645
|
+
FROM secret_vault v
|
|
22646
|
+
ORDER BY v.last_seen DESC`
|
|
22647
|
+
),
|
|
22648
|
+
{ now }
|
|
22649
|
+
);
|
|
22650
|
+
return rows.map((r) => ({
|
|
22651
|
+
pointerId: r.pointer_id,
|
|
22652
|
+
category: r.category,
|
|
22653
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
22654
|
+
maskedMatch: r.masked_match,
|
|
22655
|
+
occurrences: r.occurrence_count,
|
|
22656
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
22657
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
22658
|
+
revealGrantId: r.grant_id,
|
|
22659
|
+
sightings: this.listSightings(r.pointer_id)
|
|
22660
|
+
}));
|
|
22661
|
+
}
|
|
22662
|
+
/**
|
|
22663
|
+
* The de-reference trail, newest first. By default the batched, high-volume
|
|
22664
|
+
* reasons (display, view-render) are hidden and counted instead — the rows
|
|
22665
|
+
* that matter as a signal are the model crossings, and burying them under
|
|
22666
|
+
* render noise would defeat the audit's purpose.
|
|
22667
|
+
*/
|
|
22668
|
+
listDerefs(opts) {
|
|
22669
|
+
const limit = opts?.limit ?? 200;
|
|
22670
|
+
const where = opts?.includeBatched === true ? "" : `WHERE reason NOT IN ('display', 'view-render')`;
|
|
22671
|
+
const rows = allRows(
|
|
22672
|
+
this.db.prepare(
|
|
22673
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
22674
|
+
FROM secret_vault_deref ${where}
|
|
22675
|
+
ORDER BY at DESC, rowid DESC LIMIT :limit`
|
|
22676
|
+
),
|
|
22677
|
+
{ limit }
|
|
22678
|
+
);
|
|
22679
|
+
const hiddenBatched = opts?.includeBatched === true ? 0 : countScalar(
|
|
22680
|
+
this.db,
|
|
22681
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
22682
|
+
);
|
|
22683
|
+
return {
|
|
22684
|
+
rows: rows.map((r) => ({
|
|
22685
|
+
id: r.id,
|
|
22686
|
+
pointerId: r.pointer_id,
|
|
22687
|
+
at: new Date(r.at).toISOString(),
|
|
22688
|
+
target: r.target,
|
|
22689
|
+
reason: r.reason,
|
|
22690
|
+
outcome: r.outcome,
|
|
22691
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
22692
|
+
pointerCount: r.pointer_count
|
|
22693
|
+
})),
|
|
22694
|
+
hiddenBatched
|
|
22695
|
+
};
|
|
22696
|
+
}
|
|
22697
|
+
countEntries() {
|
|
22698
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
22699
|
+
}
|
|
22700
|
+
};
|
|
22701
|
+
|
|
22245
22702
|
// ../../packages/persistence/src/repositories/security.ts
|
|
22246
22703
|
var DAY_MS4 = 864e5;
|
|
22247
22704
|
var SEVERITIES = ["critical", "high", "medium", "low"];
|
|
@@ -22587,7 +23044,7 @@ var SqliteSecurityRepository = class {
|
|
|
22587
23044
|
};
|
|
22588
23045
|
|
|
22589
23046
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22590
|
-
import { randomUUID as
|
|
23047
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
22591
23048
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22592
23049
|
var IN_CHUNK = 500;
|
|
22593
23050
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22843,7 +23300,7 @@ var SqliteSharesRepository = class {
|
|
|
22843
23300
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22844
23301
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22845
23302
|
).run({
|
|
22846
|
-
id:
|
|
23303
|
+
id: randomUUID8(),
|
|
22847
23304
|
destinationId,
|
|
22848
23305
|
host: dest.host,
|
|
22849
23306
|
decision,
|
|
@@ -22992,7 +23449,7 @@ var SqliteSharesRepository = class {
|
|
|
22992
23449
|
let destinationId = destIds.get(hit.host);
|
|
22993
23450
|
if (destinationId === void 0) {
|
|
22994
23451
|
destStmt.run({
|
|
22995
|
-
id:
|
|
23452
|
+
id: randomUUID8(),
|
|
22996
23453
|
kind: hit.kind,
|
|
22997
23454
|
name: hit.name,
|
|
22998
23455
|
host: hit.host,
|
|
@@ -23008,7 +23465,7 @@ var SqliteSharesRepository = class {
|
|
|
23008
23465
|
let endpointId = endpointIds.get(endpointKey);
|
|
23009
23466
|
if (endpointId === void 0) {
|
|
23010
23467
|
endpointStmt.run({
|
|
23011
|
-
id:
|
|
23468
|
+
id: randomUUID8(),
|
|
23012
23469
|
destinationId,
|
|
23013
23470
|
method: hit.method,
|
|
23014
23471
|
transport: hit.transport,
|
|
@@ -23021,7 +23478,7 @@ var SqliteSharesRepository = class {
|
|
|
23021
23478
|
endpointIds.set(endpointKey, endpointId);
|
|
23022
23479
|
}
|
|
23023
23480
|
siteStmt.run({
|
|
23024
|
-
id:
|
|
23481
|
+
id: randomUUID8(),
|
|
23025
23482
|
endpointId,
|
|
23026
23483
|
project: input.project,
|
|
23027
23484
|
projectKey: input.projectKey,
|
|
@@ -23389,11 +23846,22 @@ function purgeSampleData(db) {
|
|
|
23389
23846
|
function linkHost(input, hostId) {
|
|
23390
23847
|
return hostId ? { ...input, hostId } : input;
|
|
23391
23848
|
}
|
|
23849
|
+
function closeQuietly(db) {
|
|
23850
|
+
try {
|
|
23851
|
+
db.close();
|
|
23852
|
+
} catch {
|
|
23853
|
+
}
|
|
23854
|
+
}
|
|
23392
23855
|
function openWithPragmas(file2) {
|
|
23393
23856
|
const db = new DatabaseSync(file2);
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
|
|
23857
|
+
try {
|
|
23858
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
23859
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
23860
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
23861
|
+
} catch (err) {
|
|
23862
|
+
closeQuietly(db);
|
|
23863
|
+
throw err;
|
|
23864
|
+
}
|
|
23397
23865
|
return db;
|
|
23398
23866
|
}
|
|
23399
23867
|
function backupLegacyStore(file2) {
|
|
@@ -23405,43 +23873,82 @@ function backupLegacyStore(file2) {
|
|
|
23405
23873
|
}
|
|
23406
23874
|
return backup;
|
|
23407
23875
|
}
|
|
23876
|
+
function openAndInitialize(file2) {
|
|
23877
|
+
let db = openWithPragmas(file2);
|
|
23878
|
+
try {
|
|
23879
|
+
if (isForeignSqliteLineage(db)) {
|
|
23880
|
+
db.close();
|
|
23881
|
+
const backup = backupLegacyStore(file2);
|
|
23882
|
+
db = openWithPragmas(file2);
|
|
23883
|
+
akaWarn(
|
|
23884
|
+
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
23885
|
+
);
|
|
23886
|
+
}
|
|
23887
|
+
applyMigrations(db, file2);
|
|
23888
|
+
tightenPerms(file2);
|
|
23889
|
+
const policies = new SqlitePoliciesRepository(db);
|
|
23890
|
+
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
23891
|
+
const repositories = {
|
|
23892
|
+
events: new SqliteEventsRepository(db),
|
|
23893
|
+
findings: new SqliteFindingsRepository(db),
|
|
23894
|
+
policies,
|
|
23895
|
+
installedPacks,
|
|
23896
|
+
scanLedger: new SqliteScanLedgerRepository(db),
|
|
23897
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23898
|
+
exceptions: new SqliteExceptionsRepository(db),
|
|
23899
|
+
resolutions: new SqliteResolutionsRepository(db),
|
|
23900
|
+
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
23901
|
+
security: new SqliteSecurityRepository(db),
|
|
23902
|
+
detections: new SqliteDetectionsRepository(db),
|
|
23903
|
+
shares: new SqliteSharesRepository(db),
|
|
23904
|
+
policyCatalog: new SqlitePolicyCatalogRepository(installedPacks),
|
|
23905
|
+
inventory: new SqliteInventoryRepository(db),
|
|
23906
|
+
inventoryAssets: new SqliteInventoryAssetsRepository(db),
|
|
23907
|
+
projectFiles: new SqliteProjectFilesRepository(db),
|
|
23908
|
+
activity: new SqliteActivityRepository(db),
|
|
23909
|
+
sourceProject: new SqliteSourceProjectRepository(db),
|
|
23910
|
+
auditEvents: new SqliteAuditEventsRepository(db),
|
|
23911
|
+
classifiedData: new SqliteClassifiedDataRepository(db),
|
|
23912
|
+
inspectionDefinitions: new SqliteInspectionDefinitionsRepository(db),
|
|
23913
|
+
inspectionFindings: new SqliteInspectionFindingsRepository(db),
|
|
23914
|
+
configInventory: new SqliteConfigInventoryRepository(db)
|
|
23915
|
+
};
|
|
23916
|
+
policies.seedDefaults();
|
|
23917
|
+
return { db, ...repositories };
|
|
23918
|
+
} catch (err) {
|
|
23919
|
+
closeQuietly(db);
|
|
23920
|
+
throw err;
|
|
23921
|
+
}
|
|
23922
|
+
}
|
|
23408
23923
|
function openLocalDatabase(dir) {
|
|
23409
23924
|
ensureDataDirSync(dir);
|
|
23410
23925
|
const file2 = join(dir, DB_FILENAME);
|
|
23411
|
-
|
|
23412
|
-
|
|
23413
|
-
|
|
23414
|
-
|
|
23415
|
-
|
|
23416
|
-
|
|
23417
|
-
|
|
23418
|
-
|
|
23419
|
-
|
|
23420
|
-
|
|
23421
|
-
|
|
23422
|
-
|
|
23423
|
-
|
|
23424
|
-
|
|
23425
|
-
|
|
23426
|
-
|
|
23427
|
-
|
|
23428
|
-
|
|
23429
|
-
|
|
23430
|
-
|
|
23431
|
-
|
|
23432
|
-
|
|
23433
|
-
|
|
23434
|
-
|
|
23435
|
-
|
|
23436
|
-
|
|
23437
|
-
const activity = new SqliteActivityRepository(db);
|
|
23438
|
-
const sourceProject = new SqliteSourceProjectRepository(db);
|
|
23439
|
-
const auditEvents = new SqliteAuditEventsRepository(db);
|
|
23440
|
-
const classifiedData = new SqliteClassifiedDataRepository(db);
|
|
23441
|
-
const inspectionDefinitions = new SqliteInspectionDefinitionsRepository(db);
|
|
23442
|
-
const inspectionFindings = new SqliteInspectionFindingsRepository(db);
|
|
23443
|
-
const configInventory = new SqliteConfigInventoryRepository(db);
|
|
23444
|
-
policies.seedDefaults();
|
|
23926
|
+
const {
|
|
23927
|
+
db,
|
|
23928
|
+
events,
|
|
23929
|
+
findings,
|
|
23930
|
+
policies,
|
|
23931
|
+
installedPacks,
|
|
23932
|
+
scanLedger,
|
|
23933
|
+
secretVault,
|
|
23934
|
+
exceptions,
|
|
23935
|
+
resolutions,
|
|
23936
|
+
ruleProbeCache,
|
|
23937
|
+
security,
|
|
23938
|
+
detections,
|
|
23939
|
+
shares,
|
|
23940
|
+
policyCatalog,
|
|
23941
|
+
inventory,
|
|
23942
|
+
inventoryAssets,
|
|
23943
|
+
projectFiles,
|
|
23944
|
+
activity,
|
|
23945
|
+
sourceProject,
|
|
23946
|
+
auditEvents,
|
|
23947
|
+
classifiedData,
|
|
23948
|
+
inspectionDefinitions,
|
|
23949
|
+
inspectionFindings,
|
|
23950
|
+
configInventory
|
|
23951
|
+
} = openAndInitialize(file2);
|
|
23445
23952
|
function recordCapture(event, detected) {
|
|
23446
23953
|
failOpenTransaction(db, () => {
|
|
23447
23954
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -23532,7 +24039,7 @@ function openLocalDatabase(dir) {
|
|
|
23532
24039
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23533
24040
|
if (!definitionId) continue;
|
|
23534
24041
|
inspectionFindings.insertFinding({
|
|
23535
|
-
id:
|
|
24042
|
+
id: randomUUID9(),
|
|
23536
24043
|
auditEventId: record2.scanEvent.id,
|
|
23537
24044
|
inspectionDefinitionId: definitionId,
|
|
23538
24045
|
span: finding.span,
|
|
@@ -23609,6 +24116,7 @@ function openLocalDatabase(dir) {
|
|
|
23609
24116
|
policies,
|
|
23610
24117
|
installedPacks,
|
|
23611
24118
|
scanLedger,
|
|
24119
|
+
secretVault,
|
|
23612
24120
|
exceptions,
|
|
23613
24121
|
resolutions,
|
|
23614
24122
|
ruleProbeCache,
|
|
@@ -23653,8 +24161,9 @@ function computeFindingKey(input) {
|
|
|
23653
24161
|
|
|
23654
24162
|
// ../../packages/persistence/src/fingerprint.ts
|
|
23655
24163
|
import { createHmac, randomBytes } from "crypto";
|
|
23656
|
-
import { readFileSync } from "fs";
|
|
24164
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
23657
24165
|
import { join as join2 } from "path";
|
|
24166
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23658
24167
|
var KEY_FILENAME = "exception.key";
|
|
23659
24168
|
var KEY_MATERIAL_BYTES = 32;
|
|
23660
24169
|
function keyFilePath(dataDir2) {
|
|
@@ -23678,6 +24187,50 @@ function parseKeyFile(raw) {
|
|
|
23678
24187
|
}
|
|
23679
24188
|
return { version: version2, material: bytes };
|
|
23680
24189
|
}
|
|
24190
|
+
var KEY_VERSION_COLUMNS = {
|
|
24191
|
+
exceptions: "key_version",
|
|
24192
|
+
blocked_detections: "key_version",
|
|
24193
|
+
secret_vault: "fingerprint_key_version"
|
|
24194
|
+
};
|
|
24195
|
+
var SQLITE_ERROR = 1;
|
|
24196
|
+
var FLOOR_BUSY_TIMEOUT_MS = 250;
|
|
24197
|
+
var FloorUnreadableError = class extends Error {
|
|
24198
|
+
code = "floor-unreadable";
|
|
24199
|
+
constructor(cause) {
|
|
24200
|
+
super(
|
|
24201
|
+
`cannot read the stored fingerprint key versions: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
24202
|
+
{ cause }
|
|
24203
|
+
);
|
|
24204
|
+
this.name = "FloorUnreadableError";
|
|
24205
|
+
}
|
|
24206
|
+
};
|
|
24207
|
+
function storedKeyVersionFloor(dataDir2) {
|
|
24208
|
+
const file2 = join2(dataDir2, DB_FILENAME);
|
|
24209
|
+
if (!existsSync2(file2)) return 0;
|
|
24210
|
+
let db;
|
|
24211
|
+
try {
|
|
24212
|
+
db = new DatabaseSync2(file2, { readOnly: true });
|
|
24213
|
+
db.exec(`PRAGMA busy_timeout = ${String(FLOOR_BUSY_TIMEOUT_MS)}`);
|
|
24214
|
+
let floor = 0;
|
|
24215
|
+
for (const [table2, column] of Object.entries(KEY_VERSION_COLUMNS)) {
|
|
24216
|
+
try {
|
|
24217
|
+
const row = getRow(
|
|
24218
|
+
db.prepare(`SELECT MAX(${column}) AS v FROM ${table2}`)
|
|
24219
|
+
);
|
|
24220
|
+
floor = Math.max(floor, row?.v ?? 0);
|
|
24221
|
+
} catch (err) {
|
|
24222
|
+
if (err.errcode !== SQLITE_ERROR) {
|
|
24223
|
+
throw new FloorUnreadableError(err);
|
|
24224
|
+
}
|
|
24225
|
+
}
|
|
24226
|
+
}
|
|
24227
|
+
return floor;
|
|
24228
|
+
} catch (err) {
|
|
24229
|
+
throw err instanceof FloorUnreadableError ? err : new FloorUnreadableError(err);
|
|
24230
|
+
} finally {
|
|
24231
|
+
db?.close();
|
|
24232
|
+
}
|
|
24233
|
+
}
|
|
23681
24234
|
function writeKeyFile(dataDir2, key) {
|
|
23682
24235
|
ensureDataDirSync(dataDir2);
|
|
23683
24236
|
const file2 = keyFilePath(dataDir2);
|
|
@@ -23702,7 +24255,10 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
23702
24255
|
tightenFile(keyFilePath(dataDir2));
|
|
23703
24256
|
return existing;
|
|
23704
24257
|
}
|
|
23705
|
-
return writeKeyFile(dataDir2, {
|
|
24258
|
+
return writeKeyFile(dataDir2, {
|
|
24259
|
+
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
24260
|
+
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
24261
|
+
});
|
|
23706
24262
|
}
|
|
23707
24263
|
function fingerprintValue(key, raw) {
|
|
23708
24264
|
return createHmac("sha256", key.material).update(raw, "utf8").digest("hex");
|
|
@@ -23766,16 +24322,42 @@ function readJson(file2) {
|
|
|
23766
24322
|
return parseJsonObject(text) ?? null;
|
|
23767
24323
|
}
|
|
23768
24324
|
|
|
23769
|
-
// ../../packages/persistence/src/
|
|
23770
|
-
import {
|
|
24325
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
24326
|
+
import {
|
|
24327
|
+
createCipheriv,
|
|
24328
|
+
createDecipheriv,
|
|
24329
|
+
createHmac as createHmac2,
|
|
24330
|
+
hkdfSync,
|
|
24331
|
+
timingSafeEqual
|
|
24332
|
+
} from "crypto";
|
|
24333
|
+
|
|
24334
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
24335
|
+
import { execFileSync } from "child_process";
|
|
24336
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
24337
|
+
import {
|
|
24338
|
+
chmodSync as chmodSync2,
|
|
24339
|
+
mkdirSync as mkdirSync2,
|
|
24340
|
+
readFileSync as readFileSync3,
|
|
24341
|
+
renameSync as renameSync4,
|
|
24342
|
+
rmSync as rmSync3,
|
|
24343
|
+
statSync,
|
|
24344
|
+
writeFileSync as writeFileSync2
|
|
24345
|
+
} from "fs";
|
|
23771
24346
|
import { join as join5 } from "path";
|
|
24347
|
+
|
|
24348
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
24349
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID10 } from "crypto";
|
|
24350
|
+
|
|
24351
|
+
// ../../packages/persistence/src/warn-era-cap.ts
|
|
24352
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
24353
|
+
import { join as join6 } from "path";
|
|
23772
24354
|
var MARKER = "warn-era-capped";
|
|
23773
24355
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
23774
24356
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
23775
|
-
const marker =
|
|
23776
|
-
if (
|
|
24357
|
+
const marker = join6(dataDir2, MARKER);
|
|
24358
|
+
if (existsSync3(marker)) return { capped: 0, skipped: "already-run" };
|
|
23777
24359
|
const capped = db.policies.capCategoryActions();
|
|
23778
|
-
|
|
24360
|
+
writeFileSync3(marker, `${new Date(Date.now()).toISOString()}
|
|
23779
24361
|
`, { mode: DATA_FILE_MODE });
|
|
23780
24362
|
return { capped };
|
|
23781
24363
|
}
|
|
@@ -23832,8 +24414,8 @@ function resolveProvider() {
|
|
|
23832
24414
|
function loadConfig(base = defaultDataDir()) {
|
|
23833
24415
|
try {
|
|
23834
24416
|
ensureLayoutDirSync(base);
|
|
23835
|
-
const settingsFile =
|
|
23836
|
-
if (
|
|
24417
|
+
const settingsFile = join7(settingsDir(base), "settings.json");
|
|
24418
|
+
if (existsSync4(settingsFile)) tightenFile(settingsFile);
|
|
23837
24419
|
} catch {
|
|
23838
24420
|
}
|
|
23839
24421
|
migrateLegacyLayout(base);
|
|
@@ -23856,9 +24438,9 @@ function resolveProviderSafe() {
|
|
|
23856
24438
|
}
|
|
23857
24439
|
|
|
23858
24440
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23859
|
-
import { readdirSync, readFileSync as
|
|
24441
|
+
import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
23860
24442
|
import { homedir as homedir2 } from "os";
|
|
23861
|
-
import { basename as basename2, join as
|
|
24443
|
+
import { basename as basename2, join as join9 } from "path";
|
|
23862
24444
|
|
|
23863
24445
|
// ../../packages/detections/src/egress/registry.ts
|
|
23864
24446
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -24961,9 +25543,9 @@ function extractGoMod(text) {
|
|
|
24961
25543
|
let blockKeyword = null;
|
|
24962
25544
|
eachLine(text, (rawLine, lineNumber) => {
|
|
24963
25545
|
if (blockKeyword === null) {
|
|
24964
|
-
const
|
|
24965
|
-
if (
|
|
24966
|
-
blockKeyword =
|
|
25546
|
+
const open2 = GO_BLOCK_OPEN.exec(rawLine)?.[1];
|
|
25547
|
+
if (open2 !== void 0) {
|
|
25548
|
+
blockKeyword = open2;
|
|
24967
25549
|
return;
|
|
24968
25550
|
}
|
|
24969
25551
|
const path = GO_REQUIRE_SINGLE_LINE.exec(rawLine)?.[1];
|
|
@@ -25481,12 +26063,12 @@ function redact(text, findings) {
|
|
|
25481
26063
|
const regions = [];
|
|
25482
26064
|
for (const f of sorted) {
|
|
25483
26065
|
const rank = SEVERITY_RANK2[f.severity];
|
|
25484
|
-
const
|
|
25485
|
-
if (
|
|
25486
|
-
|
|
25487
|
-
if (rank >
|
|
25488
|
-
|
|
25489
|
-
|
|
26066
|
+
const open2 = regions[regions.length - 1];
|
|
26067
|
+
if (open2 && f.span.start < open2.end) {
|
|
26068
|
+
open2.end = Math.max(open2.end, f.span.end);
|
|
26069
|
+
if (rank > open2.rank) {
|
|
26070
|
+
open2.rank = rank;
|
|
26071
|
+
open2.category = f.category;
|
|
25490
26072
|
}
|
|
25491
26073
|
} else {
|
|
25492
26074
|
regions.push({ start: f.span.start, end: f.span.end, category: f.category, rank });
|
|
@@ -25515,6 +26097,24 @@ function maskMatch(raw) {
|
|
|
25515
26097
|
return `${raw.charAt(0)}${"*".repeat(6)}${raw.charAt(raw.length - 1)}`;
|
|
25516
26098
|
}
|
|
25517
26099
|
|
|
26100
|
+
// ../../packages/detections/src/pointer-shield.ts
|
|
26101
|
+
function shieldPointers(text) {
|
|
26102
|
+
const spans = [];
|
|
26103
|
+
let out = null;
|
|
26104
|
+
for (const match of text.matchAll(pointerTokenScanner())) {
|
|
26105
|
+
spans.push({ start: match.index, end: match.index + match[0].length });
|
|
26106
|
+
out ??= text;
|
|
26107
|
+
out = out.slice(0, match.index) + " ".repeat(match[0].length) + out.slice(match.index + match[0].length);
|
|
26108
|
+
}
|
|
26109
|
+
return { text: out ?? text, spans };
|
|
26110
|
+
}
|
|
26111
|
+
function dropShieldedFindings(findings, spans) {
|
|
26112
|
+
if (spans.length === 0) return findings;
|
|
26113
|
+
return findings.filter(
|
|
26114
|
+
(finding) => !spans.some((s) => finding.span.start < s.end && finding.span.end > s.start)
|
|
26115
|
+
);
|
|
26116
|
+
}
|
|
26117
|
+
|
|
25518
26118
|
// ../../packages/detections/src/posture/config-posture.ts
|
|
25519
26119
|
var RULE_VERSION = "1";
|
|
25520
26120
|
var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -27711,8 +28311,8 @@ function bundledDetections() {
|
|
|
27711
28311
|
}
|
|
27712
28312
|
|
|
27713
28313
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
27714
|
-
import { existsSync as
|
|
27715
|
-
import { basename, dirname, isAbsolute, join as
|
|
28314
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
28315
|
+
import { basename, dirname, isAbsolute, join as join8, sep as sep2 } from "path";
|
|
27716
28316
|
function resolveRepoIdentity(cwd) {
|
|
27717
28317
|
try {
|
|
27718
28318
|
const root = findGitRoot(cwd);
|
|
@@ -27741,36 +28341,36 @@ function resolveWorktreeRoot(cwd) {
|
|
|
27741
28341
|
function findGitRoot(start) {
|
|
27742
28342
|
let dir = start;
|
|
27743
28343
|
for (; ; ) {
|
|
27744
|
-
if (
|
|
28344
|
+
if (existsSync5(join8(dir, ".git"))) return dir;
|
|
27745
28345
|
const parent = dirname(dir);
|
|
27746
28346
|
if (parent === dir) return void 0;
|
|
27747
28347
|
dir = parent;
|
|
27748
28348
|
}
|
|
27749
28349
|
}
|
|
27750
28350
|
function resolveGitContext(root) {
|
|
27751
|
-
const dotGit =
|
|
28351
|
+
const dotGit = join8(root, ".git");
|
|
27752
28352
|
try {
|
|
27753
|
-
if (
|
|
27754
|
-
return { configPath:
|
|
28353
|
+
if (statSync2(dotGit).isDirectory()) {
|
|
28354
|
+
return { configPath: join8(dotGit, "config"), headRoot: root };
|
|
27755
28355
|
}
|
|
27756
28356
|
} catch {
|
|
27757
28357
|
return void 0;
|
|
27758
28358
|
}
|
|
27759
28359
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
27760
28360
|
if (!target) return void 0;
|
|
27761
|
-
const gitdir = isAbsolute(target) ? target :
|
|
27762
|
-
if (
|
|
27763
|
-
return { configPath:
|
|
28361
|
+
const gitdir = isAbsolute(target) ? target : join8(root, target);
|
|
28362
|
+
if (existsSync5(join8(gitdir, "config"))) {
|
|
28363
|
+
return { configPath: join8(gitdir, "config"), headRoot: root };
|
|
27764
28364
|
}
|
|
27765
|
-
const commonRaw = safeRead(
|
|
28365
|
+
const commonRaw = safeRead(join8(gitdir, "commondir"))?.trim();
|
|
27766
28366
|
if (!commonRaw) return void 0;
|
|
27767
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
28367
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join8(gitdir, commonRaw);
|
|
27768
28368
|
const headRoot = basename(commonGitDir) === ".git" ? dirname(commonGitDir) : root;
|
|
27769
|
-
return { configPath:
|
|
28369
|
+
return { configPath: join8(commonGitDir, "config"), headRoot };
|
|
27770
28370
|
}
|
|
27771
28371
|
function safeRead(path) {
|
|
27772
28372
|
try {
|
|
27773
|
-
return
|
|
28373
|
+
return readFileSync4(path, "utf8");
|
|
27774
28374
|
} catch {
|
|
27775
28375
|
return void 0;
|
|
27776
28376
|
}
|
|
@@ -27808,13 +28408,13 @@ function slugFromUrl(url2) {
|
|
|
27808
28408
|
}
|
|
27809
28409
|
|
|
27810
28410
|
// ../../packages/plugin-sdk/src/events.ts
|
|
27811
|
-
import { createHash as createHash4, randomUUID as
|
|
28411
|
+
import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
|
|
27812
28412
|
function contentHashOf(text) {
|
|
27813
28413
|
return createHash4("sha256").update(text).digest("hex");
|
|
27814
28414
|
}
|
|
27815
28415
|
function buildIngestEvent(input) {
|
|
27816
28416
|
return {
|
|
27817
|
-
id:
|
|
28417
|
+
id: randomUUID11(),
|
|
27818
28418
|
sourceTool: input.sourceTool,
|
|
27819
28419
|
kind: input.kind,
|
|
27820
28420
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27825,7 +28425,7 @@ function buildIngestEvent(input) {
|
|
|
27825
28425
|
// SDK boot in the fail-open hook path). Preserve any id the caller already set.
|
|
27826
28426
|
metadata: {
|
|
27827
28427
|
...input.metadata,
|
|
27828
|
-
correlationId: input.metadata?.correlationId ??
|
|
28428
|
+
correlationId: input.metadata?.correlationId ?? randomUUID11()
|
|
27829
28429
|
}
|
|
27830
28430
|
};
|
|
27831
28431
|
}
|
|
@@ -27834,8 +28434,8 @@ function buildIngestEvent(input) {
|
|
|
27834
28434
|
import { arch, hostname as hostname3, platform, release } from "os";
|
|
27835
28435
|
|
|
27836
28436
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
27837
|
-
import { mkdirSync as
|
|
27838
|
-
import { join as
|
|
28437
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
28438
|
+
import { join as join10 } from "path";
|
|
27839
28439
|
|
|
27840
28440
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
27841
28441
|
import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
|
|
@@ -27873,8 +28473,8 @@ function resolveNonGitProject(startDir, recognizeMarker) {
|
|
|
27873
28473
|
|
|
27874
28474
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
27875
28475
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
27876
|
-
import { existsSync as
|
|
27877
|
-
import { basename as basename4, join as
|
|
28476
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
|
|
28477
|
+
import { basename as basename4, join as join11, relative, sep as sep4 } from "path";
|
|
27878
28478
|
|
|
27879
28479
|
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
27880
28480
|
var PASS_BUDGET_MS = 2e3;
|
|
@@ -27930,7 +28530,7 @@ async function filterUnsafeRules(rules, gateway, opts) {
|
|
|
27930
28530
|
}
|
|
27931
28531
|
|
|
27932
28532
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
27933
|
-
import { randomUUID as
|
|
28533
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
27934
28534
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
27935
28535
|
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
27936
28536
|
function entryIsActive(entry, now) {
|
|
@@ -28033,7 +28633,12 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28033
28633
|
if (worst === "block") return { action: "block", text: null, findings };
|
|
28034
28634
|
if (worst === "redact") {
|
|
28035
28635
|
const redactFindings = findings.filter((f) => actionFor(f) === "redact");
|
|
28036
|
-
return {
|
|
28636
|
+
return {
|
|
28637
|
+
action: "redact",
|
|
28638
|
+
text: redact(text, redactFindings),
|
|
28639
|
+
findings,
|
|
28640
|
+
enforcedFindings: redactFindings
|
|
28641
|
+
};
|
|
28037
28642
|
}
|
|
28038
28643
|
return { action: worst, text, findings };
|
|
28039
28644
|
}
|
|
@@ -28074,9 +28679,17 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28074
28679
|
else groups.set(pair, [finding]);
|
|
28075
28680
|
}
|
|
28076
28681
|
const now = Date.now();
|
|
28682
|
+
const preAuthorized = new Set(ctx.preAuthorizedGrantIds ?? []);
|
|
28077
28683
|
for (const [pair, group] of groups) {
|
|
28078
28684
|
const entry = entries.get(pair);
|
|
28079
|
-
if (!entry
|
|
28685
|
+
if (!entry) continue;
|
|
28686
|
+
if (preAuthorized.has(entry.id)) {
|
|
28687
|
+
if (!conditionsMatch(entry.conditions, ctx)) continue;
|
|
28688
|
+
for (const finding of group) excepted.add(finding);
|
|
28689
|
+
exceptionIds.push(entry.id);
|
|
28690
|
+
continue;
|
|
28691
|
+
}
|
|
28692
|
+
if (!entryIsActive(entry, now) || !conditionsMatch(entry.conditions, ctx)) {
|
|
28080
28693
|
continue;
|
|
28081
28694
|
}
|
|
28082
28695
|
let consumed = false;
|
|
@@ -28108,7 +28721,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28108
28721
|
const pair = `${finding.ruleId}:${fp}`;
|
|
28109
28722
|
if (seen.has(pair)) continue;
|
|
28110
28723
|
seen.add(pair);
|
|
28111
|
-
const reference =
|
|
28724
|
+
const reference = randomUUID12().replaceAll("-", "").slice(0, 6);
|
|
28112
28725
|
const maskedValue = maskMatch(finding.rawMatch);
|
|
28113
28726
|
try {
|
|
28114
28727
|
await gateway.recordBlockedDetection({
|
|
@@ -28132,7 +28745,8 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28132
28745
|
async function evaluate2(text, context, ctx) {
|
|
28133
28746
|
try {
|
|
28134
28747
|
await ensureInitialized();
|
|
28135
|
-
const
|
|
28748
|
+
const shielded = shieldPointers(text);
|
|
28749
|
+
const findings = dropShieldedFindings(scan(shielded.text, rules, context), shielded.spans);
|
|
28136
28750
|
const fpCache = /* @__PURE__ */ new Map();
|
|
28137
28751
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
28138
28752
|
const decision = decide(findings, text, excepted);
|
|
@@ -28155,7 +28769,11 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28155
28769
|
const { decision, excepted, exceptionIds } = await evaluate2(
|
|
28156
28770
|
input.text,
|
|
28157
28771
|
filePath ? { filePath } : void 0,
|
|
28158
|
-
{
|
|
28772
|
+
{
|
|
28773
|
+
sourceTool: input.sourceTool,
|
|
28774
|
+
metadata: input.metadata,
|
|
28775
|
+
preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
|
|
28776
|
+
}
|
|
28159
28777
|
);
|
|
28160
28778
|
if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
|
|
28161
28779
|
try {
|
|
@@ -28185,7 +28803,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28185
28803
|
valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
|
|
28186
28804
|
}) : void 0;
|
|
28187
28805
|
return {
|
|
28188
|
-
id:
|
|
28806
|
+
id: randomUUID12(),
|
|
28189
28807
|
eventId: event.id,
|
|
28190
28808
|
ruleId: match.ruleId,
|
|
28191
28809
|
category: match.category,
|
|
@@ -28211,7 +28829,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28211
28829
|
const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
28212
28830
|
return contentHashOf(JSON.stringify(sorted));
|
|
28213
28831
|
} catch {
|
|
28214
|
-
return `unresolved-${
|
|
28832
|
+
return `unresolved-${randomUUID12()}`;
|
|
28215
28833
|
}
|
|
28216
28834
|
}
|
|
28217
28835
|
async function close() {
|
|
@@ -28224,12 +28842,12 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28224
28842
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28225
28843
|
|
|
28226
28844
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28227
|
-
import { mkdirSync as
|
|
28228
|
-
import { join as
|
|
28845
|
+
import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
28846
|
+
import { join as join12 } from "path";
|
|
28229
28847
|
|
|
28230
28848
|
// ../../packages/scanner/src/discover.ts
|
|
28231
28849
|
import { readdirSync as readdirSync4 } from "fs";
|
|
28232
|
-
import { join as
|
|
28850
|
+
import { join as join13 } from "path";
|
|
28233
28851
|
|
|
28234
28852
|
// ../../packages/scanner/src/constants.ts
|
|
28235
28853
|
var COMMON_SKIP_DIRS = ["node_modules", "__pycache__", ".venv", "venv", ".cache"];
|
|
@@ -28274,7 +28892,7 @@ function discoverGitRepos(opts) {
|
|
|
28274
28892
|
if (!entry.isDirectory()) continue;
|
|
28275
28893
|
if (DISCOVER_SKIP.has(entry.name)) continue;
|
|
28276
28894
|
if (entry.name.startsWith(".")) continue;
|
|
28277
|
-
visit(
|
|
28895
|
+
visit(join13(dir, entry.name), depth + 1);
|
|
28278
28896
|
}
|
|
28279
28897
|
}
|
|
28280
28898
|
for (const root of searchRoots) {
|
|
@@ -28377,11 +28995,11 @@ function renderMultiRepoSummary(summary, opts = {}) {
|
|
|
28377
28995
|
}
|
|
28378
28996
|
|
|
28379
28997
|
// ../../packages/scanner/src/scan.ts
|
|
28380
|
-
import { existsSync as
|
|
28998
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
28381
28999
|
import { extname as extname2, isAbsolute as isAbsolute2, relative as relative4 } from "path";
|
|
28382
29000
|
|
|
28383
29001
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
28384
|
-
import { randomUUID as
|
|
29002
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
28385
29003
|
|
|
28386
29004
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
28387
29005
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -28543,7 +29161,7 @@ var StandaloneDataGateway = class {
|
|
|
28543
29161
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
28544
29162
|
const installed = this.installedScanRules();
|
|
28545
29163
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
28546
|
-
id:
|
|
29164
|
+
id: randomUUID13(),
|
|
28547
29165
|
scope: "global",
|
|
28548
29166
|
target: { ruleId },
|
|
28549
29167
|
action,
|
|
@@ -28696,16 +29314,16 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
28696
29314
|
}
|
|
28697
29315
|
|
|
28698
29316
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
28699
|
-
import { randomUUID as
|
|
29317
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28700
29318
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
28701
29319
|
|
|
28702
29320
|
// ../../packages/scanner/src/manifests.ts
|
|
28703
|
-
import { statSync as
|
|
29321
|
+
import { statSync as statSync6 } from "fs";
|
|
28704
29322
|
|
|
28705
29323
|
// ../../packages/scanner/src/walk.ts
|
|
28706
29324
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
28707
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
28708
|
-
import { extname, join as
|
|
29325
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
29326
|
+
import { extname, join as join14, relative as relative3, sep as sep5 } from "path";
|
|
28709
29327
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
28710
29328
|
".ts",
|
|
28711
29329
|
".tsx",
|
|
@@ -28737,7 +29355,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
28737
29355
|
var DEFAULT_MAX_BYTES = 512 * 1024;
|
|
28738
29356
|
function readIgnoreLayer(dir, filename) {
|
|
28739
29357
|
try {
|
|
28740
|
-
const content =
|
|
29358
|
+
const content = readFileSync8(join14(dir, filename), "utf8");
|
|
28741
29359
|
return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
|
|
28742
29360
|
} catch {
|
|
28743
29361
|
return void 0;
|
|
@@ -28769,7 +29387,7 @@ function* walkTree(rootDir, opts = {}) {
|
|
|
28769
29387
|
const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
|
|
28770
29388
|
for (const entry of dirents) {
|
|
28771
29389
|
const name = entry.name;
|
|
28772
|
-
const fullPath =
|
|
29390
|
+
const fullPath = join14(dir, name);
|
|
28773
29391
|
if (entry.isDirectory()) {
|
|
28774
29392
|
const skipState = evaluate(dirSkipLayers, fullPath, true);
|
|
28775
29393
|
if (skipState !== "unignored" && (SKIP_DIRS.has(name) || skipState === "ignored")) {
|
|
@@ -28803,7 +29421,7 @@ function* walkSourceFiles(opts = {}) {
|
|
|
28803
29421
|
let size;
|
|
28804
29422
|
let mtime;
|
|
28805
29423
|
try {
|
|
28806
|
-
const st =
|
|
29424
|
+
const st = statSync5(file2.path);
|
|
28807
29425
|
size = st.size;
|
|
28808
29426
|
mtime = st.mtime;
|
|
28809
29427
|
} catch {
|
|
@@ -28823,7 +29441,7 @@ function* walkSourceFiles(opts = {}) {
|
|
|
28823
29441
|
if (opts.shouldRead && !opts.shouldRead(meta3)) continue;
|
|
28824
29442
|
let content;
|
|
28825
29443
|
try {
|
|
28826
|
-
content =
|
|
29444
|
+
content = readFileSync8(file2.path, "utf8");
|
|
28827
29445
|
} catch {
|
|
28828
29446
|
continue;
|
|
28829
29447
|
}
|
|
@@ -28845,7 +29463,7 @@ function collectManifests(rootDir, maxFileSizeBytes = MAX_MANIFEST_BYTES) {
|
|
|
28845
29463
|
const kind = manifestKindOf(file2.name);
|
|
28846
29464
|
if (kind === null) continue;
|
|
28847
29465
|
try {
|
|
28848
|
-
const st =
|
|
29466
|
+
const st = statSync6(file2.path);
|
|
28849
29467
|
if (st.size > maxFileSizeBytes) continue;
|
|
28850
29468
|
found.push({ path: file2.path, kind, mtime: st.mtime.toISOString(), size: st.size });
|
|
28851
29469
|
} catch {
|
|
@@ -28943,7 +29561,7 @@ function isUnderRoot(path, rootDir) {
|
|
|
28943
29561
|
async function sweepDeletedFiles(gateway, rootDir, previous) {
|
|
28944
29562
|
const deleted = [];
|
|
28945
29563
|
for (const path of previous.keys()) {
|
|
28946
|
-
if (!isUnderRoot(path, rootDir) ||
|
|
29564
|
+
if (!isUnderRoot(path, rootDir) || existsSync7(path)) continue;
|
|
28947
29565
|
deleted.push(path);
|
|
28948
29566
|
await resolveRemovedFindings(gateway, path, [], { deleted: true });
|
|
28949
29567
|
}
|
|
@@ -29047,7 +29665,7 @@ function scanManifests(egress, ledger, updates, rootDir) {
|
|
|
29047
29665
|
if (prev?.mtime === manifest.mtime) continue;
|
|
29048
29666
|
let content;
|
|
29049
29667
|
try {
|
|
29050
|
-
content =
|
|
29668
|
+
content = readFileSync9(manifest.path, "utf8");
|
|
29051
29669
|
} catch {
|
|
29052
29670
|
continue;
|
|
29053
29671
|
}
|