@akasecurity/ai-tc-claude-code 0.9.3 → 0.9.5
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 +5 -1
- package/commands/setup.md +123 -35
- package/hooks/hooks.json +11 -0
- package/package.json +7 -6
- package/scripts/apply-suppressions.js +2178 -593
- package/scripts/backfill.js +3815 -429
- package/scripts/filescan.js +2379 -336
- package/scripts/firstrun.js +1748 -181
- package/scripts/intro.js +520 -48
- package/scripts/message-display.js +30054 -0
- package/scripts/onboard.js +2007 -208
- package/scripts/post-tool-use.js +3721 -388
- package/scripts/pre-tool-use.js +3900 -414
- package/scripts/query.js +1752 -181
- package/scripts/reconcile.js +3321 -372
- package/scripts/remediate.js +3925 -582
- package/scripts/scan-worker.js +18006 -0
- package/scripts/session-start.js +1879 -273
- package/scripts/start-light.js +540 -68
- package/scripts/statusline.js +1747 -180
- package/scripts/stop.js +504 -75
- package/scripts/user-prompt-submit.js +3695 -408
package/scripts/filescan.js
CHANGED
|
@@ -492,13 +492,12 @@ 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 existsSync5 } from "fs";
|
|
496
|
+
import { join as join8 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/database.ts
|
|
499
|
-
import { randomUUID as
|
|
500
|
-
import {
|
|
501
|
-
import { join, sep } from "path";
|
|
499
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
500
|
+
import { join as join2, sep } from "path";
|
|
502
501
|
import { DatabaseSync } from "node:sqlite";
|
|
503
502
|
|
|
504
503
|
// ../../packages/schema/src/drizzle/sqlite-ddl.ts
|
|
@@ -562,6 +561,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
562
561
|
{
|
|
563
562
|
tag: "0014_drop_legacy_events_findings",
|
|
564
563
|
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"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
tag: "0015_busy_vengeance",
|
|
567
|
+
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`);"
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
tag: "0016_breezy_zodiak",
|
|
571
|
+
sql: "ALTER TABLE `exceptions` ADD `capability` text DEFAULT 'suppress' NOT NULL;"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
tag: "0017_rainy_kat_farrell",
|
|
575
|
+
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`);"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
tag: "0018_serious_tana_nile",
|
|
579
|
+
sql: "ALTER TABLE `secret_vault` ADD `format_version` integer DEFAULT 2 NOT NULL;"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
tag: "0019_audit_started_at_index",
|
|
583
|
+
sql: "CREATE INDEX `idx_audit_started_at` ON `audit_events` (`started_at`);"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
tag: "0020_secret_vault_pagination_indexes",
|
|
587
|
+
sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
|
|
565
588
|
}
|
|
566
589
|
];
|
|
567
590
|
|
|
@@ -15299,7 +15322,17 @@ var Finding = external_exports.object({
|
|
|
15299
15322
|
}).meta({ id: "Finding" });
|
|
15300
15323
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15301
15324
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15302
|
-
var FindingProvider = external_exports.enum([
|
|
15325
|
+
var FindingProvider = external_exports.enum([
|
|
15326
|
+
"claudecode",
|
|
15327
|
+
"claudedesktop",
|
|
15328
|
+
"cursor",
|
|
15329
|
+
"copilot",
|
|
15330
|
+
"chatgpt",
|
|
15331
|
+
"claudeai",
|
|
15332
|
+
"codex",
|
|
15333
|
+
"antigravity",
|
|
15334
|
+
"api"
|
|
15335
|
+
]).meta({ id: "FindingProvider" });
|
|
15303
15336
|
var FindingCategory = external_exports.enum([
|
|
15304
15337
|
"secret",
|
|
15305
15338
|
"pii",
|
|
@@ -15353,7 +15386,16 @@ var FindingInstance = external_exports.object({
|
|
|
15353
15386
|
confidence: external_exports.number().min(0).max(1),
|
|
15354
15387
|
// Lifecycle status (see FindingStatus). Optional so legacy callers/rows
|
|
15355
15388
|
// that predate the resolution feature stay valid.
|
|
15356
|
-
status: FindingStatus.optional()
|
|
15389
|
+
status: FindingStatus.optional(),
|
|
15390
|
+
// The audit event this finding was captured from. Optional so callers that
|
|
15391
|
+
// do not project it stay valid. An at-rest finding is content-addressed by
|
|
15392
|
+
// finding_key and its row is upserted on re-detection, so this names the
|
|
15393
|
+
// MOST RECENT detection event, not the first.
|
|
15394
|
+
eventId: external_exports.string().optional(),
|
|
15395
|
+
// The session that event belongs to, when it has one — the seam a
|
|
15396
|
+
// per-instance "view session" link needs. Absent for events captured
|
|
15397
|
+
// outside a session.
|
|
15398
|
+
sessionId: external_exports.string().optional()
|
|
15357
15399
|
}).meta({ id: "FindingInstance" });
|
|
15358
15400
|
var FindingGroup = external_exports.object({
|
|
15359
15401
|
id: external_exports.string(),
|
|
@@ -15397,7 +15439,11 @@ var FindingFacets = external_exports.object({
|
|
|
15397
15439
|
// for every instance, so every group lands in a bucket; a status-less
|
|
15398
15440
|
// group (possible only for callers whose rows carry no statuses) is
|
|
15399
15441
|
// counted under no value.
|
|
15400
|
-
status: external_exports.array(FindingFacetItem)
|
|
15442
|
+
status: external_exports.array(FindingFacetItem),
|
|
15443
|
+
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
15444
|
+
// reads, which can filter by it; the grouped read omits the dimension
|
|
15445
|
+
// because a group spans tools.
|
|
15446
|
+
tool: external_exports.array(FindingFacetItem).optional()
|
|
15401
15447
|
}).meta({ id: "FindingFacets" });
|
|
15402
15448
|
var DEFAULT_GROUPED_FINDINGS_LIMIT = 50;
|
|
15403
15449
|
var ListGroupedFindingsQuery = external_exports.object({
|
|
@@ -15415,6 +15461,16 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
15415
15461
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
15416
15462
|
// session → findings drilldown). Findings without a session never match.
|
|
15417
15463
|
sessionId: external_exports.string().optional(),
|
|
15464
|
+
// Inclusive lower bound on the parent event's timestamp, so a caller arriving
|
|
15465
|
+
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
15466
|
+
// means all time — this list has no default window.
|
|
15467
|
+
from: external_exports.iso.datetime().optional(),
|
|
15468
|
+
// A group or instance id that must appear in the page even when the cursor
|
|
15469
|
+
// has already advanced past its sort position. This is what keeps the
|
|
15470
|
+
// Findings page's one-shot ?finding= deep link resolving once the list
|
|
15471
|
+
// paginates: the target group is appended out of sort order rather than
|
|
15472
|
+
// scanning forward for it. Never affects totals, facets or the cursor.
|
|
15473
|
+
includeId: external_exports.string().optional(),
|
|
15418
15474
|
groupBy: external_exports.literal("type").optional(),
|
|
15419
15475
|
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
15420
15476
|
cursor: external_exports.string().optional()
|
|
@@ -15459,15 +15515,110 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15459
15515
|
detection: FindingDetectionRef,
|
|
15460
15516
|
policy: FindingPolicyRef
|
|
15461
15517
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15518
|
+
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15519
|
+
var ListFindingInstancesQuery = external_exports.object({
|
|
15520
|
+
severity: external_exports.array(Severity).optional(),
|
|
15521
|
+
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
15522
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15523
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15524
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15525
|
+
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
15526
|
+
// the grouped query's group-level fold.
|
|
15527
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15528
|
+
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
15529
|
+
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
15530
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15531
|
+
// Exact repository / file-path matches, for the drill-down out of the
|
|
15532
|
+
// locations view. A row whose event carries no repo/file matches neither.
|
|
15533
|
+
repo: external_exports.string().optional(),
|
|
15534
|
+
file: external_exports.string().optional(),
|
|
15535
|
+
q: external_exports.string().optional(),
|
|
15536
|
+
sessionId: external_exports.string().optional(),
|
|
15537
|
+
from: external_exports.iso.datetime().optional(),
|
|
15538
|
+
limit: external_exports.coerce.number().int().min(1).max(200).optional(),
|
|
15539
|
+
cursor: external_exports.string().optional()
|
|
15540
|
+
});
|
|
15541
|
+
var ListFindingInstancesResponse = external_exports.object({
|
|
15542
|
+
// Instances matching the filters across the whole scope, not just this
|
|
15543
|
+
// page — cursor-independent, like the grouped list's totals.
|
|
15544
|
+
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
15545
|
+
// Counts in INSTANCES here, where the grouped response counts groups. Each
|
|
15546
|
+
// dimension still excludes its own filter.
|
|
15547
|
+
facets: FindingFacets,
|
|
15548
|
+
items: external_exports.array(FindingInstanceDetail),
|
|
15549
|
+
nextCursor: external_exports.string().nullable()
|
|
15550
|
+
}).meta({ id: "ListFindingInstancesResponse" });
|
|
15551
|
+
var FindingLocationFile = external_exports.object({
|
|
15552
|
+
// Empty when the instances carried no file path (a prompt or a tool call
|
|
15553
|
+
// with no file attribution).
|
|
15554
|
+
file: external_exports.string(),
|
|
15555
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15556
|
+
maxSeverity: Severity,
|
|
15557
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15558
|
+
// Folded from the instances' derived statuses with the same
|
|
15559
|
+
// open-dominates precedence a group uses.
|
|
15560
|
+
status: FindingStatus.optional(),
|
|
15561
|
+
// Distinct rules seen at this location, capped — the row shows them as
|
|
15562
|
+
// chips, and the count is what conveys scale.
|
|
15563
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
15564
|
+
}).meta({ id: "FindingLocationFile" });
|
|
15565
|
+
var FindingLocationRepo = external_exports.object({
|
|
15566
|
+
/** Empty when the instances carried no repo attribute. */
|
|
15567
|
+
repo: external_exports.string(),
|
|
15568
|
+
instanceCount: external_exports.number().int().nonnegative(),
|
|
15569
|
+
maxSeverity: Severity,
|
|
15570
|
+
latestDetectedAt: external_exports.iso.datetime(),
|
|
15571
|
+
status: FindingStatus.optional(),
|
|
15572
|
+
files: external_exports.array(FindingLocationFile)
|
|
15573
|
+
}).meta({ id: "FindingLocationRepo" });
|
|
15574
|
+
var ListFindingLocationsQuery = external_exports.object({
|
|
15575
|
+
severity: external_exports.array(Severity).optional(),
|
|
15576
|
+
subtype: external_exports.array(external_exports.string()).optional(),
|
|
15577
|
+
provider: external_exports.array(FindingProvider).optional(),
|
|
15578
|
+
action: external_exports.array(FindingAction).optional(),
|
|
15579
|
+
// Per-instance, as in ListFindingInstancesQuery: a location keeps the
|
|
15580
|
+
// instances that match, and folds its status from those.
|
|
15581
|
+
status: external_exports.array(FindingStatus).optional(),
|
|
15582
|
+
tool: external_exports.array(external_exports.string()).optional(),
|
|
15583
|
+
q: external_exports.string().optional(),
|
|
15584
|
+
sessionId: external_exports.string().optional(),
|
|
15585
|
+
from: external_exports.iso.datetime().optional(),
|
|
15586
|
+
limit: external_exports.coerce.number().int().min(1).max(500).optional()
|
|
15587
|
+
});
|
|
15588
|
+
var ListFindingLocationsResponse = external_exports.object({
|
|
15589
|
+
totals: external_exports.object({
|
|
15590
|
+
findings: external_exports.number().int().nonnegative(),
|
|
15591
|
+
repos: external_exports.number().int().nonnegative(),
|
|
15592
|
+
files: external_exports.number().int().nonnegative()
|
|
15593
|
+
}),
|
|
15594
|
+
/** Sorted by max severity, then most recent. */
|
|
15595
|
+
items: external_exports.array(FindingLocationRepo),
|
|
15596
|
+
/** Whether `limit` truncated the repo list. */
|
|
15597
|
+
hasMore: external_exports.boolean()
|
|
15598
|
+
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15462
15599
|
|
|
15463
15600
|
// ../../packages/schema/src/zod/harness-map.ts
|
|
15464
|
-
var Harness = external_exports.enum([
|
|
15601
|
+
var Harness = external_exports.enum([
|
|
15602
|
+
"claudecode",
|
|
15603
|
+
"cursor",
|
|
15604
|
+
"copilot",
|
|
15605
|
+
"codex",
|
|
15606
|
+
"antigravity",
|
|
15607
|
+
"windsurf",
|
|
15608
|
+
"claudedesktop",
|
|
15609
|
+
"chatgpt",
|
|
15610
|
+
"claudeai",
|
|
15611
|
+
"api"
|
|
15612
|
+
]).meta({ id: "Harness" });
|
|
15465
15613
|
var TOOL_TO_HARNESS = {
|
|
15466
15614
|
"claude-code": "claudecode",
|
|
15467
15615
|
"claude-desktop": "claudedesktop",
|
|
15468
15616
|
"github-copilot": "copilot",
|
|
15469
15617
|
cursor: "cursor",
|
|
15470
|
-
chatgpt: "chatgpt"
|
|
15618
|
+
chatgpt: "chatgpt",
|
|
15619
|
+
codex: "codex",
|
|
15620
|
+
antigravity: "antigravity",
|
|
15621
|
+
"claude-ai": "claudeai"
|
|
15471
15622
|
};
|
|
15472
15623
|
|
|
15473
15624
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -15925,7 +16076,18 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
15925
16076
|
// ../../packages/schema/src/zod/event.ts
|
|
15926
16077
|
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
15927
16078
|
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
15928
|
-
var SourceTool = external_exports.enum([
|
|
16079
|
+
var SourceTool = external_exports.enum([
|
|
16080
|
+
"claude-code",
|
|
16081
|
+
"claude-desktop",
|
|
16082
|
+
"cursor",
|
|
16083
|
+
"chatgpt",
|
|
16084
|
+
"claude-ai",
|
|
16085
|
+
"github-copilot",
|
|
16086
|
+
"codex",
|
|
16087
|
+
"antigravity",
|
|
16088
|
+
"cli",
|
|
16089
|
+
"unknown"
|
|
16090
|
+
]).meta({ id: "SourceTool" });
|
|
15929
16091
|
var EventMetadata = external_exports.object({
|
|
15930
16092
|
sessionId: external_exports.string().optional(),
|
|
15931
16093
|
repo: external_exports.string().optional(),
|
|
@@ -15996,7 +16158,7 @@ var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).me
|
|
|
15996
16158
|
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
15997
16159
|
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
15998
16160
|
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
15999
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex"]).meta({ id: "HarnessId" });
|
|
16161
|
+
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16000
16162
|
var AccessCounts = external_exports.object({
|
|
16001
16163
|
open: external_exports.number().int().nonnegative(),
|
|
16002
16164
|
approved: external_exports.number().int().nonnegative(),
|
|
@@ -16218,6 +16380,7 @@ var ExceptionConditions = external_exports.object({
|
|
|
16218
16380
|
sourceTool: external_exports.string().optional(),
|
|
16219
16381
|
provider: external_exports.string().optional()
|
|
16220
16382
|
}).strict();
|
|
16383
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16221
16384
|
var DetectionException = external_exports.object({
|
|
16222
16385
|
id: external_exports.guid(),
|
|
16223
16386
|
ruleId: external_exports.string(),
|
|
@@ -16234,6 +16397,7 @@ var DetectionException = external_exports.object({
|
|
|
16234
16397
|
keyVersion: external_exports.number().int().positive(),
|
|
16235
16398
|
// maskMatch() preview of the approved value — never the raw value.
|
|
16236
16399
|
maskedValue: external_exports.string(),
|
|
16400
|
+
capability: ExceptionCapability.default("suppress"),
|
|
16237
16401
|
scope: ExceptionScope,
|
|
16238
16402
|
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16239
16403
|
maxUses: external_exports.number().int().positive().nullable(),
|
|
@@ -16257,11 +16421,13 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16257
16421
|
ruleId: true,
|
|
16258
16422
|
valueFingerprint: true,
|
|
16259
16423
|
keyVersion: true,
|
|
16424
|
+
capability: true,
|
|
16260
16425
|
expiresAt: true,
|
|
16261
16426
|
maxUses: true,
|
|
16262
16427
|
useCount: true,
|
|
16263
16428
|
conditions: true
|
|
16264
16429
|
});
|
|
16430
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16265
16431
|
|
|
16266
16432
|
// ../../packages/schema/src/zod/rule.ts
|
|
16267
16433
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
@@ -17072,6 +17238,35 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17072
17238
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17073
17239
|
}).meta({ id: "EgressWriteSummary" });
|
|
17074
17240
|
|
|
17241
|
+
// ../../packages/schema/src/zod/exception-action.ts
|
|
17242
|
+
var confirmation = external_exports.string().optional();
|
|
17243
|
+
var ApproveBlockedInput = external_exports.object({
|
|
17244
|
+
reference: external_exports.string(),
|
|
17245
|
+
scope: external_exports.string(),
|
|
17246
|
+
reason: external_exports.string(),
|
|
17247
|
+
confirmation
|
|
17248
|
+
});
|
|
17249
|
+
var AddExceptionInput = external_exports.object({
|
|
17250
|
+
ruleId: external_exports.string(),
|
|
17251
|
+
value: external_exports.string(),
|
|
17252
|
+
scope: external_exports.string(),
|
|
17253
|
+
reason: external_exports.string(),
|
|
17254
|
+
confirmation
|
|
17255
|
+
});
|
|
17256
|
+
var GrantRevealInput = external_exports.object({
|
|
17257
|
+
pointer: external_exports.string(),
|
|
17258
|
+
scope: external_exports.string(),
|
|
17259
|
+
justification: external_exports.string(),
|
|
17260
|
+
confirmation
|
|
17261
|
+
});
|
|
17262
|
+
var RevokeExceptionInput = external_exports.object({
|
|
17263
|
+
id: external_exports.string(),
|
|
17264
|
+
reason: external_exports.string()
|
|
17265
|
+
});
|
|
17266
|
+
var RotateKeyInput = external_exports.object({
|
|
17267
|
+
confirmation: external_exports.string()
|
|
17268
|
+
});
|
|
17269
|
+
|
|
17075
17270
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
17076
17271
|
function toApiAction(dbVal) {
|
|
17077
17272
|
const map2 = {
|
|
@@ -17127,6 +17322,8 @@ function buildFindingGroups(rows, opts = {}) {
|
|
|
17127
17322
|
repo: r.repo,
|
|
17128
17323
|
file: r.file,
|
|
17129
17324
|
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
17325
|
+
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
17326
|
+
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
17130
17327
|
action: toApiAction(effectiveDbAction),
|
|
17131
17328
|
detectedAt: r.occurredAt,
|
|
17132
17329
|
confidence: r.confidence,
|
|
@@ -17258,14 +17455,17 @@ function applyFindingFilters(groups, opts) {
|
|
|
17258
17455
|
}
|
|
17259
17456
|
var SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17260
17457
|
var SEVERITY_RANK = SEVERITY_ORDER;
|
|
17458
|
+
function compareFindingGroupOrder(a, b) {
|
|
17459
|
+
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17460
|
+
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17461
|
+
const severityDiff = rankA - rankB;
|
|
17462
|
+
if (severityDiff !== 0) return severityDiff;
|
|
17463
|
+
const recencyDiff = b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17464
|
+
if (recencyDiff !== 0) return recencyDiff;
|
|
17465
|
+
return a.id.localeCompare(b.id);
|
|
17466
|
+
}
|
|
17261
17467
|
function sortFindingGroups(groups) {
|
|
17262
|
-
return [...groups].sort(
|
|
17263
|
-
const rankA = SEVERITY_RANK[a.severity] ?? -1;
|
|
17264
|
-
const rankB = SEVERITY_RANK[b.severity] ?? -1;
|
|
17265
|
-
const severityDiff = rankA - rankB;
|
|
17266
|
-
if (severityDiff !== 0) return severityDiff;
|
|
17267
|
-
return b.latestDetectedAt.localeCompare(a.latestDetectedAt);
|
|
17268
|
-
});
|
|
17468
|
+
return [...groups].sort(compareFindingGroupOrder);
|
|
17269
17469
|
}
|
|
17270
17470
|
function computeFindingFacets(allGroups, opts) {
|
|
17271
17471
|
const forSeverity = applyFindingFilters(allGroups, {
|
|
@@ -17321,15 +17521,158 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
17321
17521
|
for (const g of forStatus) {
|
|
17322
17522
|
if (g.status !== void 0) statusMap.set(g.status, (statusMap.get(g.status) ?? 0) + 1);
|
|
17323
17523
|
}
|
|
17324
|
-
const
|
|
17524
|
+
const toItems2 = (m) => [...m.entries()].map(([value, count]) => ({ value, count }));
|
|
17525
|
+
return {
|
|
17526
|
+
severity: toItems2(severityMap),
|
|
17527
|
+
provider: toItems2(providerMap),
|
|
17528
|
+
action: toItems2(actionMap),
|
|
17529
|
+
subtype: toItems2(subtypeMap),
|
|
17530
|
+
status: toItems2(statusMap)
|
|
17531
|
+
};
|
|
17532
|
+
}
|
|
17533
|
+
|
|
17534
|
+
// ../../packages/schema/src/zod/findings-flat-build.ts
|
|
17535
|
+
function rowHaystack(row) {
|
|
17536
|
+
return [
|
|
17537
|
+
row.ruleId,
|
|
17538
|
+
row.category,
|
|
17539
|
+
row.maskedMatch,
|
|
17540
|
+
row.repo,
|
|
17541
|
+
row.file,
|
|
17542
|
+
row.toolName ? `via ${row.toolName}` : "",
|
|
17543
|
+
row.id
|
|
17544
|
+
].join(" ").toLowerCase();
|
|
17545
|
+
}
|
|
17546
|
+
function matchesDimension(row, opts, dimension) {
|
|
17547
|
+
switch (dimension) {
|
|
17548
|
+
case "severity":
|
|
17549
|
+
return !opts.severity?.length || opts.severity.includes(row.severity);
|
|
17550
|
+
case "subtype":
|
|
17551
|
+
return !opts.subtype?.length || opts.subtype.includes(row.ruleId);
|
|
17552
|
+
case "providers":
|
|
17553
|
+
return !opts.providers?.length || opts.providers.includes(toApiProvider(row.sourceTool));
|
|
17554
|
+
case "actions":
|
|
17555
|
+
return !opts.actions?.length || opts.actions.includes(toApiAction(row.actionTaken));
|
|
17556
|
+
case "statuses":
|
|
17557
|
+
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
17558
|
+
case "tools":
|
|
17559
|
+
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
17560
|
+
case "repo":
|
|
17561
|
+
return opts.repo === void 0 || opts.repo === "" || row.repo === opts.repo;
|
|
17562
|
+
case "file":
|
|
17563
|
+
return opts.file === void 0 || opts.file === "" || row.file === opts.file;
|
|
17564
|
+
case "q":
|
|
17565
|
+
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
17566
|
+
}
|
|
17567
|
+
}
|
|
17568
|
+
var DIMENSIONS = [
|
|
17569
|
+
"severity",
|
|
17570
|
+
"subtype",
|
|
17571
|
+
"providers",
|
|
17572
|
+
"actions",
|
|
17573
|
+
"statuses",
|
|
17574
|
+
"tools",
|
|
17575
|
+
"repo",
|
|
17576
|
+
"file",
|
|
17577
|
+
"q"
|
|
17578
|
+
];
|
|
17579
|
+
function matchesInstanceFilters(row, opts, except) {
|
|
17580
|
+
for (const dimension of DIMENSIONS) {
|
|
17581
|
+
if (dimension === except) continue;
|
|
17582
|
+
if (!matchesDimension(row, opts, dimension)) return false;
|
|
17583
|
+
}
|
|
17584
|
+
return true;
|
|
17585
|
+
}
|
|
17586
|
+
function toItems(counts) {
|
|
17587
|
+
return [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
17588
|
+
}
|
|
17589
|
+
function bump(counts, value) {
|
|
17590
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
17591
|
+
}
|
|
17592
|
+
function createInstanceFacetAccumulator(opts) {
|
|
17593
|
+
const severity = /* @__PURE__ */ new Map();
|
|
17594
|
+
const subtype = /* @__PURE__ */ new Map();
|
|
17595
|
+
const provider = /* @__PURE__ */ new Map();
|
|
17596
|
+
const action = /* @__PURE__ */ new Map();
|
|
17597
|
+
const status = /* @__PURE__ */ new Map();
|
|
17598
|
+
const tool = /* @__PURE__ */ new Map();
|
|
17599
|
+
return {
|
|
17600
|
+
add(row) {
|
|
17601
|
+
if (matchesInstanceFilters(row, opts, "severity")) bump(severity, row.severity);
|
|
17602
|
+
if (matchesInstanceFilters(row, opts, "subtype")) bump(subtype, row.ruleId);
|
|
17603
|
+
if (matchesInstanceFilters(row, opts, "providers")) {
|
|
17604
|
+
bump(provider, toApiProvider(row.sourceTool));
|
|
17605
|
+
}
|
|
17606
|
+
if (matchesInstanceFilters(row, opts, "actions")) bump(action, toApiAction(row.actionTaken));
|
|
17607
|
+
if (row.status !== void 0 && matchesInstanceFilters(row, opts, "statuses")) {
|
|
17608
|
+
bump(status, row.status);
|
|
17609
|
+
}
|
|
17610
|
+
if (row.toolName !== void 0 && matchesInstanceFilters(row, opts, "tools")) {
|
|
17611
|
+
bump(tool, row.toolName);
|
|
17612
|
+
}
|
|
17613
|
+
},
|
|
17614
|
+
facets: () => ({
|
|
17615
|
+
severity: toItems(severity),
|
|
17616
|
+
subtype: toItems(subtype),
|
|
17617
|
+
provider: toItems(provider),
|
|
17618
|
+
action: toItems(action),
|
|
17619
|
+
status: toItems(status),
|
|
17620
|
+
tool: toItems(tool)
|
|
17621
|
+
})
|
|
17622
|
+
};
|
|
17623
|
+
}
|
|
17624
|
+
function toInstanceDetail(row) {
|
|
17625
|
+
const category = toApiCategory(row.category);
|
|
17325
17626
|
return {
|
|
17326
|
-
|
|
17327
|
-
provider:
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17627
|
+
id: row.id,
|
|
17628
|
+
provider: toApiProvider(row.sourceTool),
|
|
17629
|
+
repo: row.repo,
|
|
17630
|
+
file: row.file,
|
|
17631
|
+
...row.toolName === void 0 ? {} : { toolName: row.toolName },
|
|
17632
|
+
eventId: row.eventId,
|
|
17633
|
+
...row.sessionId === void 0 ? {} : { sessionId: row.sessionId },
|
|
17634
|
+
action: toApiAction(row.actionTaken),
|
|
17635
|
+
detectedAt: row.occurredAt,
|
|
17636
|
+
confidence: row.confidence,
|
|
17637
|
+
...row.status === void 0 ? {} : { status: row.status },
|
|
17638
|
+
groupId: row.ruleId,
|
|
17639
|
+
category,
|
|
17640
|
+
subtype: row.ruleId,
|
|
17641
|
+
severity: row.severity,
|
|
17642
|
+
match: { maskedValue: row.maskedMatch, contextPrefix: "" },
|
|
17643
|
+
detection: { id: row.ruleId, name: null },
|
|
17644
|
+
policy: { id: `category:${category}`, name: category }
|
|
17645
|
+
};
|
|
17646
|
+
}
|
|
17647
|
+
var SEVERITY_ORDER2 = {
|
|
17648
|
+
critical: 0,
|
|
17649
|
+
high: 1,
|
|
17650
|
+
medium: 2,
|
|
17651
|
+
low: 3
|
|
17652
|
+
};
|
|
17653
|
+
function newLocationAccumulator() {
|
|
17654
|
+
return {
|
|
17655
|
+
instanceCount: 0,
|
|
17656
|
+
// Sorts after every known severity, so the first row always wins the
|
|
17657
|
+
// comparison below rather than an unknown value pinning the location.
|
|
17658
|
+
maxSeverityRank: Number.MAX_SAFE_INTEGER,
|
|
17659
|
+
maxSeverity: "low",
|
|
17660
|
+
latestDetectedAt: "",
|
|
17661
|
+
statuses: [],
|
|
17662
|
+
ruleIds: /* @__PURE__ */ new Set()
|
|
17331
17663
|
};
|
|
17332
17664
|
}
|
|
17665
|
+
function addToLocation(acc, row) {
|
|
17666
|
+
acc.instanceCount += 1;
|
|
17667
|
+
const rank = SEVERITY_ORDER2[row.severity] ?? Number.MAX_SAFE_INTEGER - 1;
|
|
17668
|
+
if (rank < acc.maxSeverityRank) {
|
|
17669
|
+
acc.maxSeverityRank = rank;
|
|
17670
|
+
acc.maxSeverity = row.severity;
|
|
17671
|
+
}
|
|
17672
|
+
if (row.occurredAt > acc.latestDetectedAt) acc.latestDetectedAt = row.occurredAt;
|
|
17673
|
+
acc.statuses.push(row.status);
|
|
17674
|
+
acc.ruleIds.add(row.ruleId);
|
|
17675
|
+
}
|
|
17333
17676
|
|
|
17334
17677
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
17335
17678
|
var InstalledPack = external_exports.object({
|
|
@@ -17361,8 +17704,164 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17361
17704
|
message: "At least one field must be provided"
|
|
17362
17705
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17363
17706
|
|
|
17707
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
17708
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
17709
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
17710
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
17711
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
17712
|
+
);
|
|
17713
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
17714
|
+
function pointerTokenScanner() {
|
|
17715
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
17716
|
+
}
|
|
17717
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
17718
|
+
var ParsedPointer = external_exports.object({
|
|
17719
|
+
category: DetectionCategory,
|
|
17720
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17721
|
+
pointerId: external_exports.string(),
|
|
17722
|
+
tag: external_exports.string()
|
|
17723
|
+
});
|
|
17724
|
+
var VaultEntry = external_exports.object({
|
|
17725
|
+
pointerId: external_exports.string(),
|
|
17726
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
17727
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
17728
|
+
// independently of the vault encryption key below.
|
|
17729
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17730
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
17731
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
17732
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17733
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
17734
|
+
// different rule's category keeps the category it was minted with, so one
|
|
17735
|
+
// value always produces exactly one wire token.
|
|
17736
|
+
category: DetectionCategory,
|
|
17737
|
+
ruleId: external_exports.string(),
|
|
17738
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
17739
|
+
maskedMatch: external_exports.string(),
|
|
17740
|
+
provider: external_exports.string().optional(),
|
|
17741
|
+
ciphertext: external_exports.string(),
|
|
17742
|
+
nonce: external_exports.string(),
|
|
17743
|
+
authTag: external_exports.string(),
|
|
17744
|
+
// How many times this value has been detected on this machine — the reuse
|
|
17745
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
17746
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
17747
|
+
firstSeen: external_exports.string(),
|
|
17748
|
+
lastSeen: external_exports.string()
|
|
17749
|
+
});
|
|
17750
|
+
var PointerDescriptor = external_exports.object({
|
|
17751
|
+
category: DetectionCategory,
|
|
17752
|
+
provider: external_exports.string().optional(),
|
|
17753
|
+
maskedMatch: external_exports.string(),
|
|
17754
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17755
|
+
firstSeen: external_exports.string(),
|
|
17756
|
+
lastSeen: external_exports.string()
|
|
17757
|
+
});
|
|
17758
|
+
var PointerIdentity = external_exports.object({
|
|
17759
|
+
ruleId: external_exports.string(),
|
|
17760
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17761
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
17762
|
+
});
|
|
17763
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
17764
|
+
var VaultDerefReason = external_exports.enum([
|
|
17765
|
+
"display",
|
|
17766
|
+
"explicit-reveal",
|
|
17767
|
+
"view-render",
|
|
17768
|
+
"model-input",
|
|
17769
|
+
"remediation",
|
|
17770
|
+
"purge"
|
|
17771
|
+
]);
|
|
17772
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
17773
|
+
var VaultDeref = external_exports.object({
|
|
17774
|
+
id: external_exports.guid(),
|
|
17775
|
+
pointerId: external_exports.string(),
|
|
17776
|
+
at: external_exports.string(),
|
|
17777
|
+
target: DetokenizeTarget,
|
|
17778
|
+
reason: VaultDerefReason,
|
|
17779
|
+
outcome: VaultDerefOutcome,
|
|
17780
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
17781
|
+
grantId: external_exports.string().optional(),
|
|
17782
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
17783
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
17784
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
17785
|
+
});
|
|
17786
|
+
var VaultSightingKind = external_exports.enum([
|
|
17787
|
+
"prompt",
|
|
17788
|
+
"tool-input",
|
|
17789
|
+
"tool-output",
|
|
17790
|
+
"file",
|
|
17791
|
+
"transcript"
|
|
17792
|
+
]);
|
|
17793
|
+
var VaultSighting = external_exports.object({
|
|
17794
|
+
location: external_exports.string(),
|
|
17795
|
+
kind: VaultSightingKind,
|
|
17796
|
+
firstSeen: external_exports.string(),
|
|
17797
|
+
lastSeen: external_exports.string()
|
|
17798
|
+
});
|
|
17799
|
+
var VaultInventoryEntry = external_exports.object({
|
|
17800
|
+
pointerId: external_exports.string(),
|
|
17801
|
+
category: DetectionCategory,
|
|
17802
|
+
provider: external_exports.string().optional(),
|
|
17803
|
+
maskedMatch: external_exports.string(),
|
|
17804
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
17805
|
+
firstSeen: external_exports.string(),
|
|
17806
|
+
lastSeen: external_exports.string(),
|
|
17807
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
17808
|
+
// the inventory badges it, the row links to revocation.
|
|
17809
|
+
revealGrantId: external_exports.string().nullable(),
|
|
17810
|
+
sightings: external_exports.array(VaultSighting)
|
|
17811
|
+
});
|
|
17812
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
17813
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
17814
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
17815
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
17816
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17817
|
+
// Opaque; names the last row of the page just served.
|
|
17818
|
+
cursor: external_exports.string().optional()
|
|
17819
|
+
});
|
|
17820
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
17821
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
17822
|
+
// independent, so paging never changes what the count claims.
|
|
17823
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
17824
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17825
|
+
// `null` once the last page is reached.
|
|
17826
|
+
nextCursor: external_exports.string().nullable()
|
|
17827
|
+
});
|
|
17828
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
17829
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17830
|
+
cursor: external_exports.string().optional()
|
|
17831
|
+
});
|
|
17832
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
17833
|
+
// Reused values across the whole store — the number the section's claim
|
|
17834
|
+
// ("values detected in more than one place") is about.
|
|
17835
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
17836
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
17837
|
+
nextCursor: external_exports.string().nullable()
|
|
17838
|
+
});
|
|
17839
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
17840
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
17841
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
17842
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
17843
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
17844
|
+
includeBatched: external_exports.boolean().optional(),
|
|
17845
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
17846
|
+
cursor: external_exports.string().optional()
|
|
17847
|
+
});
|
|
17848
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
17849
|
+
items: external_exports.array(VaultDeref),
|
|
17850
|
+
nextCursor: external_exports.string().nullable(),
|
|
17851
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
17852
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
17853
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
17854
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
17855
|
+
});
|
|
17856
|
+
var VaultKeyCustody = external_exports.string();
|
|
17857
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
17858
|
+
var VaultConsent = external_exports.object({
|
|
17859
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
17860
|
+
version: external_exports.number().int().positive()
|
|
17861
|
+
});
|
|
17862
|
+
|
|
17364
17863
|
// ../../packages/schema/src/zod/local.ts
|
|
17365
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17864
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17366
17865
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17367
17866
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17368
17867
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
@@ -17384,6 +17883,16 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17384
17883
|
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17385
17884
|
// Shares writes.
|
|
17386
17885
|
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17886
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
17887
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
17888
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
17889
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
17890
|
+
// purging the vault is the eraser.
|
|
17891
|
+
vaultConsent: VaultConsent.optional(),
|
|
17892
|
+
// Where the vault master key lives.
|
|
17893
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
17894
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
17895
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
17387
17896
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17388
17897
|
onboardedAt: external_exports.iso.datetime().optional(),
|
|
17389
17898
|
// Records that the user consented to sending findings to the model API for
|
|
@@ -17716,7 +18225,7 @@ var TopSourcesQuery = external_exports.object({
|
|
|
17716
18225
|
// Omit for both kinds.
|
|
17717
18226
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
17718
18227
|
});
|
|
17719
|
-
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
18228
|
+
var Provider = external_exports.enum(["claudecode", "cursor", "codex", "antigravity", "claudeai", "chatgpt", "copilot", "api"]).meta({ id: "Provider" });
|
|
17720
18229
|
var ScanCoverageProvider = external_exports.object({
|
|
17721
18230
|
provider: Provider,
|
|
17722
18231
|
// Percent of that provider's traffic scanned in the window. 0 when unsupported.
|
|
@@ -17969,6 +18478,195 @@ function captureId(sessionId, contentHash, filePath = null) {
|
|
|
17969
18478
|
);
|
|
17970
18479
|
}
|
|
17971
18480
|
|
|
18481
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18482
|
+
import { randomUUID } from "crypto";
|
|
18483
|
+
import { existsSync, readdirSync, renameSync as renameSync2, rmSync as rmSync2, statSync } from "fs";
|
|
18484
|
+
import { basename, dirname, join } from "path";
|
|
18485
|
+
|
|
18486
|
+
// ../../packages/persistence/src/paths.ts
|
|
18487
|
+
import {
|
|
18488
|
+
chmodSync,
|
|
18489
|
+
linkSync,
|
|
18490
|
+
lstatSync,
|
|
18491
|
+
mkdirSync,
|
|
18492
|
+
renameSync,
|
|
18493
|
+
rmSync,
|
|
18494
|
+
writeFileSync
|
|
18495
|
+
} from "fs";
|
|
18496
|
+
import { threadId } from "worker_threads";
|
|
18497
|
+
var DATA_DIR_MODE = 448;
|
|
18498
|
+
var DATA_FILE_MODE = 384;
|
|
18499
|
+
var DB_FILENAME = "aka.db";
|
|
18500
|
+
function isSymlink(path) {
|
|
18501
|
+
try {
|
|
18502
|
+
return lstatSync(path).isSymbolicLink();
|
|
18503
|
+
} catch {
|
|
18504
|
+
return false;
|
|
18505
|
+
}
|
|
18506
|
+
}
|
|
18507
|
+
function chmodBestEffort(path, mode) {
|
|
18508
|
+
if (isSymlink(path)) return;
|
|
18509
|
+
try {
|
|
18510
|
+
chmodSync(path, mode);
|
|
18511
|
+
} catch {
|
|
18512
|
+
}
|
|
18513
|
+
}
|
|
18514
|
+
function tightenDir(dir) {
|
|
18515
|
+
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18516
|
+
}
|
|
18517
|
+
function ensureDataDirSync(dir) {
|
|
18518
|
+
mkdirSync(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
18519
|
+
tightenDir(dir);
|
|
18520
|
+
}
|
|
18521
|
+
function dbSidecars(file2) {
|
|
18522
|
+
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18523
|
+
}
|
|
18524
|
+
function tightenFile(file2) {
|
|
18525
|
+
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18526
|
+
}
|
|
18527
|
+
function tightenPerms(file2) {
|
|
18528
|
+
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18529
|
+
}
|
|
18530
|
+
function classifyOccupant(file2) {
|
|
18531
|
+
try {
|
|
18532
|
+
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
18533
|
+
return { kind: "gone" };
|
|
18534
|
+
} catch (err) {
|
|
18535
|
+
if (err.code === "ENOENT") return { kind: "gone" };
|
|
18536
|
+
return { kind: "unknown", cause: err };
|
|
18537
|
+
}
|
|
18538
|
+
}
|
|
18539
|
+
var KeyUnclaimableError = class extends Error {
|
|
18540
|
+
code = "key-unclaimable";
|
|
18541
|
+
// `cause` is installed only when there IS one. Passing { cause: undefined }
|
|
18542
|
+
// defines the property anyway, so an error carrying nothing would still answer
|
|
18543
|
+
// `'cause' in err` — a present-but-empty field reads as a diagnosis that was
|
|
18544
|
+
// captured and then lost, which is worse than its plain absence.
|
|
18545
|
+
constructor(message, cause) {
|
|
18546
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
18547
|
+
this.name = "KeyUnclaimableError";
|
|
18548
|
+
}
|
|
18549
|
+
};
|
|
18550
|
+
function createOwnerOnlyFileSync(file2, data) {
|
|
18551
|
+
const tmp = `${file2}.${String(process.pid)}.${String(threadId)}.new`;
|
|
18552
|
+
try {
|
|
18553
|
+
rmSync(tmp, { force: true });
|
|
18554
|
+
} catch {
|
|
18555
|
+
}
|
|
18556
|
+
let created;
|
|
18557
|
+
try {
|
|
18558
|
+
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18559
|
+
created = publishByLink(tmp, file2, data);
|
|
18560
|
+
} finally {
|
|
18561
|
+
try {
|
|
18562
|
+
rmSync(tmp, { force: true });
|
|
18563
|
+
} catch {
|
|
18564
|
+
}
|
|
18565
|
+
}
|
|
18566
|
+
if (created) tightenFile(file2);
|
|
18567
|
+
return created;
|
|
18568
|
+
}
|
|
18569
|
+
var LINK_UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EINVAL"]);
|
|
18570
|
+
function publishByLink(tmp, file2, data) {
|
|
18571
|
+
try {
|
|
18572
|
+
linkSync(tmp, file2);
|
|
18573
|
+
return true;
|
|
18574
|
+
} catch (err) {
|
|
18575
|
+
const code = err.code;
|
|
18576
|
+
if (code === "EEXIST") return false;
|
|
18577
|
+
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18578
|
+
}
|
|
18579
|
+
try {
|
|
18580
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18581
|
+
return true;
|
|
18582
|
+
} catch (err) {
|
|
18583
|
+
if (err.code === "EEXIST") return false;
|
|
18584
|
+
throw err;
|
|
18585
|
+
}
|
|
18586
|
+
}
|
|
18587
|
+
|
|
18588
|
+
// ../../packages/persistence/src/internal/snapshot.ts
|
|
18589
|
+
function backupPath(file2, tag) {
|
|
18590
|
+
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18591
|
+
}
|
|
18592
|
+
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18593
|
+
function reapStalePartials(file2) {
|
|
18594
|
+
const dir = dirname(file2);
|
|
18595
|
+
const prefix = `${basename(file2)}.`;
|
|
18596
|
+
let entries;
|
|
18597
|
+
try {
|
|
18598
|
+
entries = readdirSync(dir);
|
|
18599
|
+
} catch {
|
|
18600
|
+
return;
|
|
18601
|
+
}
|
|
18602
|
+
for (const name of entries) {
|
|
18603
|
+
if (!name.startsWith(prefix) || !name.endsWith(".bak.partial")) continue;
|
|
18604
|
+
const partial2 = join(dir, name);
|
|
18605
|
+
try {
|
|
18606
|
+
if (Date.now() - statSync(partial2).mtimeMs > STALE_PARTIAL_MS) {
|
|
18607
|
+
rmSync2(partial2, { force: true });
|
|
18608
|
+
}
|
|
18609
|
+
} catch {
|
|
18610
|
+
}
|
|
18611
|
+
}
|
|
18612
|
+
}
|
|
18613
|
+
function snapshotStore(db, backup) {
|
|
18614
|
+
const partial2 = `${backup}.partial`;
|
|
18615
|
+
try {
|
|
18616
|
+
rmSync2(partial2, { force: true });
|
|
18617
|
+
db.prepare("VACUUM INTO ?").run(partial2);
|
|
18618
|
+
tightenFile(partial2);
|
|
18619
|
+
renameSync2(partial2, backup);
|
|
18620
|
+
} catch (error51) {
|
|
18621
|
+
try {
|
|
18622
|
+
rmSync2(partial2, { force: true });
|
|
18623
|
+
} catch {
|
|
18624
|
+
}
|
|
18625
|
+
throw error51;
|
|
18626
|
+
}
|
|
18627
|
+
}
|
|
18628
|
+
function moveStoreAside(file2, backup) {
|
|
18629
|
+
const undo = [];
|
|
18630
|
+
renameSync2(file2, backup);
|
|
18631
|
+
undo.push([backup, file2]);
|
|
18632
|
+
try {
|
|
18633
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18634
|
+
const moved = `${backup}${sidecar.slice(file2.length)}`;
|
|
18635
|
+
try {
|
|
18636
|
+
renameSync2(sidecar, moved);
|
|
18637
|
+
undo.push([moved, sidecar]);
|
|
18638
|
+
} catch {
|
|
18639
|
+
rmSync2(sidecar, { force: true });
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
} catch (error51) {
|
|
18643
|
+
for (const [from, to] of undo.reverse()) {
|
|
18644
|
+
try {
|
|
18645
|
+
renameSync2(from, to);
|
|
18646
|
+
} catch {
|
|
18647
|
+
}
|
|
18648
|
+
}
|
|
18649
|
+
throw error51;
|
|
18650
|
+
}
|
|
18651
|
+
tightenPerms(backup);
|
|
18652
|
+
}
|
|
18653
|
+
function discardStore(file2, backup) {
|
|
18654
|
+
try {
|
|
18655
|
+
rmSync2(file2, { force: true });
|
|
18656
|
+
for (const sidecar of dbSidecars(file2)) {
|
|
18657
|
+
rmSync2(sidecar, { force: true });
|
|
18658
|
+
}
|
|
18659
|
+
} catch (error51) {
|
|
18660
|
+
if (existsSync(file2)) {
|
|
18661
|
+
try {
|
|
18662
|
+
rmSync2(backup, { force: true });
|
|
18663
|
+
} catch {
|
|
18664
|
+
}
|
|
18665
|
+
}
|
|
18666
|
+
throw error51;
|
|
18667
|
+
}
|
|
18668
|
+
}
|
|
18669
|
+
|
|
17972
18670
|
// ../../packages/persistence/src/internal/sql-text.ts
|
|
17973
18671
|
function escapeLikePattern(s) {
|
|
17974
18672
|
return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -18110,61 +18808,12 @@ function mapRowsTolerant(rows, map2) {
|
|
|
18110
18808
|
return out;
|
|
18111
18809
|
}
|
|
18112
18810
|
|
|
18113
|
-
// ../../packages/persistence/src/
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
var DATA_FILE_MODE = 384;
|
|
18117
|
-
var DB_FILENAME = "aka.db";
|
|
18118
|
-
function chmodBestEffort(path, mode) {
|
|
18119
|
-
try {
|
|
18120
|
-
chmodSync(path, mode);
|
|
18121
|
-
} catch {
|
|
18122
|
-
}
|
|
18123
|
-
}
|
|
18124
|
-
function tightenDir(dir) {
|
|
18125
|
-
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18811
|
+
// ../../packages/persistence/src/migrations.ts
|
|
18812
|
+
function describeObject(object2) {
|
|
18813
|
+
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
18126
18814
|
}
|
|
18127
|
-
function
|
|
18128
|
-
|
|
18129
|
-
tightenDir(dir);
|
|
18130
|
-
}
|
|
18131
|
-
function dbSidecars(file2) {
|
|
18132
|
-
return [`${file2}-wal`, `${file2}-shm`, `${file2}-journal`];
|
|
18133
|
-
}
|
|
18134
|
-
function tightenFile(file2) {
|
|
18135
|
-
try {
|
|
18136
|
-
if (lstatSync(file2).isSymbolicLink()) return;
|
|
18137
|
-
} catch {
|
|
18138
|
-
}
|
|
18139
|
-
chmodBestEffort(file2, DATA_FILE_MODE);
|
|
18140
|
-
}
|
|
18141
|
-
function tightenPerms(file2) {
|
|
18142
|
-
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18143
|
-
}
|
|
18144
|
-
function writeOwnerOnlyFileSync(file2, data) {
|
|
18145
|
-
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18146
|
-
try {
|
|
18147
|
-
rmSync(tmp, { force: true });
|
|
18148
|
-
} catch {
|
|
18149
|
-
}
|
|
18150
|
-
try {
|
|
18151
|
-
writeFileSync(tmp, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18152
|
-
renameSync(tmp, file2);
|
|
18153
|
-
} finally {
|
|
18154
|
-
try {
|
|
18155
|
-
rmSync(tmp, { force: true });
|
|
18156
|
-
} catch {
|
|
18157
|
-
}
|
|
18158
|
-
}
|
|
18159
|
-
tightenFile(file2);
|
|
18160
|
-
}
|
|
18161
|
-
|
|
18162
|
-
// ../../packages/persistence/src/migrations.ts
|
|
18163
|
-
function describeObject(object2) {
|
|
18164
|
-
return object2.kind === "column" ? `column ${object2.table}.${object2.name}` : `table ${object2.name}`;
|
|
18165
|
-
}
|
|
18166
|
-
function splitStatements(sql) {
|
|
18167
|
-
return sql.split(/-->\s*statement-breakpoint/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
18815
|
+
function splitStatements(sql) {
|
|
18816
|
+
return sql.split(/-->\s*statement-breakpoint/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
18168
18817
|
}
|
|
18169
18818
|
function createdIndexName(statement) {
|
|
18170
18819
|
const body = statement.replace(/^(?:\s*--[^\n]*\n?)+/, "").trimStart();
|
|
@@ -18274,9 +18923,9 @@ function applyLegacyDropMigration(db, file2) {
|
|
|
18274
18923
|
}
|
|
18275
18924
|
}
|
|
18276
18925
|
function backupBeforeLegacyDrop(db, file2) {
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18926
|
+
reapStalePartials(file2);
|
|
18927
|
+
const backup = backupPath(file2, "pre-drop");
|
|
18928
|
+
snapshotStore(db, backup);
|
|
18280
18929
|
return backup;
|
|
18281
18930
|
}
|
|
18282
18931
|
var TOKEN_USAGE_COLUMNS = [
|
|
@@ -18620,6 +19269,25 @@ function parseJsonObject(s) {
|
|
|
18620
19269
|
return void 0;
|
|
18621
19270
|
}
|
|
18622
19271
|
|
|
19272
|
+
// ../../packages/persistence/src/internal/keyset-cursor.ts
|
|
19273
|
+
function encodeKeysetCursor(payload) {
|
|
19274
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
19275
|
+
}
|
|
19276
|
+
function decodeKeysetCursor(cursor) {
|
|
19277
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19278
|
+
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && // `Number.isInteger`, not `typeof === 'number'`. Every timestamp this
|
|
19279
|
+
// resumes from is epoch millis, and a payload carrying ±Infinity or a
|
|
19280
|
+
// fraction binds cleanly rather than failing — returning an EMPTY page with
|
|
19281
|
+
// a null cursor, which a caller reads as "end of list". That is the one
|
|
19282
|
+
// outcome a cursor that does not decode must never produce, since the
|
|
19283
|
+
// documented behaviour above is to restart from the top. (`1e999` is valid
|
|
19284
|
+
// JSON and parses to Infinity; a bare `NaN` is not, so it cannot arrive.)
|
|
19285
|
+
Number.isInteger(parsed.startedAtMs) && typeof parsed.id === "string") {
|
|
19286
|
+
return parsed;
|
|
19287
|
+
}
|
|
19288
|
+
return null;
|
|
19289
|
+
}
|
|
19290
|
+
|
|
18623
19291
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
18624
19292
|
var DAY_MS = 864e5;
|
|
18625
19293
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -18665,16 +19333,6 @@ function utcWindow(nowMs) {
|
|
|
18665
19333
|
const startMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
|
|
18666
19334
|
return { startMs, endMs: startMs + DAY_MS };
|
|
18667
19335
|
}
|
|
18668
|
-
function encodeCursor(payload) {
|
|
18669
|
-
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
18670
|
-
}
|
|
18671
|
-
function decodeCursor(cursor) {
|
|
18672
|
-
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
18673
|
-
if (parsed !== void 0 && "startedAtMs" in parsed && "id" in parsed && typeof parsed.startedAtMs === "number" && typeof parsed.id === "string") {
|
|
18674
|
-
return parsed;
|
|
18675
|
-
}
|
|
18676
|
-
return null;
|
|
18677
|
-
}
|
|
18678
19336
|
var DB_EVENT_TYPE_TO_KIND = {
|
|
18679
19337
|
session: "session",
|
|
18680
19338
|
prompt: "prompt",
|
|
@@ -18819,7 +19477,7 @@ var SqliteActivityRepository = class {
|
|
|
18819
19477
|
return Promise.resolve({ sessionsToday, liveNow, toolCallsToday, findingsToday, egressToday });
|
|
18820
19478
|
}
|
|
18821
19479
|
listSessions(query) {
|
|
18822
|
-
const cursor = query.cursor ?
|
|
19480
|
+
const cursor = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
18823
19481
|
const toMs = query.to ? isoToEpochMillis(query.to) : this.now();
|
|
18824
19482
|
const fromMs = query.from ? isoToEpochMillis(query.from) : void 0;
|
|
18825
19483
|
const conditions = [SESSION_ROOT];
|
|
@@ -18893,7 +19551,7 @@ var SqliteActivityRepository = class {
|
|
|
18893
19551
|
)
|
|
18894
19552
|
);
|
|
18895
19553
|
const last = page[page.length - 1];
|
|
18896
|
-
const nextCursor = hasMore && last ?
|
|
19554
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: last.started_at, id: last.id }) : null;
|
|
18897
19555
|
return Promise.resolve({ items, nextCursor, emptyCount });
|
|
18898
19556
|
}
|
|
18899
19557
|
getSession(sessionId) {
|
|
@@ -19766,7 +20424,7 @@ var SqliteEventsRepository = class {
|
|
|
19766
20424
|
};
|
|
19767
20425
|
|
|
19768
20426
|
// ../../packages/persistence/src/repositories/exceptions.ts
|
|
19769
|
-
import { randomUUID } from "crypto";
|
|
20427
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19770
20428
|
|
|
19771
20429
|
// ../../packages/persistence/src/internal/sqlite-errors.ts
|
|
19772
20430
|
var SQLITE_CONSTRAINT_UNIQUE = 2067;
|
|
@@ -19798,9 +20456,13 @@ var AmbiguousExceptionIdError = class extends Error {
|
|
|
19798
20456
|
var ACTIVE_PREDICATE = `revoked_at IS NULL
|
|
19799
20457
|
AND (expires_at IS NULL OR expires_at > :now)
|
|
19800
20458
|
AND (max_uses IS NULL OR use_count < max_uses)`;
|
|
20459
|
+
var ACTIVE_REVEAL_GRANT_PREDICATE = `capability = 'reveal_to_model'
|
|
20460
|
+
AND conditions IS NULL
|
|
20461
|
+
AND ${ACTIVE_PREDICATE}`;
|
|
19801
20462
|
var SqliteExceptionsRepository = class {
|
|
19802
|
-
constructor(db) {
|
|
20463
|
+
constructor(db, now = () => Date.now()) {
|
|
19803
20464
|
this.db = db;
|
|
20465
|
+
this.now = now;
|
|
19804
20466
|
this.consumeStmt = db.prepare(
|
|
19805
20467
|
`UPDATE exceptions
|
|
19806
20468
|
SET use_count = use_count + 1, last_used_at = :now, updated_at = :now
|
|
@@ -19818,6 +20480,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19818
20480
|
);
|
|
19819
20481
|
}
|
|
19820
20482
|
db;
|
|
20483
|
+
now;
|
|
19821
20484
|
consumeStmt;
|
|
19822
20485
|
insertBlockedStmt;
|
|
19823
20486
|
sweepBlockedStmt;
|
|
@@ -19844,8 +20507,8 @@ var SqliteExceptionsRepository = class {
|
|
|
19844
20507
|
"provider conditions are not supported yet \u2014 a grant with one would never apply"
|
|
19845
20508
|
);
|
|
19846
20509
|
}
|
|
19847
|
-
const id =
|
|
19848
|
-
const now =
|
|
20510
|
+
const id = randomUUID2();
|
|
20511
|
+
const now = this.now();
|
|
19849
20512
|
try {
|
|
19850
20513
|
this.insertExceptionRow(id, input, now);
|
|
19851
20514
|
} catch (err) {
|
|
@@ -19889,11 +20552,11 @@ var SqliteExceptionsRepository = class {
|
|
|
19889
20552
|
this.db.prepare(
|
|
19890
20553
|
`INSERT INTO exceptions (
|
|
19891
20554
|
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
|
|
20555
|
+
capability, scope, expires_at, max_uses, use_count, last_used_at,
|
|
20556
|
+
justification, conditions, created_by, created_via, created_at, updated_at
|
|
19894
20557
|
) VALUES (
|
|
19895
20558
|
:id, :ruleId, :category, :valueFingerprint, :keyVersion, :maskedValue,
|
|
19896
|
-
:scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
20559
|
+
:capability, :scope, :expiresAt, :maxUses, 0, NULL, :justification,
|
|
19897
20560
|
:conditions, :createdBy, :createdVia, :now, :now
|
|
19898
20561
|
)`
|
|
19899
20562
|
).run({
|
|
@@ -19903,6 +20566,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19903
20566
|
valueFingerprint: input.valueFingerprint,
|
|
19904
20567
|
keyVersion: input.keyVersion,
|
|
19905
20568
|
maskedValue: input.maskedValue,
|
|
20569
|
+
capability: input.capability ?? "suppress",
|
|
19906
20570
|
scope: input.scope,
|
|
19907
20571
|
expiresAt: input.expiresAt === null ? null : isoToEpochMillis(input.expiresAt),
|
|
19908
20572
|
maxUses: input.maxUses,
|
|
@@ -19922,7 +20586,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19922
20586
|
const where = opts?.includeTerminal ? "" : `WHERE ${ACTIVE_PREDICATE}`;
|
|
19923
20587
|
const rows = allRows(
|
|
19924
20588
|
this.db.prepare(`SELECT * FROM exceptions ${where} ORDER BY created_at DESC, rowid DESC`),
|
|
19925
|
-
opts?.includeTerminal ? {} : { now:
|
|
20589
|
+
opts?.includeTerminal ? {} : { now: this.now() }
|
|
19926
20590
|
);
|
|
19927
20591
|
const exceptions = mapRowsTolerant(rows, parseExceptionRow);
|
|
19928
20592
|
return Promise.resolve(exceptions);
|
|
@@ -19957,7 +20621,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19957
20621
|
* already revoked.
|
|
19958
20622
|
*/
|
|
19959
20623
|
revoke(id, revokedBy, reason) {
|
|
19960
|
-
const now =
|
|
20624
|
+
const now = this.now();
|
|
19961
20625
|
const result = this.db.prepare(
|
|
19962
20626
|
`UPDATE exceptions
|
|
19963
20627
|
SET revoked_at = :now, revoked_by = :revokedBy, revoke_reason = :reason, updated_at = :now
|
|
@@ -19971,7 +20635,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19971
20635
|
* callers must treat identically — means it does not and the detection is
|
|
19972
20636
|
* enforced as usual. Deliberately NOT wrapped in try/catch.
|
|
19973
20637
|
*/
|
|
19974
|
-
consume(id, now =
|
|
20638
|
+
consume(id, now = this.now()) {
|
|
19975
20639
|
const result = this.consumeStmt.run({ id, now });
|
|
19976
20640
|
return Promise.resolve(Number(result.changes) === 1);
|
|
19977
20641
|
}
|
|
@@ -19980,7 +20644,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19980
20644
|
* version — what rides the policy bundle to the hook. Grants written under
|
|
19981
20645
|
* a different (rotated-away) key never match, so they are excluded at read.
|
|
19982
20646
|
*/
|
|
19983
|
-
activeBundleEntries(keyVersion, now =
|
|
20647
|
+
activeBundleEntries(keyVersion, now = this.now()) {
|
|
19984
20648
|
const rows = allRows(
|
|
19985
20649
|
this.db.prepare(
|
|
19986
20650
|
`SELECT * FROM exceptions
|
|
@@ -19996,6 +20660,7 @@ var SqliteExceptionsRepository = class {
|
|
|
19996
20660
|
ruleId: row.rule_id,
|
|
19997
20661
|
valueFingerprint: row.value_fingerprint,
|
|
19998
20662
|
keyVersion: row.key_version,
|
|
20663
|
+
capability: row.capability,
|
|
19999
20664
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20000
20665
|
maxUses: row.max_uses,
|
|
20001
20666
|
useCount: row.use_count,
|
|
@@ -20011,7 +20676,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20011
20676
|
* than the retention window on every write, so the ledger self-limits.
|
|
20012
20677
|
*/
|
|
20013
20678
|
recordBlocked(entry) {
|
|
20014
|
-
const now =
|
|
20679
|
+
const now = this.now();
|
|
20015
20680
|
this.sweepBlockedStmt.run({ cutoff: now - BLOCKED_DETECTIONS_RETENTION_MS });
|
|
20016
20681
|
this.insertBlockedStmt.run({
|
|
20017
20682
|
reference: entry.reference,
|
|
@@ -20034,7 +20699,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20034
20699
|
WHERE blocked_at > :cutoff
|
|
20035
20700
|
ORDER BY blocked_at DESC, rowid DESC`
|
|
20036
20701
|
),
|
|
20037
|
-
{ cutoff:
|
|
20702
|
+
{ cutoff: this.now() - windowMs }
|
|
20038
20703
|
);
|
|
20039
20704
|
return Promise.resolve(
|
|
20040
20705
|
rows.map((row) => ({
|
|
@@ -20050,6 +20715,36 @@ var SqliteExceptionsRepository = class {
|
|
|
20050
20715
|
}))
|
|
20051
20716
|
);
|
|
20052
20717
|
}
|
|
20718
|
+
/**
|
|
20719
|
+
* The active reveal-to-model grant for a vaulted value's identity, or null.
|
|
20720
|
+
* Matching is exact on (ruleId, valueFingerprint, keyVersion) — the same key
|
|
20721
|
+
* suppression uses — plus the capability: a suppression grant must never
|
|
20722
|
+
* authorize a reveal. Read-only: the caller does NOT consume here, because a
|
|
20723
|
+
* revealed value re-enters the detection scan immediately afterward and the
|
|
20724
|
+
* suppression match there claims the use — one crossing, one use.
|
|
20725
|
+
*
|
|
20726
|
+
* A grant with `conditions` NEVER matches here: the reveal path does not yet
|
|
20727
|
+
* evaluate conditions, and a narrowing clause that is ignored would WIDEN the
|
|
20728
|
+
* grant instead. Fail closed until reveal-side condition evaluation exists.
|
|
20729
|
+
*/
|
|
20730
|
+
activeRevealGrant(ruleId, valueFingerprint, keyVersion, now) {
|
|
20731
|
+
try {
|
|
20732
|
+
const at = now ?? this.now();
|
|
20733
|
+
const row = getRow(
|
|
20734
|
+
this.db.prepare(
|
|
20735
|
+
`SELECT id FROM exceptions
|
|
20736
|
+
WHERE rule_id = :ruleId AND value_fingerprint = :valueFingerprint
|
|
20737
|
+
AND key_version = :keyVersion
|
|
20738
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
20739
|
+
LIMIT 1`
|
|
20740
|
+
),
|
|
20741
|
+
{ ruleId, valueFingerprint, keyVersion, now: at }
|
|
20742
|
+
);
|
|
20743
|
+
return Promise.resolve(row ?? null);
|
|
20744
|
+
} catch (err) {
|
|
20745
|
+
return Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
20746
|
+
}
|
|
20747
|
+
}
|
|
20053
20748
|
/**
|
|
20054
20749
|
* Retention sweep: delete TERMINAL rows (revoked, expired, or use-budget
|
|
20055
20750
|
* exhausted) whose last transition is older than the retention window.
|
|
@@ -20057,7 +20752,7 @@ var SqliteExceptionsRepository = class {
|
|
|
20057
20752
|
* predicate, so correctness never depends on this sweep; it only bounds how
|
|
20058
20753
|
* long the audit evidence is kept locally. Returns the deleted count.
|
|
20059
20754
|
*/
|
|
20060
|
-
sweepTerminal(retentionMs, now =
|
|
20755
|
+
sweepTerminal(retentionMs, now = this.now()) {
|
|
20061
20756
|
const result = this.db.prepare(
|
|
20062
20757
|
`DELETE FROM exceptions
|
|
20063
20758
|
WHERE updated_at < :cutoff
|
|
@@ -20077,6 +20772,7 @@ function parseExceptionRow(row) {
|
|
|
20077
20772
|
valueFingerprint: row.value_fingerprint,
|
|
20078
20773
|
keyVersion: row.key_version,
|
|
20079
20774
|
maskedValue: row.masked_value,
|
|
20775
|
+
capability: row.capability,
|
|
20080
20776
|
scope: row.scope,
|
|
20081
20777
|
expiresAt: row.expires_at === null ? null : epochMillisToIso(row.expires_at),
|
|
20082
20778
|
maxUses: row.max_uses,
|
|
@@ -20119,6 +20815,23 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
20119
20815
|
|
|
20120
20816
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
20121
20817
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
20818
|
+
var SCAN_BATCH_ROWS = 1e3;
|
|
20819
|
+
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
20820
|
+
var LOCATION_RULE_IDS_CAP = 20;
|
|
20821
|
+
function compareLocationOrder(a, b) {
|
|
20822
|
+
return compareFindingGroupOrder(
|
|
20823
|
+
{
|
|
20824
|
+
severity: a.maxSeverity,
|
|
20825
|
+
latestDetectedAt: a.latestDetectedAt,
|
|
20826
|
+
id: ""
|
|
20827
|
+
},
|
|
20828
|
+
{
|
|
20829
|
+
severity: b.maxSeverity,
|
|
20830
|
+
latestDetectedAt: b.latestDetectedAt,
|
|
20831
|
+
id: ""
|
|
20832
|
+
}
|
|
20833
|
+
);
|
|
20834
|
+
}
|
|
20122
20835
|
var CONCAT_SEP = ",";
|
|
20123
20836
|
var TUPLE_SEP = "|";
|
|
20124
20837
|
function splitConcat(value) {
|
|
@@ -20131,6 +20844,33 @@ function deriveInstanceStatus(row) {
|
|
|
20131
20844
|
latestResolutionStatus: row.latest_status
|
|
20132
20845
|
});
|
|
20133
20846
|
}
|
|
20847
|
+
function encodeGroupCursor(group) {
|
|
20848
|
+
const payload = {
|
|
20849
|
+
sev: group.severity,
|
|
20850
|
+
t: group.latestDetectedAt,
|
|
20851
|
+
id: group.id
|
|
20852
|
+
};
|
|
20853
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
20854
|
+
}
|
|
20855
|
+
function decodeGroupCursor(cursor) {
|
|
20856
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
20857
|
+
if (parsed !== void 0 && typeof parsed.sev === "string" && typeof parsed.t === "string" && typeof parsed.id === "string") {
|
|
20858
|
+
return {
|
|
20859
|
+
severity: parsed.sev,
|
|
20860
|
+
latestDetectedAt: parsed.t,
|
|
20861
|
+
id: parsed.id
|
|
20862
|
+
};
|
|
20863
|
+
}
|
|
20864
|
+
return null;
|
|
20865
|
+
}
|
|
20866
|
+
function firstAfter(sorted, cursor) {
|
|
20867
|
+
const index = sorted.findIndex((g) => compareFindingGroupOrder(g, cursor) > 0);
|
|
20868
|
+
return index === -1 ? sorted.length : index;
|
|
20869
|
+
}
|
|
20870
|
+
function findDeepLinked(sorted, page, id) {
|
|
20871
|
+
if (page.some((g) => g.id === id || g.instances.some((i) => i.id === id))) return void 0;
|
|
20872
|
+
return sorted.find((g) => g.id === id || g.instances.some((i) => i.id === id));
|
|
20873
|
+
}
|
|
20134
20874
|
var DAY_MS3 = 864e5;
|
|
20135
20875
|
var SqliteFindingsRepository = class {
|
|
20136
20876
|
constructor(db) {
|
|
@@ -20240,8 +20980,13 @@ var SqliteFindingsRepository = class {
|
|
|
20240
20980
|
*/
|
|
20241
20981
|
listGroupedFindings(query) {
|
|
20242
20982
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
20243
|
-
const
|
|
20244
|
-
const
|
|
20983
|
+
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
20984
|
+
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
20985
|
+
const predicate = `WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})${sessionPredicate}${fromPredicate}`;
|
|
20986
|
+
const sessionParams = {
|
|
20987
|
+
...query.sessionId ? { sessionId: query.sessionId } : {},
|
|
20988
|
+
...fromMs === void 0 ? {} : { fromMs }
|
|
20989
|
+
};
|
|
20245
20990
|
const aggregates = this.groupAggregates(query.q !== void 0 && query.q !== "", {
|
|
20246
20991
|
predicate,
|
|
20247
20992
|
params: sessionParams
|
|
@@ -20249,7 +20994,8 @@ var SqliteFindingsRepository = class {
|
|
|
20249
20994
|
const rows = allRows(
|
|
20250
20995
|
this.db.prepare(
|
|
20251
20996
|
`SELECT id, rule_id, category, severity, masked_match, action_taken, confidence,
|
|
20252
|
-
occurred_at, source_tool, repo, file, tool_name,
|
|
20997
|
+
occurred_at, source_tool, repo, file, tool_name, event_id, session_id,
|
|
20998
|
+
kind, finding_key, latest_status
|
|
20253
20999
|
FROM (
|
|
20254
21000
|
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
20255
21001
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
@@ -20259,6 +21005,7 @@ var SqliteFindingsRepository = class {
|
|
|
20259
21005
|
json_extract(e.attributes, '$.repo') AS repo,
|
|
20260
21006
|
json_extract(e.attributes, '$.file_path') AS file,
|
|
20261
21007
|
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21008
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
20262
21009
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
20263
21010
|
latest.status AS latest_status,
|
|
20264
21011
|
ROW_NUMBER() OVER (
|
|
@@ -20290,6 +21037,8 @@ var SqliteFindingsRepository = class {
|
|
|
20290
21037
|
repo: r.repo ?? "",
|
|
20291
21038
|
file: r.file ?? "",
|
|
20292
21039
|
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21040
|
+
eventId: r.event_id,
|
|
21041
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
20293
21042
|
status: deriveInstanceStatus(r)
|
|
20294
21043
|
}));
|
|
20295
21044
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
@@ -20313,18 +21062,23 @@ var SqliteFindingsRepository = class {
|
|
|
20313
21062
|
groups: sorted.length
|
|
20314
21063
|
};
|
|
20315
21064
|
const limit = query.limit ?? DEFAULT_GROUPED_FINDINGS_LIMIT;
|
|
21065
|
+
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
21066
|
+
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
21067
|
+
const page = sorted.slice(start, start + limit);
|
|
21068
|
+
const lastOnPage = page.at(-1);
|
|
21069
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
21070
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
20316
21071
|
const statusSet = statusFilter.length > 0 ? new Set(statusFilter) : null;
|
|
20317
|
-
const
|
|
20318
|
-
|
|
20319
|
-
|
|
20320
|
-
|
|
20321
|
-
|
|
20322
|
-
);
|
|
21072
|
+
const narrow = (g) => statusSet ? {
|
|
21073
|
+
...g,
|
|
21074
|
+
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
21075
|
+
} : g;
|
|
21076
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
20323
21077
|
return Promise.resolve({
|
|
20324
21078
|
totals,
|
|
20325
21079
|
facets,
|
|
20326
21080
|
items,
|
|
20327
|
-
nextCursor
|
|
21081
|
+
nextCursor,
|
|
20328
21082
|
...query.sessionId ? { sessionFirings: this.sessionFirings(query.sessionId) } : {}
|
|
20329
21083
|
});
|
|
20330
21084
|
}
|
|
@@ -20356,6 +21110,266 @@ var SqliteFindingsRepository = class {
|
|
|
20356
21110
|
* request actually carries a `q`. (Substring matching is unaffected by a
|
|
20357
21111
|
* path repeating across tuples.)
|
|
20358
21112
|
*/
|
|
21113
|
+
/**
|
|
21114
|
+
* The instance-level (flat) findings list: one row per finding, newest first,
|
|
21115
|
+
* paged by keyset.
|
|
21116
|
+
*
|
|
21117
|
+
* SQL owns SCOPE, JS owns every FILTER DIMENSION. The session and the time
|
|
21118
|
+
* bound are SQL predicates: nothing counts them, so narrowing the scan by
|
|
21119
|
+
* them changes no reported number. Severity, subtype, provider, action,
|
|
21120
|
+
* status, tool, repo, file and `q` all stay in JS — each has a facet, and a
|
|
21121
|
+
* facet excludes its own filter, so a row the filter rejects still has to be
|
|
21122
|
+
* counted. Pushing any of them into SQL would silently empty its own facet.
|
|
21123
|
+
* Several could not be expressed there anyway: status comes from the one
|
|
21124
|
+
* shared classifier (deriveFindingStatus), and provider 'api' means "a tool
|
|
21125
|
+
* none of the mappers names", which no IN-list can say.
|
|
21126
|
+
*
|
|
21127
|
+
* The scan runs from the top of the scope on every request, not from the
|
|
21128
|
+
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
21129
|
+
* move as the caller pages. Rows are pulled in batches so memory stays flat
|
|
21130
|
+
* while the counting runs, and only the page itself is retained.
|
|
21131
|
+
*/
|
|
21132
|
+
listFindingInstances(query) {
|
|
21133
|
+
const opts = {
|
|
21134
|
+
severity: query.severity,
|
|
21135
|
+
subtype: query.subtype,
|
|
21136
|
+
providers: query.provider,
|
|
21137
|
+
actions: query.action,
|
|
21138
|
+
statuses: query.status,
|
|
21139
|
+
tools: query.tool,
|
|
21140
|
+
repo: query.repo,
|
|
21141
|
+
file: query.file,
|
|
21142
|
+
q: query.q
|
|
21143
|
+
};
|
|
21144
|
+
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
21145
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
21146
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
21147
|
+
const items = [];
|
|
21148
|
+
let total = 0;
|
|
21149
|
+
let last;
|
|
21150
|
+
let hasMore = false;
|
|
21151
|
+
for (const row of this.scanFindingRows({
|
|
21152
|
+
sessionId: query.sessionId,
|
|
21153
|
+
from: query.from
|
|
21154
|
+
})) {
|
|
21155
|
+
accumulator.add(row);
|
|
21156
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21157
|
+
total += 1;
|
|
21158
|
+
if (items.length < limit) {
|
|
21159
|
+
items.push(toInstanceDetail(row));
|
|
21160
|
+
last = row;
|
|
21161
|
+
} else {
|
|
21162
|
+
hasMore = true;
|
|
21163
|
+
}
|
|
21164
|
+
}
|
|
21165
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
21166
|
+
if (cursor !== null) {
|
|
21167
|
+
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
21168
|
+
return Promise.resolve({
|
|
21169
|
+
totals: { findings: total },
|
|
21170
|
+
facets: accumulator.facets(),
|
|
21171
|
+
items: resumed.items,
|
|
21172
|
+
nextCursor: resumed.nextCursor
|
|
21173
|
+
});
|
|
21174
|
+
}
|
|
21175
|
+
return Promise.resolve({
|
|
21176
|
+
totals: { findings: total },
|
|
21177
|
+
facets: accumulator.facets(),
|
|
21178
|
+
items,
|
|
21179
|
+
nextCursor
|
|
21180
|
+
});
|
|
21181
|
+
}
|
|
21182
|
+
/**
|
|
21183
|
+
* The page of matching rows strictly after `cursor`. Separate from the
|
|
21184
|
+
* counting pass because that one starts at the top of the scope by design;
|
|
21185
|
+
* this one narrows the scan with the same keyset predicate the activity list
|
|
21186
|
+
* uses, so a later page costs less than the first rather than more.
|
|
21187
|
+
*/
|
|
21188
|
+
pageAfter(cursor, opts, limit, query) {
|
|
21189
|
+
const items = [];
|
|
21190
|
+
let last;
|
|
21191
|
+
let hasMore = false;
|
|
21192
|
+
for (const row of this.scanFindingRows({
|
|
21193
|
+
sessionId: query.sessionId,
|
|
21194
|
+
from: query.from,
|
|
21195
|
+
after: cursor
|
|
21196
|
+
})) {
|
|
21197
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21198
|
+
if (items.length < limit) {
|
|
21199
|
+
items.push(toInstanceDetail(row));
|
|
21200
|
+
last = row;
|
|
21201
|
+
} else {
|
|
21202
|
+
hasMore = true;
|
|
21203
|
+
break;
|
|
21204
|
+
}
|
|
21205
|
+
}
|
|
21206
|
+
return {
|
|
21207
|
+
items,
|
|
21208
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
21209
|
+
};
|
|
21210
|
+
}
|
|
21211
|
+
/**
|
|
21212
|
+
* The same findings folded by location: repository, then file within it.
|
|
21213
|
+
*
|
|
21214
|
+
* The grouping keys come from the capturing event's attributes, which is what
|
|
21215
|
+
* the local store relates a finding to — there is no finding↔asset row to
|
|
21216
|
+
* group by instead. A repo or file the event did not record folds into the
|
|
21217
|
+
* empty-string bucket, which the view renders but does not link, since no
|
|
21218
|
+
* filter can name it.
|
|
21219
|
+
*/
|
|
21220
|
+
listFindingLocations(query) {
|
|
21221
|
+
const opts = {
|
|
21222
|
+
severity: query.severity,
|
|
21223
|
+
subtype: query.subtype,
|
|
21224
|
+
providers: query.provider,
|
|
21225
|
+
actions: query.action,
|
|
21226
|
+
statuses: query.status,
|
|
21227
|
+
tools: query.tool,
|
|
21228
|
+
q: query.q
|
|
21229
|
+
};
|
|
21230
|
+
const limit = query.limit ?? DEFAULT_LOCATIONS_LIMIT;
|
|
21231
|
+
const byRepo = /* @__PURE__ */ new Map();
|
|
21232
|
+
let total = 0;
|
|
21233
|
+
for (const row of this.scanFindingRows({
|
|
21234
|
+
sessionId: query.sessionId,
|
|
21235
|
+
from: query.from
|
|
21236
|
+
})) {
|
|
21237
|
+
if (!matchesInstanceFilters(row, opts)) continue;
|
|
21238
|
+
total += 1;
|
|
21239
|
+
let files = byRepo.get(row.repo);
|
|
21240
|
+
if (files === void 0) {
|
|
21241
|
+
files = /* @__PURE__ */ new Map();
|
|
21242
|
+
byRepo.set(row.repo, files);
|
|
21243
|
+
}
|
|
21244
|
+
let acc = files.get(row.file);
|
|
21245
|
+
if (acc === void 0) {
|
|
21246
|
+
acc = newLocationAccumulator();
|
|
21247
|
+
files.set(row.file, acc);
|
|
21248
|
+
}
|
|
21249
|
+
addToLocation(acc, row);
|
|
21250
|
+
}
|
|
21251
|
+
let fileCount = 0;
|
|
21252
|
+
const repos = [...byRepo.entries()].map(([repo, files]) => {
|
|
21253
|
+
fileCount += files.size;
|
|
21254
|
+
const fileRows = [...files.entries()].map(([file2, acc]) => ({
|
|
21255
|
+
file: file2,
|
|
21256
|
+
instanceCount: acc.instanceCount,
|
|
21257
|
+
maxSeverity: acc.maxSeverity,
|
|
21258
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
21259
|
+
...foldGroupStatus(acc.statuses) === void 0 ? {} : { status: foldGroupStatus(acc.statuses) },
|
|
21260
|
+
ruleIds: [...acc.ruleIds].slice(0, LOCATION_RULE_IDS_CAP)
|
|
21261
|
+
})).sort(compareLocationOrder);
|
|
21262
|
+
const rollup = fileRows.reduce(
|
|
21263
|
+
(a, f) => ({
|
|
21264
|
+
instanceCount: a.instanceCount + f.instanceCount,
|
|
21265
|
+
maxSeverity: compareLocationOrder(f, a) < 0 ? f.maxSeverity : a.maxSeverity,
|
|
21266
|
+
latestDetectedAt: f.latestDetectedAt > a.latestDetectedAt ? f.latestDetectedAt : a.latestDetectedAt
|
|
21267
|
+
}),
|
|
21268
|
+
{
|
|
21269
|
+
instanceCount: 0,
|
|
21270
|
+
maxSeverity: fileRows[0]?.maxSeverity ?? "low",
|
|
21271
|
+
latestDetectedAt: ""
|
|
21272
|
+
}
|
|
21273
|
+
);
|
|
21274
|
+
const statuses = fileRows.map((f) => f.status);
|
|
21275
|
+
const folded = foldGroupStatus(statuses);
|
|
21276
|
+
return {
|
|
21277
|
+
repo,
|
|
21278
|
+
instanceCount: rollup.instanceCount,
|
|
21279
|
+
maxSeverity: rollup.maxSeverity,
|
|
21280
|
+
latestDetectedAt: rollup.latestDetectedAt,
|
|
21281
|
+
...folded === void 0 ? {} : { status: folded },
|
|
21282
|
+
files: fileRows
|
|
21283
|
+
};
|
|
21284
|
+
});
|
|
21285
|
+
repos.sort(compareLocationOrder);
|
|
21286
|
+
return Promise.resolve({
|
|
21287
|
+
totals: { findings: total, repos: repos.length, files: fileCount },
|
|
21288
|
+
items: repos.slice(0, limit),
|
|
21289
|
+
hasMore: repos.length > limit
|
|
21290
|
+
});
|
|
21291
|
+
}
|
|
21292
|
+
/**
|
|
21293
|
+
* Every finding in scope as a FlatFindingRow, newest first, pulled in batches.
|
|
21294
|
+
*
|
|
21295
|
+
* A generator so a caller streams the scope without it ever being an array:
|
|
21296
|
+
* the flat list counts and facets the whole filtered scope, which on a large
|
|
21297
|
+
* store is far more rows than any page. Each batch advances the same keyset
|
|
21298
|
+
* predicate the page read uses, so the scan is a sequence of bounded reads
|
|
21299
|
+
* rather than one unbounded result set.
|
|
21300
|
+
*
|
|
21301
|
+
* The latest-resolution lookup is the CORRELATED form, not the derived table
|
|
21302
|
+
* the grouped path joins: only `status` is needed, idx_finding_resolution_key
|
|
21303
|
+
* makes it a point lookup per row, and the derived table would re-materialize
|
|
21304
|
+
* a window over the whole resolution table once per batch.
|
|
21305
|
+
*
|
|
21306
|
+
* `scope` carries ONLY what no facet counts. A filter dimension narrowed here
|
|
21307
|
+
* would be missing from its own facet, which is computed by excluding that
|
|
21308
|
+
* dimension — see listFindingInstances.
|
|
21309
|
+
*/
|
|
21310
|
+
*scanFindingRows(scope) {
|
|
21311
|
+
const conditions = [`e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
21312
|
+
const params = [];
|
|
21313
|
+
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
21314
|
+
conditions.push("e.root_session_id = ?");
|
|
21315
|
+
params.push(scope.sessionId);
|
|
21316
|
+
}
|
|
21317
|
+
if (scope.from !== void 0) {
|
|
21318
|
+
conditions.push("e.started_at >= ?");
|
|
21319
|
+
params.push(isoToEpochMillis(scope.from));
|
|
21320
|
+
}
|
|
21321
|
+
const sql = `SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
21322
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
21323
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
21324
|
+
e.started_at AS occurred_at,
|
|
21325
|
+
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
21326
|
+
json_extract(e.attributes, '$.repo') AS repo,
|
|
21327
|
+
json_extract(e.attributes, '$.file_path') AS file,
|
|
21328
|
+
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
21329
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
21330
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
21331
|
+
${latestResolutionStatusSql("f")} AS latest_status
|
|
21332
|
+
FROM inspection_findings f
|
|
21333
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
21334
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21335
|
+
WHERE ${conditions.join(" AND ")}
|
|
21336
|
+
AND (e.started_at < ? OR (e.started_at = ? AND f.id < ?))
|
|
21337
|
+
ORDER BY e.started_at DESC, f.id DESC
|
|
21338
|
+
LIMIT ?`;
|
|
21339
|
+
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
21340
|
+
for (; ; ) {
|
|
21341
|
+
const rows = allRows(this.db.prepare(sql), [
|
|
21342
|
+
...params,
|
|
21343
|
+
after.startedAtMs,
|
|
21344
|
+
after.startedAtMs,
|
|
21345
|
+
after.id,
|
|
21346
|
+
SCAN_BATCH_ROWS
|
|
21347
|
+
]);
|
|
21348
|
+
for (const r of rows) {
|
|
21349
|
+
yield {
|
|
21350
|
+
id: r.id,
|
|
21351
|
+
ruleId: r.rule_id,
|
|
21352
|
+
category: r.category,
|
|
21353
|
+
severity: r.severity,
|
|
21354
|
+
maskedMatch: r.masked_match,
|
|
21355
|
+
actionTaken: r.action_taken,
|
|
21356
|
+
confidence: r.confidence,
|
|
21357
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
21358
|
+
sourceTool: r.source_tool,
|
|
21359
|
+
repo: r.repo ?? "",
|
|
21360
|
+
file: r.file ?? "",
|
|
21361
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
21362
|
+
eventId: r.event_id,
|
|
21363
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
21364
|
+
status: deriveInstanceStatus(r)
|
|
21365
|
+
};
|
|
21366
|
+
}
|
|
21367
|
+
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
21368
|
+
const lastRow = rows[rows.length - 1];
|
|
21369
|
+
if (lastRow === void 0) return;
|
|
21370
|
+
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
21371
|
+
}
|
|
21372
|
+
}
|
|
20359
21373
|
groupAggregates(withSearchText, scope) {
|
|
20360
21374
|
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT json_extract(e.attributes, '$.repo')) AS repos,
|
|
20361
21375
|
group_concat(DISTINCT json_extract(e.attributes, '$.file_path')) AS files,
|
|
@@ -20616,7 +21630,7 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
20616
21630
|
};
|
|
20617
21631
|
|
|
20618
21632
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
20619
|
-
import { createHash as createHash2, randomUUID as
|
|
21633
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
20620
21634
|
|
|
20621
21635
|
// ../../packages/persistence/src/semver.ts
|
|
20622
21636
|
function parse3(version2) {
|
|
@@ -20767,7 +21781,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20767
21781
|
let behind = false;
|
|
20768
21782
|
for (const row of rows) {
|
|
20769
21783
|
const params = {
|
|
20770
|
-
id:
|
|
21784
|
+
id: randomUUID3(),
|
|
20771
21785
|
namespace: row.namespace,
|
|
20772
21786
|
packId: row.packId,
|
|
20773
21787
|
version: row.version,
|
|
@@ -20779,7 +21793,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
20779
21793
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
20780
21794
|
this.upsertAvailableStmt.run({
|
|
20781
21795
|
...params,
|
|
20782
|
-
id:
|
|
21796
|
+
id: randomUUID3(),
|
|
20783
21797
|
recordedBy: meta3?.recordedBy ?? null
|
|
20784
21798
|
});
|
|
20785
21799
|
} else {
|
|
@@ -21102,14 +22116,15 @@ var SqliteInventoryRepository = class {
|
|
|
21102
22116
|
};
|
|
21103
22117
|
|
|
21104
22118
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
21105
|
-
import { randomUUID as
|
|
22119
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
21106
22120
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
21107
22121
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
21108
22122
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
21109
22123
|
var HARNESS_LABELS = {
|
|
21110
22124
|
claudecode: "Claude Code",
|
|
21111
22125
|
cursor: "Cursor",
|
|
21112
|
-
codex: "Codex"
|
|
22126
|
+
codex: "Codex",
|
|
22127
|
+
antigravity: "Antigravity"
|
|
21113
22128
|
};
|
|
21114
22129
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
21115
22130
|
var EMPTY_PROJECT_AGG = {
|
|
@@ -21124,6 +22139,7 @@ function resolveHarnessId(attrs, row) {
|
|
|
21124
22139
|
if (t.includes("claudecode") || t === "claude") return "claudecode";
|
|
21125
22140
|
if (t.includes("cursor")) return "cursor";
|
|
21126
22141
|
if (t.includes("codex")) return "codex";
|
|
22142
|
+
if (t.includes("antigravity")) return "antigravity";
|
|
21127
22143
|
return null;
|
|
21128
22144
|
}
|
|
21129
22145
|
function isLiveRealClaudeCode(rows) {
|
|
@@ -21582,7 +22598,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21582
22598
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
21583
22599
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
21584
22600
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
21585
|
-
).run({ id:
|
|
22601
|
+
).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
|
|
21586
22602
|
}
|
|
21587
22603
|
return true;
|
|
21588
22604
|
}
|
|
@@ -21603,7 +22619,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21603
22619
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
21604
22620
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
21605
22621
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
21606
|
-
).run({ id:
|
|
22622
|
+
).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
|
|
21607
22623
|
}
|
|
21608
22624
|
this.configRowsCache = void 0;
|
|
21609
22625
|
return "ok";
|
|
@@ -21900,7 +22916,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
21900
22916
|
};
|
|
21901
22917
|
|
|
21902
22918
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
21903
|
-
import { randomUUID as
|
|
22919
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
21904
22920
|
var SqlitePoliciesRepository = class {
|
|
21905
22921
|
constructor(db) {
|
|
21906
22922
|
this.db = db;
|
|
@@ -21935,7 +22951,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21935
22951
|
failOpenTransaction(this.db, () => {
|
|
21936
22952
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
21937
22953
|
stmt.run({
|
|
21938
|
-
id:
|
|
22954
|
+
id: randomUUID5(),
|
|
21939
22955
|
target: JSON.stringify({ category }),
|
|
21940
22956
|
action,
|
|
21941
22957
|
now: Date.now()
|
|
@@ -21955,7 +22971,7 @@ var SqlitePoliciesRepository = class {
|
|
|
21955
22971
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
21956
22972
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
21957
22973
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
21958
|
-
).run({ id:
|
|
22974
|
+
).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
|
|
21959
22975
|
}
|
|
21960
22976
|
// Caps every global per-category policy currently set to block/redact down
|
|
21961
22977
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -22023,7 +23039,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
22023
23039
|
};
|
|
22024
23040
|
|
|
22025
23041
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
22026
|
-
import { randomUUID as
|
|
23042
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
22027
23043
|
var SqliteProjectFilesRepository = class {
|
|
22028
23044
|
constructor(db) {
|
|
22029
23045
|
this.db = db;
|
|
@@ -22055,7 +23071,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22055
23071
|
const stamp = Math.max(now, maxStamp + 1);
|
|
22056
23072
|
for (const file2 of scan2.files) {
|
|
22057
23073
|
this.upsertStmt.run({
|
|
22058
|
-
id:
|
|
23074
|
+
id: randomUUID6(),
|
|
22059
23075
|
projectId,
|
|
22060
23076
|
path: file2.path,
|
|
22061
23077
|
name: file2.name,
|
|
@@ -22069,7 +23085,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
22069
23085
|
};
|
|
22070
23086
|
|
|
22071
23087
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
22072
|
-
import { randomUUID as
|
|
23088
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
22073
23089
|
var SqliteResolutionsRepository = class {
|
|
22074
23090
|
constructor(db, now = () => Date.now()) {
|
|
22075
23091
|
this.db = db;
|
|
@@ -22123,7 +23139,7 @@ var SqliteResolutionsRepository = class {
|
|
|
22123
23139
|
*/
|
|
22124
23140
|
insertResolution(r) {
|
|
22125
23141
|
this.insertStmt.run({
|
|
22126
|
-
id:
|
|
23142
|
+
id: randomUUID7(),
|
|
22127
23143
|
findingKey: r.findingKey,
|
|
22128
23144
|
status: FindingStatus.parse(r.status),
|
|
22129
23145
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -22182,13 +23198,51 @@ var SqliteRuleProbeCacheRepository = class {
|
|
|
22182
23198
|
this.readStmt = db.prepare(
|
|
22183
23199
|
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
22184
23200
|
);
|
|
23201
|
+
this.countQuarantinedStmt = db.prepare(
|
|
23202
|
+
`SELECT COUNT(*) AS n FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23203
|
+
);
|
|
23204
|
+
this.clearQuarantinedStmt = db.prepare(
|
|
23205
|
+
`DELETE FROM rule_probe_cache WHERE verdict = 'quarantined'`
|
|
23206
|
+
);
|
|
22185
23207
|
}
|
|
22186
23208
|
db;
|
|
22187
23209
|
upsertStmt;
|
|
22188
23210
|
readStmt;
|
|
23211
|
+
countQuarantinedStmt;
|
|
23212
|
+
clearQuarantinedStmt;
|
|
22189
23213
|
getVerdict(ruleKey) {
|
|
22190
23214
|
return getRow(this.readStmt, { ruleKey });
|
|
22191
23215
|
}
|
|
23216
|
+
/** How many rules are currently excluded by a cached quarantine verdict. */
|
|
23217
|
+
countQuarantined() {
|
|
23218
|
+
return getRow(this.countQuarantinedStmt, {})?.n ?? 0;
|
|
23219
|
+
}
|
|
23220
|
+
/**
|
|
23221
|
+
* Forgets every quarantine verdict, so the rules behind them are measured
|
|
23222
|
+
* again on the next load. This is the undo for a verdict the machine reached
|
|
23223
|
+
* on its own: a rule terminated mid-scan is cached forever and dropped from
|
|
23224
|
+
* every later scan, and a timing verdict is a wall-clock judgement that a
|
|
23225
|
+
* loaded or slow machine can reach about a rule that is in fact fine.
|
|
23226
|
+
*
|
|
23227
|
+
* Only 'quarantined' rows go — a 'safe' verdict is a measurement worth
|
|
23228
|
+
* keeping, and dropping it would make every rule pay the battery again.
|
|
23229
|
+
*
|
|
23230
|
+
* Reports `refused` from the write's own result rather than inferring it from
|
|
23231
|
+
* the row count. The two are NOT the same answer: `failOpenTransaction`
|
|
23232
|
+
* swallows a contended DELETE (another writer holding the lock past
|
|
23233
|
+
* `busy_timeout` — reads are unaffected in WAL), and a swallowed refusal
|
|
23234
|
+
* leaves the count unchanged, which is indistinguishable from "there was
|
|
23235
|
+
* nothing to clear". An undo that reports success while the quarantines are
|
|
23236
|
+
* still in place is worse than one that fails, because the rules it claimed
|
|
23237
|
+
* to restore are silently still disabled.
|
|
23238
|
+
*/
|
|
23239
|
+
clearQuarantined() {
|
|
23240
|
+
const before = this.countQuarantined();
|
|
23241
|
+
const committed = failOpenTransaction(this.db, () => {
|
|
23242
|
+
this.clearQuarantinedStmt.run();
|
|
23243
|
+
});
|
|
23244
|
+
return { refused: !committed, cleared: before - this.countQuarantined() };
|
|
23245
|
+
}
|
|
22192
23246
|
setVerdict(ruleKey, verdict, worstProbeMs2) {
|
|
22193
23247
|
failOpenTransaction(this.db, () => {
|
|
22194
23248
|
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs: worstProbeMs2, checkedAt: Date.now() });
|
|
@@ -22225,20 +23279,433 @@ var SqliteScanLedgerRepository = class {
|
|
|
22225
23279
|
});
|
|
22226
23280
|
return new Map(rows.map((r) => [r.path, { mtime: r.mtime, contentHash: r.contentHash }]));
|
|
22227
23281
|
}
|
|
22228
|
-
upsertEntries(entries) {
|
|
22229
|
-
if (entries.length === 0) return;
|
|
22230
|
-
const scannedAt = Date.now();
|
|
22231
|
-
failOpenTransaction(this.db, () => {
|
|
22232
|
-
for (const entry of entries) {
|
|
22233
|
-
this.upsertStmt.run({
|
|
22234
|
-
path: entry.path,
|
|
22235
|
-
mtime: entry.mtime,
|
|
22236
|
-
contentHash: entry.contentHash,
|
|
22237
|
-
rulesetHash: entry.rulesetHash,
|
|
22238
|
-
scannedAt
|
|
22239
|
-
});
|
|
22240
|
-
}
|
|
22241
|
-
});
|
|
23282
|
+
upsertEntries(entries) {
|
|
23283
|
+
if (entries.length === 0) return;
|
|
23284
|
+
const scannedAt = Date.now();
|
|
23285
|
+
failOpenTransaction(this.db, () => {
|
|
23286
|
+
for (const entry of entries) {
|
|
23287
|
+
this.upsertStmt.run({
|
|
23288
|
+
path: entry.path,
|
|
23289
|
+
mtime: entry.mtime,
|
|
23290
|
+
contentHash: entry.contentHash,
|
|
23291
|
+
rulesetHash: entry.rulesetHash,
|
|
23292
|
+
scannedAt
|
|
23293
|
+
});
|
|
23294
|
+
}
|
|
23295
|
+
});
|
|
23296
|
+
}
|
|
23297
|
+
};
|
|
23298
|
+
|
|
23299
|
+
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
23300
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23301
|
+
function pageLimit(requested, fallback) {
|
|
23302
|
+
if (requested === void 0) return fallback;
|
|
23303
|
+
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
23304
|
+
}
|
|
23305
|
+
function encodeReuseCursor(payload) {
|
|
23306
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
23307
|
+
}
|
|
23308
|
+
function decodeReuseCursor(cursor) {
|
|
23309
|
+
const parsed = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
23310
|
+
if (parsed !== void 0 && // `Number.isInteger`, not `typeof === 'number'`: a payload carrying
|
|
23311
|
+
// ±Infinity or a fraction binds cleanly and returns an EMPTY page with a
|
|
23312
|
+
// null cursor, which the caller reads as "end of list" — the one outcome a
|
|
23313
|
+
// malformed cursor must never produce, since restarting from the top is the
|
|
23314
|
+
// documented behaviour and the only recoverable one. (`1e999` is valid JSON
|
|
23315
|
+
// and parses to Infinity; a bare `NaN` is not, so it cannot arrive here.)
|
|
23316
|
+
Number.isInteger(parsed.occurrences) && typeof parsed.pointerId === "string") {
|
|
23317
|
+
return { occurrences: parsed.occurrences, pointerId: parsed.pointerId };
|
|
23318
|
+
}
|
|
23319
|
+
return null;
|
|
23320
|
+
}
|
|
23321
|
+
var REUSED_PREDICATE = `(v.occurrence_count > 1
|
|
23322
|
+
OR (SELECT count(*) FROM secret_vault_sighting s WHERE s.pointer_id = v.pointer_id) > 1)`;
|
|
23323
|
+
var INVENTORY_COLUMNS = `v.pointer_id, v.category, v.rule_id, v.masked_match, v.provider,
|
|
23324
|
+
v.occurrence_count, v.first_seen, v.last_seen`;
|
|
23325
|
+
function toSighting(row) {
|
|
23326
|
+
return {
|
|
23327
|
+
location: row.location,
|
|
23328
|
+
kind: row.kind,
|
|
23329
|
+
firstSeen: new Date(row.first_seen).toISOString(),
|
|
23330
|
+
lastSeen: new Date(row.last_seen).toISOString()
|
|
23331
|
+
};
|
|
23332
|
+
}
|
|
23333
|
+
var SELECT_COLUMNS = `
|
|
23334
|
+
pointer_id AS pointerId,
|
|
23335
|
+
value_fingerprint AS valueFingerprint,
|
|
23336
|
+
fingerprint_key_version AS fingerprintKeyVersion,
|
|
23337
|
+
key_version AS keyVersion,
|
|
23338
|
+
format_version AS formatVersion,
|
|
23339
|
+
category,
|
|
23340
|
+
rule_id AS ruleId,
|
|
23341
|
+
masked_match AS maskedMatch,
|
|
23342
|
+
provider,
|
|
23343
|
+
ciphertext,
|
|
23344
|
+
nonce,
|
|
23345
|
+
auth_tag AS authTag,
|
|
23346
|
+
occurrence_count AS occurrenceCount,
|
|
23347
|
+
first_seen AS firstSeen,
|
|
23348
|
+
last_seen AS lastSeen`;
|
|
23349
|
+
function toRow(raw) {
|
|
23350
|
+
const { provider, ...rest } = raw;
|
|
23351
|
+
return provider === null ? rest : { ...rest, provider };
|
|
23352
|
+
}
|
|
23353
|
+
var SqliteSecretVaultRepository = class {
|
|
23354
|
+
constructor(db) {
|
|
23355
|
+
this.db = db;
|
|
23356
|
+
this.insertStmt = db.prepare(
|
|
23357
|
+
`INSERT INTO secret_vault (
|
|
23358
|
+
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
23359
|
+
format_version, category, rule_id, masked_match, provider,
|
|
23360
|
+
ciphertext, nonce, auth_tag,
|
|
23361
|
+
occurrence_count, first_seen, last_seen
|
|
23362
|
+
) VALUES (
|
|
23363
|
+
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
23364
|
+
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
23365
|
+
:ciphertext, :nonce, :authTag,
|
|
23366
|
+
1, :now, :now
|
|
23367
|
+
)`
|
|
23368
|
+
);
|
|
23369
|
+
this.bumpStmt = db.prepare(
|
|
23370
|
+
`UPDATE secret_vault
|
|
23371
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
23372
|
+
WHERE value_fingerprint = :valueFingerprint`
|
|
23373
|
+
);
|
|
23374
|
+
this.byPointerStmt = db.prepare(
|
|
23375
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE pointer_id = :pointerId`
|
|
23376
|
+
);
|
|
23377
|
+
this.byFingerprintStmt = db.prepare(
|
|
23378
|
+
`SELECT ${SELECT_COLUMNS} FROM secret_vault WHERE value_fingerprint = :valueFingerprint`
|
|
23379
|
+
);
|
|
23380
|
+
this.listStmt = db.prepare(`SELECT ${SELECT_COLUMNS} FROM secret_vault ORDER BY first_seen`);
|
|
23381
|
+
this.replaceCiphertextStmt = db.prepare(
|
|
23382
|
+
`UPDATE secret_vault
|
|
23383
|
+
SET key_version = :keyVersion, ciphertext = :ciphertext, nonce = :nonce, auth_tag = :authTag
|
|
23384
|
+
WHERE pointer_id = :pointerId`
|
|
23385
|
+
);
|
|
23386
|
+
this.refreshFingerprintStmt = db.prepare(
|
|
23387
|
+
`UPDATE secret_vault
|
|
23388
|
+
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
23389
|
+
WHERE pointer_id = :pointerId`
|
|
23390
|
+
);
|
|
23391
|
+
this.derefStmt = db.prepare(
|
|
23392
|
+
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
23393
|
+
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
23394
|
+
);
|
|
23395
|
+
}
|
|
23396
|
+
db;
|
|
23397
|
+
insertStmt;
|
|
23398
|
+
bumpStmt;
|
|
23399
|
+
byPointerStmt;
|
|
23400
|
+
byFingerprintStmt;
|
|
23401
|
+
listStmt;
|
|
23402
|
+
replaceCiphertextStmt;
|
|
23403
|
+
refreshFingerprintStmt;
|
|
23404
|
+
derefStmt;
|
|
23405
|
+
/**
|
|
23406
|
+
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
23407
|
+
* `valueFingerprint`, never on the caller's pointer id: a value seen again
|
|
23408
|
+
* bumps `occurrence_count` and `last_seen` and comes back with its ORIGINAL
|
|
23409
|
+
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
23410
|
+
* wire token. `minted` is true only when this call created the row.
|
|
23411
|
+
*
|
|
23412
|
+
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
23413
|
+
* writers cannot both decide they are minting.
|
|
23414
|
+
*/
|
|
23415
|
+
upsert(input, now) {
|
|
23416
|
+
let minted = false;
|
|
23417
|
+
withTransaction(
|
|
23418
|
+
this.db,
|
|
23419
|
+
() => {
|
|
23420
|
+
const existing = getRow(this.byFingerprintStmt, {
|
|
23421
|
+
valueFingerprint: input.valueFingerprint
|
|
23422
|
+
});
|
|
23423
|
+
if (existing === void 0) {
|
|
23424
|
+
this.insertStmt.run(
|
|
23425
|
+
bindParams({
|
|
23426
|
+
pointerId: input.pointerId,
|
|
23427
|
+
valueFingerprint: input.valueFingerprint,
|
|
23428
|
+
fingerprintKeyVersion: input.fingerprintKeyVersion,
|
|
23429
|
+
keyVersion: input.keyVersion,
|
|
23430
|
+
formatVersion: input.formatVersion ?? POINTER_FORMAT_VERSION,
|
|
23431
|
+
category: input.category,
|
|
23432
|
+
ruleId: input.ruleId,
|
|
23433
|
+
maskedMatch: input.maskedMatch,
|
|
23434
|
+
provider: input.provider,
|
|
23435
|
+
ciphertext: input.ciphertext,
|
|
23436
|
+
nonce: input.nonce,
|
|
23437
|
+
authTag: input.authTag,
|
|
23438
|
+
now
|
|
23439
|
+
})
|
|
23440
|
+
);
|
|
23441
|
+
minted = true;
|
|
23442
|
+
return;
|
|
23443
|
+
}
|
|
23444
|
+
this.bumpStmt.run({ valueFingerprint: input.valueFingerprint, now });
|
|
23445
|
+
},
|
|
23446
|
+
"IMMEDIATE"
|
|
23447
|
+
);
|
|
23448
|
+
const row = getRow(this.byFingerprintStmt, {
|
|
23449
|
+
valueFingerprint: input.valueFingerprint
|
|
23450
|
+
});
|
|
23451
|
+
if (row === void 0) throw new Error("vault: row vanished immediately after write");
|
|
23452
|
+
return { row: toRow(row), minted };
|
|
23453
|
+
}
|
|
23454
|
+
byPointerId(pointerId) {
|
|
23455
|
+
const raw = getRow(this.byPointerStmt, { pointerId });
|
|
23456
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23457
|
+
}
|
|
23458
|
+
byValueFingerprint(fingerprint) {
|
|
23459
|
+
const raw = getRow(this.byFingerprintStmt, { valueFingerprint: fingerprint });
|
|
23460
|
+
return raw === void 0 ? null : toRow(raw);
|
|
23461
|
+
}
|
|
23462
|
+
/** Append one audit row. Carries no raw value and no ciphertext, by shape. */
|
|
23463
|
+
recordDeref(entry) {
|
|
23464
|
+
this.derefStmt.run(
|
|
23465
|
+
bindParams({
|
|
23466
|
+
id: entry.id,
|
|
23467
|
+
pointerId: entry.pointerId,
|
|
23468
|
+
at: entry.at,
|
|
23469
|
+
target: entry.target,
|
|
23470
|
+
reason: entry.reason,
|
|
23471
|
+
outcome: entry.outcome,
|
|
23472
|
+
grantId: entry.grantId,
|
|
23473
|
+
pointerCount: entry.pointerCount ?? 1
|
|
23474
|
+
})
|
|
23475
|
+
);
|
|
23476
|
+
}
|
|
23477
|
+
listAll() {
|
|
23478
|
+
return allRows(this.listStmt).map(toRow);
|
|
23479
|
+
}
|
|
23480
|
+
/** Re-seal an entry under a new key epoch, leaving its identity untouched. */
|
|
23481
|
+
replaceCiphertext(pointerId, next) {
|
|
23482
|
+
this.replaceCiphertextStmt.run({ pointerId, ...next });
|
|
23483
|
+
}
|
|
23484
|
+
/** Re-derive an entry's fingerprint under a new fingerprint-key epoch. */
|
|
23485
|
+
refreshFingerprint(pointerId, next) {
|
|
23486
|
+
this.refreshFingerprintStmt.run({ pointerId, ...next });
|
|
23487
|
+
}
|
|
23488
|
+
/**
|
|
23489
|
+
* Destroy every vaulted value and report how many were destroyed. The deref
|
|
23490
|
+
* audit is left alone on purpose — see the table note above.
|
|
23491
|
+
*/
|
|
23492
|
+
purgeAll() {
|
|
23493
|
+
let destroyed = 0;
|
|
23494
|
+
withTransaction(
|
|
23495
|
+
this.db,
|
|
23496
|
+
() => {
|
|
23497
|
+
destroyed = this.countEntries();
|
|
23498
|
+
this.db.exec("DELETE FROM secret_vault");
|
|
23499
|
+
},
|
|
23500
|
+
"IMMEDIATE"
|
|
23501
|
+
);
|
|
23502
|
+
return destroyed;
|
|
23503
|
+
}
|
|
23504
|
+
/**
|
|
23505
|
+
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
23506
|
+
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
23507
|
+
* on hook paths — a failure must never affect the rewrite that triggered it,
|
|
23508
|
+
* so callers wrap this, not the other way around.
|
|
23509
|
+
*/
|
|
23510
|
+
recordSighting(entry, now) {
|
|
23511
|
+
this.db.prepare(
|
|
23512
|
+
`INSERT INTO secret_vault_sighting (id, pointer_id, location, kind, first_seen, last_seen)
|
|
23513
|
+
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
23514
|
+
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
23515
|
+
).run({
|
|
23516
|
+
id: randomUUID8(),
|
|
23517
|
+
pointerId: entry.pointerId,
|
|
23518
|
+
location: entry.location,
|
|
23519
|
+
kind: entry.kind,
|
|
23520
|
+
now
|
|
23521
|
+
});
|
|
23522
|
+
}
|
|
23523
|
+
/**
|
|
23524
|
+
* Sightings for a whole page of pointers, in ONE query grouped in JS rather
|
|
23525
|
+
* than one query per row. A pointer with no sightings still gets an entry, so
|
|
23526
|
+
* the caller never has to distinguish "none" from "missing".
|
|
23527
|
+
*
|
|
23528
|
+
* The `IN` list is sized to the page, so this statement cannot be cached on
|
|
23529
|
+
* the instance the way the fixed-shape ones in the constructor are.
|
|
23530
|
+
*/
|
|
23531
|
+
sightingsFor(pointerIds) {
|
|
23532
|
+
const byPointer = new Map(pointerIds.map((id) => [id, []]));
|
|
23533
|
+
if (pointerIds.length === 0) return byPointer;
|
|
23534
|
+
const rows = allRows(
|
|
23535
|
+
this.db.prepare(
|
|
23536
|
+
`SELECT pointer_id, location, kind, first_seen, last_seen
|
|
23537
|
+
FROM secret_vault_sighting
|
|
23538
|
+
WHERE pointer_id IN (${placeholders(pointerIds.length)})
|
|
23539
|
+
ORDER BY last_seen DESC`
|
|
23540
|
+
),
|
|
23541
|
+
pointerIds
|
|
23542
|
+
);
|
|
23543
|
+
for (const row of rows) byPointer.get(row.pointer_id)?.push(toSighting(row));
|
|
23544
|
+
return byPointer;
|
|
23545
|
+
}
|
|
23546
|
+
/** Hydrate a page of raw inventory rows with their sightings, batched. */
|
|
23547
|
+
toInventoryEntries(rows) {
|
|
23548
|
+
const sightings = this.sightingsFor(rows.map((r) => r.pointer_id));
|
|
23549
|
+
return rows.map((r) => ({
|
|
23550
|
+
pointerId: r.pointer_id,
|
|
23551
|
+
category: r.category,
|
|
23552
|
+
...r.provider === null ? {} : { provider: r.provider },
|
|
23553
|
+
maskedMatch: r.masked_match,
|
|
23554
|
+
occurrences: r.occurrence_count,
|
|
23555
|
+
firstSeen: new Date(r.first_seen).toISOString(),
|
|
23556
|
+
lastSeen: new Date(r.last_seen).toISOString(),
|
|
23557
|
+
revealGrantId: r.grant_id,
|
|
23558
|
+
sightings: sightings.get(r.pointer_id) ?? []
|
|
23559
|
+
}));
|
|
23560
|
+
}
|
|
23561
|
+
/**
|
|
23562
|
+
* The dashboard inventory, newest-first, ONE PAGE at a time: every vaulted
|
|
23563
|
+
* value's descriptor data joined with its sightings and the active
|
|
23564
|
+
* reveal-to-model grant when one exists. Raw-free by construction — neither
|
|
23565
|
+
* the fingerprint nor the ciphertext columns are selected.
|
|
23566
|
+
*
|
|
23567
|
+
* `totals.values` counts the whole store, not the page, so the count a reader
|
|
23568
|
+
* sees never depends on how far they have paged.
|
|
23569
|
+
*/
|
|
23570
|
+
listInventory(query = {}, now = Date.now()) {
|
|
23571
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23572
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23573
|
+
const where = cursor === null ? "" : "WHERE (v.last_seen < :cursorLastSeen OR (v.last_seen = :cursorLastSeen AND v.pointer_id < :cursorPointerId))";
|
|
23574
|
+
const rows = allRows(
|
|
23575
|
+
this.db.prepare(
|
|
23576
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23577
|
+
(SELECT e.id FROM exceptions e
|
|
23578
|
+
WHERE e.rule_id = v.rule_id
|
|
23579
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23580
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23581
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23582
|
+
LIMIT 1) AS grant_id
|
|
23583
|
+
FROM secret_vault v
|
|
23584
|
+
${where}
|
|
23585
|
+
ORDER BY v.last_seen DESC, v.pointer_id DESC
|
|
23586
|
+
LIMIT :limit`
|
|
23587
|
+
),
|
|
23588
|
+
bindParams({
|
|
23589
|
+
now,
|
|
23590
|
+
limit: limit + 1,
|
|
23591
|
+
...cursor === null ? {} : { cursorLastSeen: cursor.startedAtMs, cursorPointerId: cursor.id }
|
|
23592
|
+
})
|
|
23593
|
+
);
|
|
23594
|
+
const hasMore = rows.length > limit;
|
|
23595
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23596
|
+
const last = page[page.length - 1];
|
|
23597
|
+
return {
|
|
23598
|
+
totals: { values: this.countEntries() },
|
|
23599
|
+
items: this.toInventoryEntries(page),
|
|
23600
|
+
// Minted from the last row of the PAGE, never the extra probe row.
|
|
23601
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.last_seen, id: last.pointer_id }) : null
|
|
23602
|
+
};
|
|
23603
|
+
}
|
|
23604
|
+
/**
|
|
23605
|
+
* Values reused on this machine — detected more than once, or written to more
|
|
23606
|
+
* than one location — most-reused first, one page at a time.
|
|
23607
|
+
*
|
|
23608
|
+
* Its own read rather than a filter over an inventory page: reuse is a
|
|
23609
|
+
* property of the whole store, and deriving it from 50 newest rows would
|
|
23610
|
+
* under-report exactly the values a reader most needs to see.
|
|
23611
|
+
*/
|
|
23612
|
+
listReuse(query = {}, now = Date.now()) {
|
|
23613
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_INVENTORY_LIMIT);
|
|
23614
|
+
const cursor = query.cursor === void 0 ? null : decodeReuseCursor(query.cursor);
|
|
23615
|
+
const after = cursor === null ? "" : `AND (v.occurrence_count < :cursorOccurrences
|
|
23616
|
+
OR (v.occurrence_count = :cursorOccurrences AND v.pointer_id < :cursorPointerId))`;
|
|
23617
|
+
const rows = allRows(
|
|
23618
|
+
this.db.prepare(
|
|
23619
|
+
`SELECT ${INVENTORY_COLUMNS},
|
|
23620
|
+
(SELECT e.id FROM exceptions e
|
|
23621
|
+
WHERE e.rule_id = v.rule_id
|
|
23622
|
+
AND e.value_fingerprint = v.value_fingerprint
|
|
23623
|
+
AND e.key_version = v.fingerprint_key_version
|
|
23624
|
+
AND ${ACTIVE_REVEAL_GRANT_PREDICATE}
|
|
23625
|
+
LIMIT 1) AS grant_id
|
|
23626
|
+
FROM secret_vault v
|
|
23627
|
+
WHERE ${REUSED_PREDICATE} ${after}
|
|
23628
|
+
ORDER BY v.occurrence_count DESC, v.pointer_id DESC
|
|
23629
|
+
LIMIT :limit`
|
|
23630
|
+
),
|
|
23631
|
+
bindParams({
|
|
23632
|
+
now,
|
|
23633
|
+
limit: limit + 1,
|
|
23634
|
+
...cursor === null ? {} : { cursorOccurrences: cursor.occurrences, cursorPointerId: cursor.pointerId }
|
|
23635
|
+
})
|
|
23636
|
+
);
|
|
23637
|
+
const hasMore = rows.length > limit;
|
|
23638
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23639
|
+
const last = page[page.length - 1];
|
|
23640
|
+
return {
|
|
23641
|
+
totals: { reused: this.countReused() },
|
|
23642
|
+
items: this.toInventoryEntries(page),
|
|
23643
|
+
nextCursor: hasMore && last ? encodeReuseCursor({ occurrences: last.occurrence_count, pointerId: last.pointer_id }) : null
|
|
23644
|
+
};
|
|
23645
|
+
}
|
|
23646
|
+
/**
|
|
23647
|
+
* The de-reference trail, newest first, one page at a time. By default the
|
|
23648
|
+
* batched, high-volume reasons (display, view-render) are hidden and counted
|
|
23649
|
+
* instead — the rows that matter as a signal are the model crossings, and
|
|
23650
|
+
* burying them under render noise would defeat the audit's purpose.
|
|
23651
|
+
*
|
|
23652
|
+
* `hiddenBatched` counts the whole trail rather than the page: it is what the
|
|
23653
|
+
* view's "N hidden" line and its toggle speak for, so it must not shrink as
|
|
23654
|
+
* the reader pages.
|
|
23655
|
+
*/
|
|
23656
|
+
listDerefs(query = {}) {
|
|
23657
|
+
const limit = pageLimit(query.limit, DEFAULT_VAULT_DEREFS_LIMIT);
|
|
23658
|
+
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
23659
|
+
const conditions = [];
|
|
23660
|
+
if (query.includeBatched !== true) {
|
|
23661
|
+
conditions.push(`reason NOT IN ('display', 'view-render')`);
|
|
23662
|
+
}
|
|
23663
|
+
if (cursor !== null) {
|
|
23664
|
+
conditions.push("(at < :cursorAt OR (at = :cursorAt AND id < :cursorId))");
|
|
23665
|
+
}
|
|
23666
|
+
const where = conditions.length === 0 ? "" : `WHERE ${conditions.join(" AND ")}`;
|
|
23667
|
+
const rows = allRows(
|
|
23668
|
+
this.db.prepare(
|
|
23669
|
+
`SELECT id, pointer_id, at, target, reason, outcome, grant_id, pointer_count
|
|
23670
|
+
FROM secret_vault_deref ${where}
|
|
23671
|
+
ORDER BY at DESC, id DESC LIMIT :limit`
|
|
23672
|
+
),
|
|
23673
|
+
bindParams({
|
|
23674
|
+
limit: limit + 1,
|
|
23675
|
+
...cursor === null ? {} : { cursorAt: cursor.startedAtMs, cursorId: cursor.id }
|
|
23676
|
+
})
|
|
23677
|
+
);
|
|
23678
|
+
const hasMore = rows.length > limit;
|
|
23679
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
23680
|
+
const last = page[page.length - 1];
|
|
23681
|
+
const hiddenBatched = query.includeBatched === true ? 0 : countScalar(
|
|
23682
|
+
this.db,
|
|
23683
|
+
`SELECT count(*) AS n FROM secret_vault_deref WHERE reason IN ('display', 'view-render')`
|
|
23684
|
+
);
|
|
23685
|
+
return {
|
|
23686
|
+
items: page.map((r) => ({
|
|
23687
|
+
id: r.id,
|
|
23688
|
+
pointerId: r.pointer_id,
|
|
23689
|
+
at: new Date(r.at).toISOString(),
|
|
23690
|
+
target: r.target,
|
|
23691
|
+
reason: r.reason,
|
|
23692
|
+
outcome: r.outcome,
|
|
23693
|
+
...r.grant_id === null ? {} : { grantId: r.grant_id },
|
|
23694
|
+
pointerCount: r.pointer_count
|
|
23695
|
+
})),
|
|
23696
|
+
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: last.at, id: last.id }) : null,
|
|
23697
|
+
hiddenBatched
|
|
23698
|
+
};
|
|
23699
|
+
}
|
|
23700
|
+
countEntries() {
|
|
23701
|
+
return countScalar(this.db, "SELECT COUNT(*) AS n FROM secret_vault");
|
|
23702
|
+
}
|
|
23703
|
+
/** Values reused on this machine — the reuse list's page-independent total. */
|
|
23704
|
+
countReused() {
|
|
23705
|
+
return countScalar(
|
|
23706
|
+
this.db,
|
|
23707
|
+
`SELECT COUNT(*) AS n FROM secret_vault v WHERE ${REUSED_PREDICATE}`
|
|
23708
|
+
);
|
|
22242
23709
|
}
|
|
22243
23710
|
};
|
|
22244
23711
|
|
|
@@ -22254,7 +23721,9 @@ var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
|
22254
23721
|
var SCAN_COVERAGE = [
|
|
22255
23722
|
{ provider: "claudecode", coverage: 100, supported: true },
|
|
22256
23723
|
{ provider: "cursor", coverage: 0, supported: false },
|
|
22257
|
-
{ provider: "codex", coverage:
|
|
23724
|
+
{ provider: "codex", coverage: 80, supported: true },
|
|
23725
|
+
{ provider: "antigravity", coverage: 60, supported: true },
|
|
23726
|
+
{ provider: "claudeai", coverage: 0, supported: false },
|
|
22258
23727
|
{ provider: "chatgpt", coverage: 0, supported: false },
|
|
22259
23728
|
{ provider: "copilot", coverage: 0, supported: false },
|
|
22260
23729
|
{ provider: "api", coverage: 0, supported: false }
|
|
@@ -22587,7 +24056,7 @@ var SqliteSecurityRepository = class {
|
|
|
22587
24056
|
};
|
|
22588
24057
|
|
|
22589
24058
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22590
|
-
import { randomUUID as
|
|
24059
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
22591
24060
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22592
24061
|
var IN_CHUNK = 500;
|
|
22593
24062
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -22843,7 +24312,7 @@ var SqliteSharesRepository = class {
|
|
|
22843
24312
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
22844
24313
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22845
24314
|
).run({
|
|
22846
|
-
id:
|
|
24315
|
+
id: randomUUID9(),
|
|
22847
24316
|
destinationId,
|
|
22848
24317
|
host: dest.host,
|
|
22849
24318
|
decision,
|
|
@@ -22992,7 +24461,7 @@ var SqliteSharesRepository = class {
|
|
|
22992
24461
|
let destinationId = destIds.get(hit.host);
|
|
22993
24462
|
if (destinationId === void 0) {
|
|
22994
24463
|
destStmt.run({
|
|
22995
|
-
id:
|
|
24464
|
+
id: randomUUID9(),
|
|
22996
24465
|
kind: hit.kind,
|
|
22997
24466
|
name: hit.name,
|
|
22998
24467
|
host: hit.host,
|
|
@@ -23008,7 +24477,7 @@ var SqliteSharesRepository = class {
|
|
|
23008
24477
|
let endpointId = endpointIds.get(endpointKey);
|
|
23009
24478
|
if (endpointId === void 0) {
|
|
23010
24479
|
endpointStmt.run({
|
|
23011
|
-
id:
|
|
24480
|
+
id: randomUUID9(),
|
|
23012
24481
|
destinationId,
|
|
23013
24482
|
method: hit.method,
|
|
23014
24483
|
transport: hit.transport,
|
|
@@ -23021,7 +24490,7 @@ var SqliteSharesRepository = class {
|
|
|
23021
24490
|
endpointIds.set(endpointKey, endpointId);
|
|
23022
24491
|
}
|
|
23023
24492
|
siteStmt.run({
|
|
23024
|
-
id:
|
|
24493
|
+
id: randomUUID9(),
|
|
23025
24494
|
endpointId,
|
|
23026
24495
|
project: input.project,
|
|
23027
24496
|
projectKey: input.projectKey,
|
|
@@ -23386,6 +24855,9 @@ function purgeSampleData(db) {
|
|
|
23386
24855
|
}
|
|
23387
24856
|
|
|
23388
24857
|
// ../../packages/persistence/src/database.ts
|
|
24858
|
+
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
24859
|
+
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
24860
|
+
);
|
|
23389
24861
|
function linkHost(input, hostId) {
|
|
23390
24862
|
return hostId ? { ...input, hostId } : input;
|
|
23391
24863
|
}
|
|
@@ -23407,21 +24879,34 @@ function openWithPragmas(file2) {
|
|
|
23407
24879
|
}
|
|
23408
24880
|
return db;
|
|
23409
24881
|
}
|
|
23410
|
-
function backupLegacyStore(file2) {
|
|
23411
|
-
|
|
23412
|
-
|
|
23413
|
-
|
|
23414
|
-
|
|
23415
|
-
|
|
24882
|
+
function backupLegacyStore(db, file2) {
|
|
24883
|
+
reapStalePartials(file2);
|
|
24884
|
+
const backup = backupPath(file2, "legacy");
|
|
24885
|
+
let snapshotted = false;
|
|
24886
|
+
let snapshotError;
|
|
24887
|
+
try {
|
|
24888
|
+
snapshotStore(db, backup);
|
|
24889
|
+
snapshotted = true;
|
|
24890
|
+
} catch (error51) {
|
|
24891
|
+
snapshotError = error51;
|
|
24892
|
+
} finally {
|
|
24893
|
+
db.close();
|
|
24894
|
+
}
|
|
24895
|
+
if (!snapshotted) {
|
|
24896
|
+
akaWarn(
|
|
24897
|
+
`Could not snapshot the incompatible ${DB_FILENAME} (${String(snapshotError)}); moving the store aside with its sidecars instead.`
|
|
24898
|
+
);
|
|
24899
|
+
moveStoreAside(file2, backup);
|
|
24900
|
+
return backup;
|
|
23416
24901
|
}
|
|
24902
|
+
discardStore(file2, backup);
|
|
23417
24903
|
return backup;
|
|
23418
24904
|
}
|
|
23419
24905
|
function openAndInitialize(file2) {
|
|
23420
24906
|
let db = openWithPragmas(file2);
|
|
23421
24907
|
try {
|
|
23422
24908
|
if (isForeignSqliteLineage(db)) {
|
|
23423
|
-
db
|
|
23424
|
-
const backup = backupLegacyStore(file2);
|
|
24909
|
+
const backup = backupLegacyStore(db, file2);
|
|
23425
24910
|
db = openWithPragmas(file2);
|
|
23426
24911
|
akaWarn(
|
|
23427
24912
|
`Detected an older, incompatible (tenant-bearing) ${DB_FILENAME}; backed it up to ${backup} and created a fresh store.`
|
|
@@ -23437,6 +24922,7 @@ function openAndInitialize(file2) {
|
|
|
23437
24922
|
policies,
|
|
23438
24923
|
installedPacks,
|
|
23439
24924
|
scanLedger: new SqliteScanLedgerRepository(db),
|
|
24925
|
+
secretVault: new SqliteSecretVaultRepository(db),
|
|
23440
24926
|
exceptions: new SqliteExceptionsRepository(db),
|
|
23441
24927
|
resolutions: new SqliteResolutionsRepository(db),
|
|
23442
24928
|
ruleProbeCache: new SqliteRuleProbeCacheRepository(db),
|
|
@@ -23464,7 +24950,7 @@ function openAndInitialize(file2) {
|
|
|
23464
24950
|
}
|
|
23465
24951
|
function openLocalDatabase(dir) {
|
|
23466
24952
|
ensureDataDirSync(dir);
|
|
23467
|
-
const file2 =
|
|
24953
|
+
const file2 = join2(dir, DB_FILENAME);
|
|
23468
24954
|
const {
|
|
23469
24955
|
db,
|
|
23470
24956
|
events,
|
|
@@ -23472,6 +24958,7 @@ function openLocalDatabase(dir) {
|
|
|
23472
24958
|
policies,
|
|
23473
24959
|
installedPacks,
|
|
23474
24960
|
scanLedger,
|
|
24961
|
+
secretVault,
|
|
23475
24962
|
exceptions,
|
|
23476
24963
|
resolutions,
|
|
23477
24964
|
ruleProbeCache,
|
|
@@ -23580,7 +25067,7 @@ function openLocalDatabase(dir) {
|
|
|
23580
25067
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
23581
25068
|
if (!definitionId) continue;
|
|
23582
25069
|
inspectionFindings.insertFinding({
|
|
23583
|
-
id:
|
|
25070
|
+
id: randomUUID10(),
|
|
23584
25071
|
auditEventId: record2.scanEvent.id,
|
|
23585
25072
|
inspectionDefinitionId: definitionId,
|
|
23586
25073
|
span: finding.span,
|
|
@@ -23657,6 +25144,7 @@ function openLocalDatabase(dir) {
|
|
|
23657
25144
|
policies,
|
|
23658
25145
|
installedPacks,
|
|
23659
25146
|
scanLedger,
|
|
25147
|
+
secretVault,
|
|
23660
25148
|
exceptions,
|
|
23661
25149
|
resolutions,
|
|
23662
25150
|
ruleProbeCache,
|
|
@@ -23685,10 +25173,26 @@ function openLocalDatabase(dir) {
|
|
|
23685
25173
|
transaction,
|
|
23686
25174
|
close: () => {
|
|
23687
25175
|
db.close();
|
|
23688
|
-
}
|
|
25176
|
+
},
|
|
25177
|
+
// Last, and a plain value rather than a getter, so `{ ...db }` carries it.
|
|
25178
|
+
[UNSAFE_TEST_ONLY_RAW_HANDLE]: db
|
|
23689
25179
|
};
|
|
23690
25180
|
}
|
|
23691
25181
|
|
|
25182
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
25183
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
25184
|
+
import {
|
|
25185
|
+
closeSync,
|
|
25186
|
+
existsSync as existsSync2,
|
|
25187
|
+
openSync,
|
|
25188
|
+
readFileSync,
|
|
25189
|
+
rmSync as rmSync3,
|
|
25190
|
+
statSync as statSync2,
|
|
25191
|
+
writeFileSync as writeFileSync2
|
|
25192
|
+
} from "fs";
|
|
25193
|
+
import { hostname as hostname3 } from "os";
|
|
25194
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
25195
|
+
|
|
23692
25196
|
// ../../packages/persistence/src/finding-key.ts
|
|
23693
25197
|
import { createHash as createHash3 } from "crypto";
|
|
23694
25198
|
function normalizeFilePath(filePath) {
|
|
@@ -23701,13 +25205,13 @@ function computeFindingKey(input) {
|
|
|
23701
25205
|
|
|
23702
25206
|
// ../../packages/persistence/src/fingerprint.ts
|
|
23703
25207
|
import { createHmac, randomBytes } from "crypto";
|
|
23704
|
-
import { existsSync as
|
|
23705
|
-
import { join as
|
|
25208
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
25209
|
+
import { join as join3 } from "path";
|
|
23706
25210
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23707
|
-
var
|
|
25211
|
+
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
23708
25212
|
var KEY_MATERIAL_BYTES = 32;
|
|
23709
25213
|
function keyFilePath(dataDir2) {
|
|
23710
|
-
return
|
|
25214
|
+
return join3(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
23711
25215
|
}
|
|
23712
25216
|
function parseKeyFile(raw) {
|
|
23713
25217
|
const parsed = JSON.parse(raw);
|
|
@@ -23727,7 +25231,11 @@ function parseKeyFile(raw) {
|
|
|
23727
25231
|
}
|
|
23728
25232
|
return { version: version2, material: bytes };
|
|
23729
25233
|
}
|
|
23730
|
-
var
|
|
25234
|
+
var KEY_VERSION_COLUMNS = {
|
|
25235
|
+
exceptions: "key_version",
|
|
25236
|
+
blocked_detections: "key_version",
|
|
25237
|
+
secret_vault: "fingerprint_key_version"
|
|
25238
|
+
};
|
|
23731
25239
|
var SQLITE_ERROR = 1;
|
|
23732
25240
|
var FLOOR_BUSY_TIMEOUT_MS = 250;
|
|
23733
25241
|
var FloorUnreadableError = class extends Error {
|
|
@@ -23741,17 +25249,17 @@ var FloorUnreadableError = class extends Error {
|
|
|
23741
25249
|
}
|
|
23742
25250
|
};
|
|
23743
25251
|
function storedKeyVersionFloor(dataDir2) {
|
|
23744
|
-
const file2 =
|
|
23745
|
-
if (!
|
|
25252
|
+
const file2 = join3(dataDir2, DB_FILENAME);
|
|
25253
|
+
if (!existsSync3(file2)) return 0;
|
|
23746
25254
|
let db;
|
|
23747
25255
|
try {
|
|
23748
25256
|
db = new DatabaseSync2(file2, { readOnly: true });
|
|
23749
25257
|
db.exec(`PRAGMA busy_timeout = ${String(FLOOR_BUSY_TIMEOUT_MS)}`);
|
|
23750
25258
|
let floor = 0;
|
|
23751
|
-
for (const table2 of
|
|
25259
|
+
for (const [table2, column] of Object.entries(KEY_VERSION_COLUMNS)) {
|
|
23752
25260
|
try {
|
|
23753
25261
|
const row = getRow(
|
|
23754
|
-
db.prepare(`SELECT MAX(
|
|
25262
|
+
db.prepare(`SELECT MAX(${column}) AS v FROM ${table2}`)
|
|
23755
25263
|
);
|
|
23756
25264
|
floor = Math.max(floor, row?.v ?? 0);
|
|
23757
25265
|
} catch (err) {
|
|
@@ -23767,18 +25275,36 @@ function storedKeyVersionFloor(dataDir2) {
|
|
|
23767
25275
|
db?.close();
|
|
23768
25276
|
}
|
|
23769
25277
|
}
|
|
23770
|
-
function
|
|
25278
|
+
function serializeKey(key) {
|
|
25279
|
+
return JSON.stringify({ version: key.version, material: key.material.toString("base64") });
|
|
25280
|
+
}
|
|
25281
|
+
function createKeyFile(dataDir2, key) {
|
|
23771
25282
|
ensureDataDirSync(dataDir2);
|
|
23772
25283
|
const file2 = keyFilePath(dataDir2);
|
|
23773
|
-
|
|
23774
|
-
|
|
23775
|
-
|
|
23776
|
-
|
|
25284
|
+
if (createOwnerOnlyFileSync(file2, `${serializeKey(key)}
|
|
25285
|
+
`)) return key;
|
|
25286
|
+
const winner = readFingerprintKey(dataDir2);
|
|
25287
|
+
if (winner) {
|
|
25288
|
+
tightenFile(file2);
|
|
25289
|
+
return winner;
|
|
25290
|
+
}
|
|
25291
|
+
const occupant = classifyOccupant(file2);
|
|
25292
|
+
throw new KeyUnclaimableError(occupantMessage(file2, occupant.kind), occupant.cause);
|
|
25293
|
+
}
|
|
25294
|
+
function occupantMessage(file2, kind) {
|
|
25295
|
+
switch (kind) {
|
|
25296
|
+
case "symlink":
|
|
25297
|
+
return `exception key file is a symlink (${file2}); remove it so a key can be created`;
|
|
25298
|
+
case "gone":
|
|
25299
|
+
return "exception key file was removed while it was being created";
|
|
25300
|
+
case "unknown":
|
|
25301
|
+
return `exception key file (${file2}) is occupied but cannot be inspected; check the permissions on its directory`;
|
|
25302
|
+
}
|
|
23777
25303
|
}
|
|
23778
25304
|
function readFingerprintKey(dataDir2) {
|
|
23779
25305
|
let raw;
|
|
23780
25306
|
try {
|
|
23781
|
-
raw =
|
|
25307
|
+
raw = readFileSync2(keyFilePath(dataDir2), "utf8");
|
|
23782
25308
|
} catch (err) {
|
|
23783
25309
|
if (err.code === "ENOENT") return null;
|
|
23784
25310
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -23791,7 +25317,7 @@ function loadOrCreateFingerprintKey(dataDir2) {
|
|
|
23791
25317
|
tightenFile(keyFilePath(dataDir2));
|
|
23792
25318
|
return existing;
|
|
23793
25319
|
}
|
|
23794
|
-
return
|
|
25320
|
+
return createKeyFile(dataDir2, {
|
|
23795
25321
|
version: storedKeyVersionFloor(dataDir2) + 1,
|
|
23796
25322
|
material: randomBytes(KEY_MATERIAL_BYTES)
|
|
23797
25323
|
});
|
|
@@ -23804,18 +25330,18 @@ function fingerprintValue(key, raw) {
|
|
|
23804
25330
|
import { renameSync as renameSync3 } from "fs";
|
|
23805
25331
|
import { mkdir } from "fs/promises";
|
|
23806
25332
|
import { homedir } from "os";
|
|
23807
|
-
import { join as
|
|
25333
|
+
import { join as join4 } from "path";
|
|
23808
25334
|
function defaultDataDir() {
|
|
23809
|
-
return
|
|
25335
|
+
return join4(homedir(), ".aka");
|
|
23810
25336
|
}
|
|
23811
25337
|
function settingsDir(base = defaultDataDir()) {
|
|
23812
|
-
return
|
|
25338
|
+
return join4(base, "settings");
|
|
23813
25339
|
}
|
|
23814
25340
|
function dataDir(base = defaultDataDir()) {
|
|
23815
|
-
return
|
|
25341
|
+
return join4(base, "data");
|
|
23816
25342
|
}
|
|
23817
25343
|
function dbPath(base = defaultDataDir()) {
|
|
23818
|
-
return
|
|
25344
|
+
return join4(dataDir(base), "aka.db");
|
|
23819
25345
|
}
|
|
23820
25346
|
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
23821
25347
|
ensureDataDirSync(dir);
|
|
@@ -23828,8 +25354,8 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23828
25354
|
for (const { name, dest } of moves) {
|
|
23829
25355
|
try {
|
|
23830
25356
|
ensureDataDirSync(dest);
|
|
23831
|
-
const moved =
|
|
23832
|
-
renameSync3(
|
|
25357
|
+
const moved = join4(dest, name);
|
|
25358
|
+
renameSync3(join4(base, name), moved);
|
|
23833
25359
|
tightenFile(moved);
|
|
23834
25360
|
} catch {
|
|
23835
25361
|
}
|
|
@@ -23837,10 +25363,11 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
23837
25363
|
}
|
|
23838
25364
|
|
|
23839
25365
|
// ../../packages/persistence/src/settings.ts
|
|
23840
|
-
import { readFileSync as
|
|
23841
|
-
import { join as
|
|
25366
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
25367
|
+
import { join as join5 } from "path";
|
|
25368
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
23842
25369
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
23843
|
-
const record2 = readJson(
|
|
25370
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
23844
25371
|
if (!record2) return defaultWorkspaceSettings();
|
|
23845
25372
|
try {
|
|
23846
25373
|
return WorkspaceSettings.parse(record2);
|
|
@@ -23851,23 +25378,49 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
23851
25378
|
function readJson(file2) {
|
|
23852
25379
|
let text;
|
|
23853
25380
|
try {
|
|
23854
|
-
text =
|
|
25381
|
+
text = readFileSync3(file2, "utf8");
|
|
23855
25382
|
} catch {
|
|
23856
25383
|
return null;
|
|
23857
25384
|
}
|
|
23858
25385
|
return parseJsonObject(text) ?? null;
|
|
23859
25386
|
}
|
|
23860
25387
|
|
|
25388
|
+
// ../../packages/persistence/src/vault/crypto.ts
|
|
25389
|
+
import {
|
|
25390
|
+
createCipheriv,
|
|
25391
|
+
createDecipheriv,
|
|
25392
|
+
createHmac as createHmac2,
|
|
25393
|
+
hkdfSync,
|
|
25394
|
+
timingSafeEqual
|
|
25395
|
+
} from "crypto";
|
|
25396
|
+
|
|
25397
|
+
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25398
|
+
import { execFileSync } from "child_process";
|
|
25399
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
25400
|
+
import {
|
|
25401
|
+
chmodSync as chmodSync2,
|
|
25402
|
+
mkdirSync as mkdirSync2,
|
|
25403
|
+
readFileSync as readFileSync4,
|
|
25404
|
+
renameSync as renameSync4,
|
|
25405
|
+
rmSync as rmSync4,
|
|
25406
|
+
statSync as statSync3,
|
|
25407
|
+
writeFileSync as writeFileSync3
|
|
25408
|
+
} from "fs";
|
|
25409
|
+
import { join as join6 } from "path";
|
|
25410
|
+
|
|
25411
|
+
// ../../packages/persistence/src/vault/vault.ts
|
|
25412
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
25413
|
+
|
|
23861
25414
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
23862
|
-
import { existsSync as
|
|
23863
|
-
import { join as
|
|
25415
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
25416
|
+
import { join as join7 } from "path";
|
|
23864
25417
|
var MARKER = "warn-era-capped";
|
|
23865
25418
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
23866
25419
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
23867
|
-
const marker =
|
|
23868
|
-
if (
|
|
25420
|
+
const marker = join7(dataDir2, MARKER);
|
|
25421
|
+
if (existsSync4(marker)) return { capped: 0, skipped: "already-run" };
|
|
23869
25422
|
const capped = db.policies.capCategoryActions();
|
|
23870
|
-
|
|
25423
|
+
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
23871
25424
|
`, { mode: DATA_FILE_MODE });
|
|
23872
25425
|
return { capped };
|
|
23873
25426
|
}
|
|
@@ -23921,11 +25474,11 @@ function resolveProvider() {
|
|
|
23921
25474
|
}
|
|
23922
25475
|
|
|
23923
25476
|
// ../../packages/plugin-sdk/src/config.ts
|
|
23924
|
-
function loadConfig(base = defaultDataDir()) {
|
|
25477
|
+
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
23925
25478
|
try {
|
|
23926
25479
|
ensureLayoutDirSync(base);
|
|
23927
|
-
const settingsFile =
|
|
23928
|
-
if (
|
|
25480
|
+
const settingsFile = join8(settingsDir(base), "settings.json");
|
|
25481
|
+
if (existsSync5(settingsFile)) tightenFile(settingsFile);
|
|
23929
25482
|
} catch {
|
|
23930
25483
|
}
|
|
23931
25484
|
migrateLegacyLayout(base);
|
|
@@ -23936,21 +25489,21 @@ function loadConfig(base = defaultDataDir()) {
|
|
|
23936
25489
|
dbPath: dbPath(base),
|
|
23937
25490
|
settingsDir: settingsDir(base),
|
|
23938
25491
|
onboarded: settings.onboardedAt != null,
|
|
23939
|
-
provider: resolveProviderSafe()
|
|
25492
|
+
provider: resolveProviderSafe(resolveProviderFn)
|
|
23940
25493
|
};
|
|
23941
25494
|
}
|
|
23942
|
-
function resolveProviderSafe() {
|
|
25495
|
+
function resolveProviderSafe(resolveProviderFn) {
|
|
23943
25496
|
try {
|
|
23944
|
-
return
|
|
25497
|
+
return resolveProviderFn();
|
|
23945
25498
|
} catch {
|
|
23946
25499
|
return { provider: "anthropic" };
|
|
23947
25500
|
}
|
|
23948
25501
|
}
|
|
23949
25502
|
|
|
23950
25503
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
23951
|
-
import { readdirSync, readFileSync as
|
|
25504
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync5 } from "fs";
|
|
23952
25505
|
import { homedir as homedir2 } from "os";
|
|
23953
|
-
import { basename as
|
|
25506
|
+
import { basename as basename3, join as join10 } from "path";
|
|
23954
25507
|
|
|
23955
25508
|
// ../../packages/detections/src/egress/registry.ts
|
|
23956
25509
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -24940,11 +26493,11 @@ var EXACT_KIND_BY_BASENAME = {
|
|
|
24940
26493
|
"composer.json": "composer.json",
|
|
24941
26494
|
"packages.config": "packages.config"
|
|
24942
26495
|
};
|
|
24943
|
-
function manifestKindOf(
|
|
24944
|
-
if (LOCKFILE_BASENAMES.has(
|
|
24945
|
-
const exact = Object.hasOwn(EXACT_KIND_BY_BASENAME,
|
|
26496
|
+
function manifestKindOf(basename7) {
|
|
26497
|
+
if (LOCKFILE_BASENAMES.has(basename7)) return null;
|
|
26498
|
+
const exact = Object.hasOwn(EXACT_KIND_BY_BASENAME, basename7) ? EXACT_KIND_BY_BASENAME[basename7] : void 0;
|
|
24946
26499
|
if (exact !== void 0) return exact;
|
|
24947
|
-
if (
|
|
26500
|
+
if (basename7.endsWith(".csproj")) return "csproj";
|
|
24948
26501
|
return null;
|
|
24949
26502
|
}
|
|
24950
26503
|
function extractManifestSdks(text, kind) {
|
|
@@ -25053,9 +26606,9 @@ function extractGoMod(text) {
|
|
|
25053
26606
|
let blockKeyword = null;
|
|
25054
26607
|
eachLine(text, (rawLine, lineNumber) => {
|
|
25055
26608
|
if (blockKeyword === null) {
|
|
25056
|
-
const
|
|
25057
|
-
if (
|
|
25058
|
-
blockKeyword =
|
|
26609
|
+
const open2 = GO_BLOCK_OPEN.exec(rawLine)?.[1];
|
|
26610
|
+
if (open2 !== void 0) {
|
|
26611
|
+
blockKeyword = open2;
|
|
25059
26612
|
return;
|
|
25060
26613
|
}
|
|
25061
26614
|
const path = GO_REQUIRE_SINGLE_LINE.exec(rawLine)?.[1];
|
|
@@ -25573,12 +27126,12 @@ function redact(text, findings) {
|
|
|
25573
27126
|
const regions = [];
|
|
25574
27127
|
for (const f of sorted) {
|
|
25575
27128
|
const rank = SEVERITY_RANK2[f.severity];
|
|
25576
|
-
const
|
|
25577
|
-
if (
|
|
25578
|
-
|
|
25579
|
-
if (rank >
|
|
25580
|
-
|
|
25581
|
-
|
|
27129
|
+
const open2 = regions[regions.length - 1];
|
|
27130
|
+
if (open2 && f.span.start < open2.end) {
|
|
27131
|
+
open2.end = Math.max(open2.end, f.span.end);
|
|
27132
|
+
if (rank > open2.rank) {
|
|
27133
|
+
open2.rank = rank;
|
|
27134
|
+
open2.category = f.category;
|
|
25582
27135
|
}
|
|
25583
27136
|
} else {
|
|
25584
27137
|
regions.push({ start: f.span.start, end: f.span.end, category: f.category, rank });
|
|
@@ -25607,6 +27160,24 @@ function maskMatch(raw) {
|
|
|
25607
27160
|
return `${raw.charAt(0)}${"*".repeat(6)}${raw.charAt(raw.length - 1)}`;
|
|
25608
27161
|
}
|
|
25609
27162
|
|
|
27163
|
+
// ../../packages/detections/src/pointer-shield.ts
|
|
27164
|
+
function shieldPointers(text) {
|
|
27165
|
+
const spans = [];
|
|
27166
|
+
let out = null;
|
|
27167
|
+
for (const match of text.matchAll(pointerTokenScanner())) {
|
|
27168
|
+
spans.push({ start: match.index, end: match.index + match[0].length });
|
|
27169
|
+
out ??= text;
|
|
27170
|
+
out = out.slice(0, match.index) + " ".repeat(match[0].length) + out.slice(match.index + match[0].length);
|
|
27171
|
+
}
|
|
27172
|
+
return { text: out ?? text, spans };
|
|
27173
|
+
}
|
|
27174
|
+
function dropShieldedFindings(findings, spans) {
|
|
27175
|
+
if (spans.length === 0) return findings;
|
|
27176
|
+
return findings.filter(
|
|
27177
|
+
(finding) => !spans.some((s) => finding.span.start < s.end && finding.span.end > s.start)
|
|
27178
|
+
);
|
|
27179
|
+
}
|
|
27180
|
+
|
|
25610
27181
|
// ../../packages/detections/src/posture/config-posture.ts
|
|
25611
27182
|
var RULE_VERSION = "1";
|
|
25612
27183
|
var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -27424,7 +28995,7 @@ var gcp_service_account_default = {
|
|
|
27424
28995
|
severity: "critical",
|
|
27425
28996
|
matcher: {
|
|
27426
28997
|
type: "regex",
|
|
27427
|
-
pattern: "
|
|
28998
|
+
pattern: "(?<![a-z0-9-])[a-z0-9-]{3,}@[a-z0-9][a-z0-9-]{2,60}\\.iam\\.gserviceaccount\\.com\\b",
|
|
27428
28999
|
flags: "g"
|
|
27429
29000
|
},
|
|
27430
29001
|
examples: [
|
|
@@ -27803,8 +29374,8 @@ function bundledDetections() {
|
|
|
27803
29374
|
}
|
|
27804
29375
|
|
|
27805
29376
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
27806
|
-
import { existsSync as
|
|
27807
|
-
import { basename, dirname, isAbsolute, join as
|
|
29377
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
29378
|
+
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
27808
29379
|
function resolveRepoIdentity(cwd) {
|
|
27809
29380
|
try {
|
|
27810
29381
|
const root = findGitRoot(cwd);
|
|
@@ -27817,7 +29388,7 @@ function resolveRepoIdentity(cwd) {
|
|
|
27817
29388
|
// win32) so the persistence layer's `/`-separated checkout-path patterns
|
|
27818
29389
|
// (the ghost sweep + the read-side worktree filter) match it as written.
|
|
27819
29390
|
url: url2 ?? headRoot.split(sep2).join("/"),
|
|
27820
|
-
name: (url2 ? slugFromUrl(url2) : void 0) ??
|
|
29391
|
+
name: (url2 ? slugFromUrl(url2) : void 0) ?? basename2(headRoot)
|
|
27821
29392
|
};
|
|
27822
29393
|
} catch {
|
|
27823
29394
|
return void 0;
|
|
@@ -27833,36 +29404,36 @@ function resolveWorktreeRoot(cwd) {
|
|
|
27833
29404
|
function findGitRoot(start) {
|
|
27834
29405
|
let dir = start;
|
|
27835
29406
|
for (; ; ) {
|
|
27836
|
-
if (
|
|
27837
|
-
const parent =
|
|
29407
|
+
if (existsSync6(join9(dir, ".git"))) return dir;
|
|
29408
|
+
const parent = dirname2(dir);
|
|
27838
29409
|
if (parent === dir) return void 0;
|
|
27839
29410
|
dir = parent;
|
|
27840
29411
|
}
|
|
27841
29412
|
}
|
|
27842
29413
|
function resolveGitContext(root) {
|
|
27843
|
-
const dotGit =
|
|
29414
|
+
const dotGit = join9(root, ".git");
|
|
27844
29415
|
try {
|
|
27845
|
-
if (
|
|
27846
|
-
return { configPath:
|
|
29416
|
+
if (statSync4(dotGit).isDirectory()) {
|
|
29417
|
+
return { configPath: join9(dotGit, "config"), headRoot: root };
|
|
27847
29418
|
}
|
|
27848
29419
|
} catch {
|
|
27849
29420
|
return void 0;
|
|
27850
29421
|
}
|
|
27851
29422
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
27852
29423
|
if (!target) return void 0;
|
|
27853
|
-
const gitdir = isAbsolute(target) ? target :
|
|
27854
|
-
if (
|
|
27855
|
-
return { configPath:
|
|
29424
|
+
const gitdir = isAbsolute(target) ? target : join9(root, target);
|
|
29425
|
+
if (existsSync6(join9(gitdir, "config"))) {
|
|
29426
|
+
return { configPath: join9(gitdir, "config"), headRoot: root };
|
|
27856
29427
|
}
|
|
27857
|
-
const commonRaw = safeRead(
|
|
29428
|
+
const commonRaw = safeRead(join9(gitdir, "commondir"))?.trim();
|
|
27858
29429
|
if (!commonRaw) return void 0;
|
|
27859
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
27860
|
-
const headRoot =
|
|
27861
|
-
return { configPath:
|
|
29430
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join9(gitdir, commonRaw);
|
|
29431
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
|
|
29432
|
+
return { configPath: join9(commonGitDir, "config"), headRoot };
|
|
27862
29433
|
}
|
|
27863
29434
|
function safeRead(path) {
|
|
27864
29435
|
try {
|
|
27865
|
-
return
|
|
29436
|
+
return readFileSync5(path, "utf8");
|
|
27866
29437
|
} catch {
|
|
27867
29438
|
return void 0;
|
|
27868
29439
|
}
|
|
@@ -27900,13 +29471,13 @@ function slugFromUrl(url2) {
|
|
|
27900
29471
|
}
|
|
27901
29472
|
|
|
27902
29473
|
// ../../packages/plugin-sdk/src/events.ts
|
|
27903
|
-
import { createHash as createHash4, randomUUID as
|
|
29474
|
+
import { createHash as createHash4, randomUUID as randomUUID13 } from "crypto";
|
|
27904
29475
|
function contentHashOf(text) {
|
|
27905
29476
|
return createHash4("sha256").update(text).digest("hex");
|
|
27906
29477
|
}
|
|
27907
29478
|
function buildIngestEvent(input) {
|
|
27908
29479
|
return {
|
|
27909
|
-
id:
|
|
29480
|
+
id: randomUUID13(),
|
|
27910
29481
|
sourceTool: input.sourceTool,
|
|
27911
29482
|
kind: input.kind,
|
|
27912
29483
|
occurredAt: input.occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27917,21 +29488,473 @@ function buildIngestEvent(input) {
|
|
|
27917
29488
|
// SDK boot in the fail-open hook path). Preserve any id the caller already set.
|
|
27918
29489
|
metadata: {
|
|
27919
29490
|
...input.metadata,
|
|
27920
|
-
correlationId: input.metadata?.correlationId ??
|
|
29491
|
+
correlationId: input.metadata?.correlationId ?? randomUUID13()
|
|
29492
|
+
}
|
|
29493
|
+
};
|
|
29494
|
+
}
|
|
29495
|
+
|
|
29496
|
+
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
29497
|
+
import { existsSync as existsSync7 } from "fs";
|
|
29498
|
+
import { fileURLToPath } from "url";
|
|
29499
|
+
import { Worker } from "worker_threads";
|
|
29500
|
+
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
29501
|
+
var ISOLATED_PROBE_BUDGET_MS = 1e3;
|
|
29502
|
+
var ISOLATED_START_BUDGET_MS = 5e3;
|
|
29503
|
+
var ATTRIBUTION_MIN_RULE_MS = 500;
|
|
29504
|
+
var ATTRIBUTION_MIN_SHARE = 0.5;
|
|
29505
|
+
var resolvedWorkerUrl;
|
|
29506
|
+
function resolveWorkerUrl() {
|
|
29507
|
+
if (resolvedWorkerUrl !== void 0) return resolvedWorkerUrl ?? void 0;
|
|
29508
|
+
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
29509
|
+
const candidate = new URL(name, import.meta.url);
|
|
29510
|
+
try {
|
|
29511
|
+
if (existsSync7(fileURLToPath(candidate))) {
|
|
29512
|
+
resolvedWorkerUrl = candidate;
|
|
29513
|
+
return candidate;
|
|
29514
|
+
}
|
|
29515
|
+
} catch {
|
|
29516
|
+
}
|
|
29517
|
+
}
|
|
29518
|
+
resolvedWorkerUrl = null;
|
|
29519
|
+
return void 0;
|
|
29520
|
+
}
|
|
29521
|
+
function messageOf(error51) {
|
|
29522
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
29523
|
+
}
|
|
29524
|
+
function createIsolatedScanner(data, opts = {}) {
|
|
29525
|
+
const budgetMs = opts.budgetMs ?? ISOLATED_SCAN_BUDGET_MS;
|
|
29526
|
+
const probeBudgetMs = opts.probeBudgetMs ?? ISOLATED_PROBE_BUDGET_MS;
|
|
29527
|
+
const startBudgetMs = opts.startBudgetMs ?? ISOLATED_START_BUDGET_MS;
|
|
29528
|
+
const minAttributionMs = opts.minAttributionMs ?? ATTRIBUTION_MIN_RULE_MS;
|
|
29529
|
+
let worker;
|
|
29530
|
+
let readyWorker;
|
|
29531
|
+
let broken;
|
|
29532
|
+
let closed = false;
|
|
29533
|
+
let nextJobId = 1;
|
|
29534
|
+
let pending;
|
|
29535
|
+
const terminating = /* @__PURE__ */ new Set();
|
|
29536
|
+
let chain = Promise.resolve();
|
|
29537
|
+
function clearTimers(job) {
|
|
29538
|
+
if (job.startupTimer !== void 0) clearTimeout(job.startupTimer);
|
|
29539
|
+
if (job.timer !== void 0) clearTimeout(job.timer);
|
|
29540
|
+
}
|
|
29541
|
+
function take() {
|
|
29542
|
+
const job = pending;
|
|
29543
|
+
if (!job) return void 0;
|
|
29544
|
+
pending = void 0;
|
|
29545
|
+
clearTimers(job);
|
|
29546
|
+
worker?.unref();
|
|
29547
|
+
return job;
|
|
29548
|
+
}
|
|
29549
|
+
function failPending(outcome) {
|
|
29550
|
+
take()?.fail(outcome);
|
|
29551
|
+
}
|
|
29552
|
+
function kill(dead) {
|
|
29553
|
+
if (worker === dead) worker = void 0;
|
|
29554
|
+
if (readyWorker === dead) readyWorker = void 0;
|
|
29555
|
+
const done = dead.terminate().catch(() => void 0);
|
|
29556
|
+
terminating.add(done);
|
|
29557
|
+
void done.finally(() => terminating.delete(done));
|
|
29558
|
+
}
|
|
29559
|
+
function onDeadline(job) {
|
|
29560
|
+
if (pending !== job) return;
|
|
29561
|
+
const now = performance.now();
|
|
29562
|
+
const runningMs = now - job.progressAt;
|
|
29563
|
+
const elapsedMs = now - job.startedAt;
|
|
29564
|
+
const blamed = job.progressIndex >= 0 && runningMs >= minAttributionMs && runningMs >= elapsedMs * ATTRIBUTION_MIN_SHARE;
|
|
29565
|
+
const culpritIndex = blamed ? job.progressIndex : void 0;
|
|
29566
|
+
kill(job.worker);
|
|
29567
|
+
failPending({ status: "timeout", culpritIndex, elapsedMs });
|
|
29568
|
+
}
|
|
29569
|
+
function ensureWorker() {
|
|
29570
|
+
if (worker) return worker;
|
|
29571
|
+
const url2 = opts.workerUrl ?? resolveWorkerUrl();
|
|
29572
|
+
if (!url2) {
|
|
29573
|
+
return {
|
|
29574
|
+
error: "the scan worker script was not found next to this bundle"
|
|
29575
|
+
};
|
|
29576
|
+
}
|
|
29577
|
+
let started;
|
|
29578
|
+
try {
|
|
29579
|
+
started = new Worker(url2, { workerData: data });
|
|
29580
|
+
} catch (error51) {
|
|
29581
|
+
return { error: `could not start the scan worker: ${messageOf(error51)}` };
|
|
29582
|
+
}
|
|
29583
|
+
opts.onWorkerStart?.(started.threadId);
|
|
29584
|
+
started.on("message", (message) => {
|
|
29585
|
+
if (worker !== started) return;
|
|
29586
|
+
if (message.kind === "ready") {
|
|
29587
|
+
readyWorker = started;
|
|
29588
|
+
if (pending?.worker === started) beginDeadline(pending);
|
|
29589
|
+
return;
|
|
29590
|
+
}
|
|
29591
|
+
if (message.kind === "progress") {
|
|
29592
|
+
if (pending?.worker === started) {
|
|
29593
|
+
pending.progressIndex = message.index;
|
|
29594
|
+
pending.progressAt = performance.now();
|
|
29595
|
+
}
|
|
29596
|
+
return;
|
|
29597
|
+
}
|
|
29598
|
+
if (pending?.id !== message.id) return;
|
|
29599
|
+
if (message.kind === "failed") {
|
|
29600
|
+
failPending({
|
|
29601
|
+
status: "unavailable",
|
|
29602
|
+
reason: `the scan worker failed: ${message.message}`
|
|
29603
|
+
});
|
|
29604
|
+
return;
|
|
29605
|
+
}
|
|
29606
|
+
const job = take();
|
|
29607
|
+
if (job && !job.reply(message)) {
|
|
29608
|
+
job.fail({ status: "unavailable", reason: "the scan worker answered the wrong job" });
|
|
29609
|
+
}
|
|
29610
|
+
});
|
|
29611
|
+
started.on("error", (error51) => {
|
|
29612
|
+
if (worker !== started) return;
|
|
29613
|
+
broken = messageOf(error51);
|
|
29614
|
+
worker = void 0;
|
|
29615
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29616
|
+
failPending({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29617
|
+
});
|
|
29618
|
+
started.on("exit", () => {
|
|
29619
|
+
if (worker !== started) return;
|
|
29620
|
+
broken ??= "the scan worker exited before answering";
|
|
29621
|
+
worker = void 0;
|
|
29622
|
+
if (readyWorker === started) readyWorker = void 0;
|
|
29623
|
+
failPending({ status: "unavailable", reason: "the scan worker exited before answering" });
|
|
29624
|
+
});
|
|
29625
|
+
started.unref();
|
|
29626
|
+
worker = started;
|
|
29627
|
+
return started;
|
|
29628
|
+
}
|
|
29629
|
+
function beginDeadline(job) {
|
|
29630
|
+
if (job.startupTimer !== void 0) {
|
|
29631
|
+
clearTimeout(job.startupTimer);
|
|
29632
|
+
job.startupTimer = void 0;
|
|
29633
|
+
}
|
|
29634
|
+
if (job.timer !== void 0) return;
|
|
29635
|
+
job.startedAt = performance.now();
|
|
29636
|
+
job.progressAt = job.startedAt;
|
|
29637
|
+
job.timer = setTimeout(() => {
|
|
29638
|
+
onDeadline(job);
|
|
29639
|
+
}, job.budgetMs);
|
|
29640
|
+
}
|
|
29641
|
+
function runOne(spec, fail) {
|
|
29642
|
+
if (closed) {
|
|
29643
|
+
fail({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29644
|
+
return;
|
|
29645
|
+
}
|
|
29646
|
+
if (broken !== void 0) {
|
|
29647
|
+
fail({ status: "unavailable", reason: `the scan worker crashed: ${broken}` });
|
|
29648
|
+
return;
|
|
29649
|
+
}
|
|
29650
|
+
const started = ensureWorker();
|
|
29651
|
+
if (!(started instanceof Worker)) {
|
|
29652
|
+
broken = started.error;
|
|
29653
|
+
fail({ status: "unavailable", reason: started.error });
|
|
29654
|
+
return;
|
|
29655
|
+
}
|
|
29656
|
+
const id = nextJobId++;
|
|
29657
|
+
const now = performance.now();
|
|
29658
|
+
const job = {
|
|
29659
|
+
id,
|
|
29660
|
+
worker: started,
|
|
29661
|
+
budgetMs: spec.budgetMs,
|
|
29662
|
+
startedAt: now,
|
|
29663
|
+
progressIndex: -1,
|
|
29664
|
+
progressAt: now,
|
|
29665
|
+
startupTimer: void 0,
|
|
29666
|
+
timer: void 0,
|
|
29667
|
+
reply: spec.reply,
|
|
29668
|
+
fail
|
|
29669
|
+
};
|
|
29670
|
+
pending = job;
|
|
29671
|
+
started.ref();
|
|
29672
|
+
if (readyWorker === started) {
|
|
29673
|
+
beginDeadline(job);
|
|
29674
|
+
} else {
|
|
29675
|
+
job.startupTimer = setTimeout(() => {
|
|
29676
|
+
if (pending !== job) return;
|
|
29677
|
+
kill(job.worker);
|
|
29678
|
+
failPending({
|
|
29679
|
+
status: "unavailable",
|
|
29680
|
+
reason: `the scan worker did not start within ${String(startBudgetMs)}ms`
|
|
29681
|
+
});
|
|
29682
|
+
}, startBudgetMs);
|
|
29683
|
+
}
|
|
29684
|
+
try {
|
|
29685
|
+
started.postMessage(spec.build(id));
|
|
29686
|
+
} catch (error51) {
|
|
29687
|
+
failPending({
|
|
29688
|
+
// The thread went away between the ref and the post.
|
|
29689
|
+
status: "unavailable",
|
|
29690
|
+
reason: `could not reach the scan worker: ${messageOf(error51)}`
|
|
29691
|
+
});
|
|
29692
|
+
}
|
|
29693
|
+
}
|
|
29694
|
+
function enqueue(spec) {
|
|
29695
|
+
const next = chain.then(
|
|
29696
|
+
() => new Promise((resolve) => {
|
|
29697
|
+
spec(resolve);
|
|
29698
|
+
})
|
|
29699
|
+
);
|
|
29700
|
+
chain = next.then(
|
|
29701
|
+
() => void 0,
|
|
29702
|
+
() => void 0
|
|
29703
|
+
);
|
|
29704
|
+
return next;
|
|
29705
|
+
}
|
|
29706
|
+
return {
|
|
29707
|
+
scan(text, context, scanOpts) {
|
|
29708
|
+
return enqueue((resolve) => {
|
|
29709
|
+
runOne(
|
|
29710
|
+
{
|
|
29711
|
+
budgetMs,
|
|
29712
|
+
build: (id) => ({
|
|
29713
|
+
kind: "scan",
|
|
29714
|
+
id,
|
|
29715
|
+
text,
|
|
29716
|
+
filePath: context?.filePath,
|
|
29717
|
+
attribute: scanOpts?.attribute === true
|
|
29718
|
+
}),
|
|
29719
|
+
reply: (message) => {
|
|
29720
|
+
if (message.kind !== "result") return false;
|
|
29721
|
+
resolve({ status: "ok", findings: message.findings });
|
|
29722
|
+
return true;
|
|
29723
|
+
}
|
|
29724
|
+
},
|
|
29725
|
+
resolve
|
|
29726
|
+
);
|
|
29727
|
+
});
|
|
29728
|
+
},
|
|
29729
|
+
probe(rule) {
|
|
29730
|
+
return enqueue((resolve) => {
|
|
29731
|
+
runOne(
|
|
29732
|
+
{
|
|
29733
|
+
budgetMs: probeBudgetMs,
|
|
29734
|
+
build: (id) => ({ kind: "probe", id, rule }),
|
|
29735
|
+
reply: (message) => {
|
|
29736
|
+
if (message.kind !== "probed") return false;
|
|
29737
|
+
resolve({ status: "ok", safe: message.safe, worstMs: message.worstMs });
|
|
29738
|
+
return true;
|
|
29739
|
+
}
|
|
29740
|
+
},
|
|
29741
|
+
resolve
|
|
29742
|
+
);
|
|
29743
|
+
});
|
|
29744
|
+
},
|
|
29745
|
+
async close() {
|
|
29746
|
+
closed = true;
|
|
29747
|
+
const live = worker;
|
|
29748
|
+
worker = void 0;
|
|
29749
|
+
readyWorker = void 0;
|
|
29750
|
+
failPending({ status: "unavailable", reason: "the scan worker is closed" });
|
|
29751
|
+
if (live) kill(live);
|
|
29752
|
+
await Promise.all([...terminating]);
|
|
29753
|
+
}
|
|
29754
|
+
};
|
|
29755
|
+
}
|
|
29756
|
+
|
|
29757
|
+
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
29758
|
+
var PASS_BUDGET_MS = 2e3;
|
|
29759
|
+
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
29760
|
+
function ruleProbeKey(rule) {
|
|
29761
|
+
if (rule.matcher.type !== "regex") return void 0;
|
|
29762
|
+
return contentHashOf(`${rule.matcher.pattern} ${rule.matcher.flags}`);
|
|
29763
|
+
}
|
|
29764
|
+
function warn(rule, verb, detail, recoverable) {
|
|
29765
|
+
const hint = recoverable ? ` (${UNQUARANTINE_HINT})` : "";
|
|
29766
|
+
process.stderr.write(`[aka] ${verb} rule "${rule.id}": ${detail}${hint}
|
|
29767
|
+
`);
|
|
29768
|
+
}
|
|
29769
|
+
function warnQuarantined(rule, worstMs, cached2) {
|
|
29770
|
+
warn(
|
|
29771
|
+
rule,
|
|
29772
|
+
"quarantined",
|
|
29773
|
+
Number.isFinite(worstMs) ? `regex matcher exceeded the ReDoS timing budget (${worstMs.toFixed(1)}ms); excluded from this scan.` : "the timing battery failed while measuring its regex matcher; excluded from this scan.",
|
|
29774
|
+
cached2
|
|
29775
|
+
);
|
|
29776
|
+
}
|
|
29777
|
+
function warnUnmeasured(rule) {
|
|
29778
|
+
warn(
|
|
29779
|
+
rule,
|
|
29780
|
+
"skipped",
|
|
29781
|
+
"the timing pre-flight ran out of time before this rule could be measured; excluded for the rest of this run, and measured again next time.",
|
|
29782
|
+
false
|
|
29783
|
+
);
|
|
29784
|
+
}
|
|
29785
|
+
function warnUnmeasurable(reason, count) {
|
|
29786
|
+
process.stderr.write(
|
|
29787
|
+
`[aka] ${String(count)} pulled/custom-pack rule(s) could not be time-checked: ${reason}. That is a problem with this install, not with the rules \u2014 until it is fixed they are excluded from every scan on this machine. Nothing was quarantined, so reinstalling AKA brings them straight back.
|
|
29788
|
+
`
|
|
29789
|
+
);
|
|
29790
|
+
}
|
|
29791
|
+
async function quarantineRule(gateway, rule, worstMs, detail) {
|
|
29792
|
+
const key = ruleProbeKey(rule);
|
|
29793
|
+
let cached2 = false;
|
|
29794
|
+
if (key !== void 0) {
|
|
29795
|
+
try {
|
|
29796
|
+
await gateway.setRuleProbeVerdict(key, "quarantined", worstMs);
|
|
29797
|
+
cached2 = true;
|
|
29798
|
+
} catch {
|
|
29799
|
+
}
|
|
29800
|
+
}
|
|
29801
|
+
warn(rule, "quarantined", detail, cached2);
|
|
29802
|
+
}
|
|
29803
|
+
async function filterUnsafeRules(rules, gateway, opts) {
|
|
29804
|
+
const passBudgetMs = opts?.passBudgetMs ?? PASS_BUDGET_MS;
|
|
29805
|
+
const prober = opts?.prober;
|
|
29806
|
+
const passStart = performance.now();
|
|
29807
|
+
const safe = [];
|
|
29808
|
+
const unmeasurable = /* @__PURE__ */ new Map();
|
|
29809
|
+
try {
|
|
29810
|
+
for (const rule of rules) {
|
|
29811
|
+
const key = ruleProbeKey(rule);
|
|
29812
|
+
if (key === void 0) {
|
|
29813
|
+
safe.push(rule);
|
|
29814
|
+
continue;
|
|
29815
|
+
}
|
|
29816
|
+
let cached2;
|
|
29817
|
+
try {
|
|
29818
|
+
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
29819
|
+
} catch {
|
|
29820
|
+
cached2 = void 0;
|
|
29821
|
+
}
|
|
29822
|
+
if (cached2) {
|
|
29823
|
+
if (cached2.verdict === "safe") safe.push(rule);
|
|
29824
|
+
else warnQuarantined(rule, cached2.worstProbeMs, true);
|
|
29825
|
+
continue;
|
|
29826
|
+
}
|
|
29827
|
+
if (performance.now() - passStart >= passBudgetMs) {
|
|
29828
|
+
warnUnmeasured(rule);
|
|
29829
|
+
continue;
|
|
29830
|
+
}
|
|
29831
|
+
let isSafe;
|
|
29832
|
+
let worstMs;
|
|
29833
|
+
if (prober) {
|
|
29834
|
+
const outcome = await prober.probe(rule);
|
|
29835
|
+
if (outcome.status === "unavailable") {
|
|
29836
|
+
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29837
|
+
continue;
|
|
29838
|
+
}
|
|
29839
|
+
isSafe = outcome.status === "ok" ? outcome.safe : false;
|
|
29840
|
+
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
29841
|
+
} else {
|
|
29842
|
+
try {
|
|
29843
|
+
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
29844
|
+
} catch {
|
|
29845
|
+
isSafe = false;
|
|
29846
|
+
worstMs = Number.POSITIVE_INFINITY;
|
|
29847
|
+
}
|
|
29848
|
+
}
|
|
29849
|
+
let persisted = false;
|
|
29850
|
+
try {
|
|
29851
|
+
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
29852
|
+
persisted = true;
|
|
29853
|
+
} catch {
|
|
29854
|
+
}
|
|
29855
|
+
if (isSafe) safe.push(rule);
|
|
29856
|
+
else warnQuarantined(rule, worstMs, persisted);
|
|
29857
|
+
}
|
|
29858
|
+
} finally {
|
|
29859
|
+
for (const [reason, count] of unmeasurable) warnUnmeasurable(reason, count);
|
|
29860
|
+
}
|
|
29861
|
+
return safe;
|
|
29862
|
+
}
|
|
29863
|
+
|
|
29864
|
+
// ../../packages/plugin-sdk/src/guarded-scan.ts
|
|
29865
|
+
var DEFAULT_DEGRADE_SCOPE = "the rest of this process";
|
|
29866
|
+
function warnDegraded(scope, dropped, detail) {
|
|
29867
|
+
process.stderr.write(
|
|
29868
|
+
`[aka] isolated scanning is off for ${scope}: ${detail}. ${String(dropped)} pulled/custom-pack rule(s) are excluded; the built-in packs still run.
|
|
29869
|
+
`
|
|
29870
|
+
);
|
|
29871
|
+
}
|
|
29872
|
+
function createGuardedScanner(partition, gateway, opts) {
|
|
29873
|
+
const degradeScope = opts?.degradeScope ?? DEFAULT_DEGRADE_SCOPE;
|
|
29874
|
+
const verified = partition.verified;
|
|
29875
|
+
let unverified = partition.unverified;
|
|
29876
|
+
let isolated = unverified.length > 0 ? createIsolatedScanner({ verified, unverified }, opts) : void 0;
|
|
29877
|
+
let retired = false;
|
|
29878
|
+
function inProcess(text, context) {
|
|
29879
|
+
return scan(text, verified, context);
|
|
29880
|
+
}
|
|
29881
|
+
async function retire() {
|
|
29882
|
+
const live = isolated;
|
|
29883
|
+
isolated = void 0;
|
|
29884
|
+
unverified = [];
|
|
29885
|
+
if (live) await live.close();
|
|
29886
|
+
}
|
|
29887
|
+
async function degrade() {
|
|
29888
|
+
retired = true;
|
|
29889
|
+
await retire();
|
|
29890
|
+
}
|
|
29891
|
+
async function attempt(active, text, context, attribute) {
|
|
29892
|
+
try {
|
|
29893
|
+
return await active.scan(text, context, { attribute });
|
|
29894
|
+
} catch (error51) {
|
|
29895
|
+
return {
|
|
29896
|
+
status: "unavailable",
|
|
29897
|
+
reason: error51 instanceof Error ? error51.message : "the scan worker failed unexpectedly"
|
|
29898
|
+
};
|
|
29899
|
+
}
|
|
29900
|
+
}
|
|
29901
|
+
async function guardedScan(text, context) {
|
|
29902
|
+
const active = isolated;
|
|
29903
|
+
if (!active) return inProcess(text, context);
|
|
29904
|
+
let outcome = await attempt(active, text, context, false);
|
|
29905
|
+
if (outcome.status === "ok") return outcome.findings;
|
|
29906
|
+
if (outcome.status === "timeout") outcome = await attempt(active, text, context, true);
|
|
29907
|
+
const dropped = unverified.length;
|
|
29908
|
+
if (outcome.status === "ok") {
|
|
29909
|
+
warnDegraded(
|
|
29910
|
+
degradeScope,
|
|
29911
|
+
dropped,
|
|
29912
|
+
"a scan overran its bound once and no rule could be held responsible"
|
|
29913
|
+
);
|
|
29914
|
+
const findings = outcome.findings;
|
|
29915
|
+
await degrade();
|
|
29916
|
+
return findings;
|
|
29917
|
+
}
|
|
29918
|
+
if (outcome.status === "timeout") {
|
|
29919
|
+
const culprit = outcome.culpritIndex === void 0 ? void 0 : unverified[outcome.culpritIndex];
|
|
29920
|
+
if (culprit) {
|
|
29921
|
+
await quarantineRule(
|
|
29922
|
+
gateway,
|
|
29923
|
+
culprit,
|
|
29924
|
+
outcome.elapsedMs,
|
|
29925
|
+
`it did not finish within the ${outcome.elapsedMs.toFixed(0)}ms isolated-scan bound and was terminated; excluded from every later scan.`
|
|
29926
|
+
);
|
|
29927
|
+
}
|
|
29928
|
+
warnDegraded(
|
|
29929
|
+
degradeScope,
|
|
29930
|
+
dropped,
|
|
29931
|
+
culprit ? `rule "${culprit.id}" had to be terminated mid-scan` : `a scan was terminated at the ${outcome.elapsedMs.toFixed(0)}ms bound and no single rule could be held responsible, so nothing was quarantined and the next process will try these rules again`
|
|
29932
|
+
);
|
|
29933
|
+
} else {
|
|
29934
|
+
warnDegraded(degradeScope, dropped, outcome.reason);
|
|
29935
|
+
}
|
|
29936
|
+
await degrade();
|
|
29937
|
+
return inProcess(text, context);
|
|
29938
|
+
}
|
|
29939
|
+
return {
|
|
29940
|
+
scan: guardedScan,
|
|
29941
|
+
degraded: () => retired,
|
|
29942
|
+
async close() {
|
|
29943
|
+
await retire();
|
|
27921
29944
|
}
|
|
27922
29945
|
};
|
|
27923
29946
|
}
|
|
27924
29947
|
|
|
27925
29948
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
27926
|
-
import { arch, hostname as
|
|
29949
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
27927
29950
|
|
|
27928
29951
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
27929
|
-
import { mkdirSync as
|
|
27930
|
-
import { join as
|
|
29952
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
29953
|
+
import { join as join11 } from "path";
|
|
27931
29954
|
|
|
27932
29955
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
27933
|
-
import { readdirSync as
|
|
27934
|
-
import { basename as
|
|
29956
|
+
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
29957
|
+
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
27935
29958
|
function toPosix(path) {
|
|
27936
29959
|
return path.split(sep3).join("/");
|
|
27937
29960
|
}
|
|
@@ -27941,7 +29964,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
27941
29964
|
let root = null;
|
|
27942
29965
|
for (let level = 0; level < MAX_PROJECT_ROOT_LEVELS; level += 1) {
|
|
27943
29966
|
if (directoryHasMarker(dir, recognizeMarker)) root = dir;
|
|
27944
|
-
const parent =
|
|
29967
|
+
const parent = dirname3(dir);
|
|
27945
29968
|
if (parent === dir) break;
|
|
27946
29969
|
dir = parent;
|
|
27947
29970
|
}
|
|
@@ -27949,7 +29972,7 @@ function findProjectRoot(startDir, recognizeMarker) {
|
|
|
27949
29972
|
}
|
|
27950
29973
|
function directoryHasMarker(dir, recognizeMarker) {
|
|
27951
29974
|
try {
|
|
27952
|
-
for (const entry of
|
|
29975
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
27953
29976
|
if (entry.isFile() && recognizeMarker(entry.name) != null) return true;
|
|
27954
29977
|
}
|
|
27955
29978
|
} catch {
|
|
@@ -27960,69 +29983,42 @@ function directoryHasMarker(dir, recognizeMarker) {
|
|
|
27960
29983
|
function resolveNonGitProject(startDir, recognizeMarker) {
|
|
27961
29984
|
const projectRoot = findProjectRoot(startDir, recognizeMarker);
|
|
27962
29985
|
const realRoot = realpathSync2(projectRoot);
|
|
27963
|
-
return { root: projectRoot, projectKey: `path:${realRoot}`, project:
|
|
29986
|
+
return { root: projectRoot, projectKey: `path:${realRoot}`, project: basename4(realRoot) };
|
|
27964
29987
|
}
|
|
27965
29988
|
|
|
27966
29989
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
27967
29990
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
27968
|
-
import { existsSync as
|
|
27969
|
-
import { basename as
|
|
29991
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
|
|
29992
|
+
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
27970
29993
|
|
|
27971
|
-
// ../../packages/plugin-sdk/src/
|
|
27972
|
-
var
|
|
27973
|
-
|
|
27974
|
-
|
|
27975
|
-
|
|
27976
|
-
|
|
27977
|
-
|
|
27978
|
-
const
|
|
27979
|
-
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
|
|
27986
|
-
|
|
27987
|
-
|
|
27988
|
-
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
|
|
27992
|
-
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
|
|
27996
|
-
cached2 = await gateway.getRuleProbeVerdict(key);
|
|
27997
|
-
} catch {
|
|
27998
|
-
cached2 = void 0;
|
|
27999
|
-
}
|
|
28000
|
-
if (cached2) {
|
|
28001
|
-
if (cached2.verdict === "safe") safe.push(rule);
|
|
28002
|
-
else warnQuarantined(rule, cached2.worstProbeMs);
|
|
28003
|
-
continue;
|
|
28004
|
-
}
|
|
28005
|
-
if (performance.now() - passStart >= passBudgetMs) {
|
|
28006
|
-
warnQuarantined(rule, void 0);
|
|
28007
|
-
continue;
|
|
28008
|
-
}
|
|
28009
|
-
let isSafe;
|
|
28010
|
-
let worstMs;
|
|
28011
|
-
try {
|
|
28012
|
-
({ safe: isSafe, worstMs } = checkRuleTiming(rule));
|
|
28013
|
-
} catch {
|
|
28014
|
-
isSafe = false;
|
|
28015
|
-
worstMs = Number.POSITIVE_INFINITY;
|
|
28016
|
-
}
|
|
28017
|
-
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
28018
|
-
if (isSafe) safe.push(rule);
|
|
28019
|
-
else warnQuarantined(rule, worstMs);
|
|
28020
|
-
}
|
|
28021
|
-
return safe;
|
|
28022
|
-
}
|
|
29994
|
+
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
29995
|
+
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
29996
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
29997
|
+
return v;
|
|
29998
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
29999
|
+
var optionalFlag = external_exports.preprocess((v) => {
|
|
30000
|
+
if (typeof v !== "string") return false;
|
|
30001
|
+
const normalized = v.trim().toLowerCase();
|
|
30002
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
30003
|
+
}, external_exports.boolean()).catch(false);
|
|
30004
|
+
var antigravityProviderEnvShape = {
|
|
30005
|
+
GOOGLE_GENAI_USE_VERTEXAI: optionalFlag,
|
|
30006
|
+
GOOGLE_GEMINI_BASE_URL: optionalBaseUrl2
|
|
30007
|
+
};
|
|
30008
|
+
var AntigravityProviderEnvSchema = external_exports.object(antigravityProviderEnvShape);
|
|
30009
|
+
|
|
30010
|
+
// ../../packages/plugin-sdk/src/provider-env-codex.ts
|
|
30011
|
+
var optionalBaseUrl3 = external_exports.preprocess((v) => {
|
|
30012
|
+
if (typeof v === "string" && v.trim() === "") return void 0;
|
|
30013
|
+
return v;
|
|
30014
|
+
}, external_exports.string().optional()).catch(void 0);
|
|
30015
|
+
var codexProviderEnvShape = {
|
|
30016
|
+
OPENAI_BASE_URL: optionalBaseUrl3
|
|
30017
|
+
};
|
|
30018
|
+
var CodexProviderEnvSchema = external_exports.object(codexProviderEnvShape);
|
|
28023
30019
|
|
|
28024
30020
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
28025
|
-
import { randomUUID as
|
|
30021
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
28026
30022
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
28027
30023
|
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
28028
30024
|
function entryIsActive(entry, now) {
|
|
@@ -28047,6 +30043,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28047
30043
|
const dataDir2 = opts?.dataDir;
|
|
28048
30044
|
let policies = [];
|
|
28049
30045
|
let rules = [];
|
|
30046
|
+
let scanner;
|
|
28050
30047
|
let bundleExceptions = [];
|
|
28051
30048
|
let initialized = false;
|
|
28052
30049
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
@@ -28072,8 +30069,24 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28072
30069
|
return key !== void 0 && bundledProbeKeys.has(key);
|
|
28073
30070
|
});
|
|
28074
30071
|
const needsGate = incoming.filter((rule) => !ciVerified.includes(rule));
|
|
28075
|
-
|
|
28076
|
-
|
|
30072
|
+
let prober;
|
|
30073
|
+
const gated = await filterUnsafeRules(needsGate, gateway, {
|
|
30074
|
+
prober: {
|
|
30075
|
+
probe: (rule) => {
|
|
30076
|
+
prober ??= createIsolatedScanner({ verified: [], unverified: [] }, opts?.scanIsolation);
|
|
30077
|
+
return prober.probe(rule);
|
|
30078
|
+
}
|
|
30079
|
+
}
|
|
30080
|
+
});
|
|
30081
|
+
await prober?.close();
|
|
30082
|
+
const verified = bundle.rulesComplete ? [...ciVerified] : [...getLoadedRules(), ...ciVerified];
|
|
30083
|
+
const unverified = [];
|
|
30084
|
+
for (const rule of gated) {
|
|
30085
|
+
if (rule.matcher.type === "regex") unverified.push(rule);
|
|
30086
|
+
else verified.push(rule);
|
|
30087
|
+
}
|
|
30088
|
+
rules = [...verified, ...unverified];
|
|
30089
|
+
scanner = createGuardedScanner({ verified, unverified }, gateway, opts?.scanIsolation);
|
|
28077
30090
|
bundleExceptions = bundle.exceptions ?? [];
|
|
28078
30091
|
initialized = true;
|
|
28079
30092
|
}
|
|
@@ -28125,7 +30138,12 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28125
30138
|
if (worst === "block") return { action: "block", text: null, findings };
|
|
28126
30139
|
if (worst === "redact") {
|
|
28127
30140
|
const redactFindings = findings.filter((f) => actionFor(f) === "redact");
|
|
28128
|
-
return {
|
|
30141
|
+
return {
|
|
30142
|
+
action: "redact",
|
|
30143
|
+
text: redact(text, redactFindings),
|
|
30144
|
+
findings,
|
|
30145
|
+
enforcedFindings: redactFindings
|
|
30146
|
+
};
|
|
28129
30147
|
}
|
|
28130
30148
|
return { action: worst, text, findings };
|
|
28131
30149
|
}
|
|
@@ -28166,9 +30184,17 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28166
30184
|
else groups.set(pair, [finding]);
|
|
28167
30185
|
}
|
|
28168
30186
|
const now = Date.now();
|
|
30187
|
+
const preAuthorized = new Set(ctx.preAuthorizedGrantIds ?? []);
|
|
28169
30188
|
for (const [pair, group] of groups) {
|
|
28170
30189
|
const entry = entries.get(pair);
|
|
28171
|
-
if (!entry
|
|
30190
|
+
if (!entry) continue;
|
|
30191
|
+
if (preAuthorized.has(entry.id)) {
|
|
30192
|
+
if (!conditionsMatch(entry.conditions, ctx)) continue;
|
|
30193
|
+
for (const finding of group) excepted.add(finding);
|
|
30194
|
+
exceptionIds.push(entry.id);
|
|
30195
|
+
continue;
|
|
30196
|
+
}
|
|
30197
|
+
if (!entryIsActive(entry, now) || !conditionsMatch(entry.conditions, ctx)) {
|
|
28172
30198
|
continue;
|
|
28173
30199
|
}
|
|
28174
30200
|
let consumed = false;
|
|
@@ -28200,7 +30226,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28200
30226
|
const pair = `${finding.ruleId}:${fp}`;
|
|
28201
30227
|
if (seen.has(pair)) continue;
|
|
28202
30228
|
seen.add(pair);
|
|
28203
|
-
const reference =
|
|
30229
|
+
const reference = randomUUID14().replaceAll("-", "").slice(0, 6);
|
|
28204
30230
|
const maskedValue = maskMatch(finding.rawMatch);
|
|
28205
30231
|
try {
|
|
28206
30232
|
await gateway.recordBlockedDetection({
|
|
@@ -28224,7 +30250,10 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28224
30250
|
async function evaluate2(text, context, ctx) {
|
|
28225
30251
|
try {
|
|
28226
30252
|
await ensureInitialized();
|
|
28227
|
-
|
|
30253
|
+
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
30254
|
+
const shielded = shieldPointers(text);
|
|
30255
|
+
const matched = await scanner.scan(shielded.text, context);
|
|
30256
|
+
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
28228
30257
|
const fpCache = /* @__PURE__ */ new Map();
|
|
28229
30258
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
28230
30259
|
const decision = decide(findings, text, excepted);
|
|
@@ -28247,7 +30276,11 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28247
30276
|
const { decision, excepted, exceptionIds } = await evaluate2(
|
|
28248
30277
|
input.text,
|
|
28249
30278
|
filePath ? { filePath } : void 0,
|
|
28250
|
-
{
|
|
30279
|
+
{
|
|
30280
|
+
sourceTool: input.sourceTool,
|
|
30281
|
+
metadata: input.metadata,
|
|
30282
|
+
preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
|
|
30283
|
+
}
|
|
28251
30284
|
);
|
|
28252
30285
|
if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
|
|
28253
30286
|
try {
|
|
@@ -28277,7 +30310,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28277
30310
|
valueFingerprint: findingKeyFingerprintKey ? fingerprintOf(findingKeyFingerprintKey, match, findingKeyFpCache) : maskedMatch
|
|
28278
30311
|
}) : void 0;
|
|
28279
30312
|
return {
|
|
28280
|
-
id:
|
|
30313
|
+
id: randomUUID14(),
|
|
28281
30314
|
eventId: event.id,
|
|
28282
30315
|
ruleId: match.ruleId,
|
|
28283
30316
|
category: match.category,
|
|
@@ -28303,25 +30336,32 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
28303
30336
|
const sorted = [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
28304
30337
|
return contentHashOf(JSON.stringify(sorted));
|
|
28305
30338
|
} catch {
|
|
28306
|
-
return `unresolved-${
|
|
30339
|
+
return `unresolved-${randomUUID14()}`;
|
|
28307
30340
|
}
|
|
28308
30341
|
}
|
|
30342
|
+
function scanIsolationDegraded() {
|
|
30343
|
+
return scanner?.degraded() ?? false;
|
|
30344
|
+
}
|
|
28309
30345
|
async function close() {
|
|
30346
|
+
try {
|
|
30347
|
+
await scanner?.close();
|
|
30348
|
+
} catch {
|
|
30349
|
+
}
|
|
28310
30350
|
await gateway.close();
|
|
28311
30351
|
}
|
|
28312
|
-
return { processText, capture, rulesetFingerprint, close };
|
|
30352
|
+
return { processText, capture, rulesetFingerprint, scanIsolationDegraded, close };
|
|
28313
30353
|
}
|
|
28314
30354
|
|
|
28315
30355
|
// ../../packages/plugin-sdk/src/suppressions.ts
|
|
28316
30356
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
28317
30357
|
|
|
28318
30358
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
28319
|
-
import { mkdirSync as
|
|
28320
|
-
import { join as
|
|
30359
|
+
import { mkdirSync as mkdirSync4, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
30360
|
+
import { join as join13 } from "path";
|
|
28321
30361
|
|
|
28322
30362
|
// ../../packages/scanner/src/discover.ts
|
|
28323
|
-
import { readdirSync as
|
|
28324
|
-
import { join as
|
|
30363
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
30364
|
+
import { join as join14 } from "path";
|
|
28325
30365
|
|
|
28326
30366
|
// ../../packages/scanner/src/constants.ts
|
|
28327
30367
|
var COMMON_SKIP_DIRS = ["node_modules", "__pycache__", ".venv", "venv", ".cache"];
|
|
@@ -28350,7 +30390,7 @@ function discoverGitRepos(opts) {
|
|
|
28350
30390
|
if (depth > maxDepth || excludePaths.has(dir)) return;
|
|
28351
30391
|
let entries;
|
|
28352
30392
|
try {
|
|
28353
|
-
entries =
|
|
30393
|
+
entries = readdirSync5(dir, { withFileTypes: true, encoding: "utf8" });
|
|
28354
30394
|
} catch {
|
|
28355
30395
|
return;
|
|
28356
30396
|
}
|
|
@@ -28366,7 +30406,7 @@ function discoverGitRepos(opts) {
|
|
|
28366
30406
|
if (!entry.isDirectory()) continue;
|
|
28367
30407
|
if (DISCOVER_SKIP.has(entry.name)) continue;
|
|
28368
30408
|
if (entry.name.startsWith(".")) continue;
|
|
28369
|
-
visit(
|
|
30409
|
+
visit(join14(dir, entry.name), depth + 1);
|
|
28370
30410
|
}
|
|
28371
30411
|
}
|
|
28372
30412
|
for (const root of searchRoots) {
|
|
@@ -28376,8 +30416,8 @@ function discoverGitRepos(opts) {
|
|
|
28376
30416
|
}
|
|
28377
30417
|
|
|
28378
30418
|
// ../../packages/scanner/src/render.ts
|
|
28379
|
-
import { basename as
|
|
28380
|
-
var
|
|
30419
|
+
import { basename as basename6, relative as relative2 } from "path";
|
|
30420
|
+
var SEVERITY_ORDER3 = ["critical", "high", "medium", "low"];
|
|
28381
30421
|
var SEVERITY_GLYPH = {
|
|
28382
30422
|
critical: "\u2588",
|
|
28383
30423
|
high: "\u2593",
|
|
@@ -28408,7 +30448,7 @@ function findingsLabel(total, gitignored) {
|
|
|
28408
30448
|
return `${String(total)} (${String(gitignored)} in .gitignore'd files \u2014 informational)`;
|
|
28409
30449
|
}
|
|
28410
30450
|
function severitySection(bySeverity) {
|
|
28411
|
-
const rows =
|
|
30451
|
+
const rows = SEVERITY_ORDER3.filter((s) => (bySeverity[s] ?? 0) > 0).map((s) => [
|
|
28412
30452
|
`${SEVERITY_GLYPH[s] ?? ""} ${s}`,
|
|
28413
30453
|
String(bySeverity[s])
|
|
28414
30454
|
]);
|
|
@@ -28456,7 +30496,7 @@ function renderMultiRepoSummary(summary, opts = {}) {
|
|
|
28456
30496
|
"\n"
|
|
28457
30497
|
);
|
|
28458
30498
|
}
|
|
28459
|
-
const repoRows = summary.repos.filter((r) => r.summary.scanned > 0 || r.summary.findings > 0).map((r) => [
|
|
30499
|
+
const repoRows = summary.repos.filter((r) => r.summary.scanned > 0 || r.summary.findings > 0).map((r) => [basename6(r.rootDir), String(r.summary.scanned), String(r.summary.findings)]);
|
|
28460
30500
|
const repoSection = repoRows.length > 0 ? ["", indent(table(["REPO", "SCANNED", "FINDINGS"], repoRows))].join("\n") : "";
|
|
28461
30501
|
return [
|
|
28462
30502
|
"\u2713 Multi-repo scan complete",
|
|
@@ -28469,11 +30509,11 @@ function renderMultiRepoSummary(summary, opts = {}) {
|
|
|
28469
30509
|
}
|
|
28470
30510
|
|
|
28471
30511
|
// ../../packages/scanner/src/scan.ts
|
|
28472
|
-
import { existsSync as
|
|
30512
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
28473
30513
|
import { extname as extname2, isAbsolute as isAbsolute2, relative as relative4 } from "path";
|
|
28474
30514
|
|
|
28475
30515
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
28476
|
-
import { randomUUID as
|
|
30516
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
28477
30517
|
|
|
28478
30518
|
// ../../packages/plugin-runtime/src/recorder.ts
|
|
28479
30519
|
var PLUGIN_RECORDER_BINARY = "plugin";
|
|
@@ -28635,7 +30675,7 @@ var StandaloneDataGateway = class {
|
|
|
28635
30675
|
const customKeywords = [...new Set(policies.flatMap((p) => p.customKeywords ?? []))];
|
|
28636
30676
|
const installed = this.installedScanRules();
|
|
28637
30677
|
const rulePolicies = installed ? [...installed.ruleActions].map(([ruleId, action]) => ({
|
|
28638
|
-
id:
|
|
30678
|
+
id: randomUUID15(),
|
|
28639
30679
|
scope: "global",
|
|
28640
30680
|
target: { ruleId },
|
|
28641
30681
|
action,
|
|
@@ -28788,16 +30828,16 @@ function resolveDataGateway(config2, meta3, gatewayFactory = standaloneGatewayFa
|
|
|
28788
30828
|
}
|
|
28789
30829
|
|
|
28790
30830
|
// ../../packages/plugin-runtime/src/handle-session-start.ts
|
|
28791
|
-
import { randomUUID as
|
|
30831
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
28792
30832
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
28793
30833
|
|
|
28794
30834
|
// ../../packages/scanner/src/manifests.ts
|
|
28795
|
-
import { statSync as
|
|
30835
|
+
import { statSync as statSync8 } from "fs";
|
|
28796
30836
|
|
|
28797
30837
|
// ../../packages/scanner/src/walk.ts
|
|
28798
30838
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
28799
|
-
import { readdirSync as
|
|
28800
|
-
import { extname, join as
|
|
30839
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
|
|
30840
|
+
import { extname, join as join15, relative as relative3, sep as sep5 } from "path";
|
|
28801
30841
|
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
28802
30842
|
".ts",
|
|
28803
30843
|
".tsx",
|
|
@@ -28829,7 +30869,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
28829
30869
|
var DEFAULT_MAX_BYTES = 512 * 1024;
|
|
28830
30870
|
function readIgnoreLayer(dir, filename) {
|
|
28831
30871
|
try {
|
|
28832
|
-
const content =
|
|
30872
|
+
const content = readFileSync9(join15(dir, filename), "utf8");
|
|
28833
30873
|
return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
|
|
28834
30874
|
} catch {
|
|
28835
30875
|
return void 0;
|
|
@@ -28851,7 +30891,7 @@ function* walkTree(rootDir, opts = {}) {
|
|
|
28851
30891
|
function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
|
|
28852
30892
|
let dirents;
|
|
28853
30893
|
try {
|
|
28854
|
-
dirents =
|
|
30894
|
+
dirents = readdirSync6(dir, { withFileTypes: true, encoding: "utf8" });
|
|
28855
30895
|
} catch {
|
|
28856
30896
|
return;
|
|
28857
30897
|
}
|
|
@@ -28861,7 +30901,7 @@ function* walkTree(rootDir, opts = {}) {
|
|
|
28861
30901
|
const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
|
|
28862
30902
|
for (const entry of dirents) {
|
|
28863
30903
|
const name = entry.name;
|
|
28864
|
-
const fullPath =
|
|
30904
|
+
const fullPath = join15(dir, name);
|
|
28865
30905
|
if (entry.isDirectory()) {
|
|
28866
30906
|
const skipState = evaluate(dirSkipLayers, fullPath, true);
|
|
28867
30907
|
if (skipState !== "unignored" && (SKIP_DIRS.has(name) || skipState === "ignored")) {
|
|
@@ -28895,7 +30935,7 @@ function* walkSourceFiles(opts = {}) {
|
|
|
28895
30935
|
let size;
|
|
28896
30936
|
let mtime;
|
|
28897
30937
|
try {
|
|
28898
|
-
const st =
|
|
30938
|
+
const st = statSync7(file2.path);
|
|
28899
30939
|
size = st.size;
|
|
28900
30940
|
mtime = st.mtime;
|
|
28901
30941
|
} catch {
|
|
@@ -28915,7 +30955,7 @@ function* walkSourceFiles(opts = {}) {
|
|
|
28915
30955
|
if (opts.shouldRead && !opts.shouldRead(meta3)) continue;
|
|
28916
30956
|
let content;
|
|
28917
30957
|
try {
|
|
28918
|
-
content =
|
|
30958
|
+
content = readFileSync9(file2.path, "utf8");
|
|
28919
30959
|
} catch {
|
|
28920
30960
|
continue;
|
|
28921
30961
|
}
|
|
@@ -28937,7 +30977,7 @@ function collectManifests(rootDir, maxFileSizeBytes = MAX_MANIFEST_BYTES) {
|
|
|
28937
30977
|
const kind = manifestKindOf(file2.name);
|
|
28938
30978
|
if (kind === null) continue;
|
|
28939
30979
|
try {
|
|
28940
|
-
const st =
|
|
30980
|
+
const st = statSync8(file2.path);
|
|
28941
30981
|
if (st.size > maxFileSizeBytes) continue;
|
|
28942
30982
|
found.push({ path: file2.path, kind, mtime: st.mtime.toISOString(), size: st.size });
|
|
28943
30983
|
} catch {
|
|
@@ -29035,7 +31075,7 @@ function isUnderRoot(path, rootDir) {
|
|
|
29035
31075
|
async function sweepDeletedFiles(gateway, rootDir, previous) {
|
|
29036
31076
|
const deleted = [];
|
|
29037
31077
|
for (const path of previous.keys()) {
|
|
29038
|
-
if (!isUnderRoot(path, rootDir) ||
|
|
31078
|
+
if (!isUnderRoot(path, rootDir) || existsSync9(path)) continue;
|
|
29039
31079
|
deleted.push(path);
|
|
29040
31080
|
await resolveRemovedFindings(gateway, path, [], { deleted: true });
|
|
29041
31081
|
}
|
|
@@ -29130,6 +31170,9 @@ async function scanDir(runtime, gateway, config2, seen, ledger, rootDir, opts) {
|
|
|
29130
31170
|
if (committed === null) {
|
|
29131
31171
|
return { rootDir, scanned, skipped, findings, gitignoredFindings, byRule, bySeverity };
|
|
29132
31172
|
}
|
|
31173
|
+
if (runtime.scanIsolationDegraded()) {
|
|
31174
|
+
return { rootDir, scanned, skipped, findings, gitignoredFindings, byRule, bySeverity };
|
|
31175
|
+
}
|
|
29133
31176
|
await gateway.recordScanned(ledgerable(updates, egress, committed));
|
|
29134
31177
|
return { rootDir, scanned, skipped, findings, gitignoredFindings, byRule, bySeverity };
|
|
29135
31178
|
}
|
|
@@ -29139,7 +31182,7 @@ function scanManifests(egress, ledger, updates, rootDir) {
|
|
|
29139
31182
|
if (prev?.mtime === manifest.mtime) continue;
|
|
29140
31183
|
let content;
|
|
29141
31184
|
try {
|
|
29142
|
-
content =
|
|
31185
|
+
content = readFileSync10(manifest.path, "utf8");
|
|
29143
31186
|
} catch {
|
|
29144
31187
|
continue;
|
|
29145
31188
|
}
|