@akasecurity/ai-tc-claude-code 0.9.9 → 0.9.10
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 +2 -2
- package/hooks/hooks.json +1 -1
- package/package.json +5 -5
- package/scripts/apply-suppressions.js +1200 -671
- package/scripts/backfill.js +2299 -1434
- package/scripts/filescan.js +2053 -1249
- package/scripts/firstrun.js +1961 -1188
- package/scripts/history-sync.js +1882 -1029
- package/scripts/intro.js +363 -238
- package/scripts/message-display.js +1212 -662
- package/scripts/onboard.js +1598 -1066
- package/scripts/post-model-switch.js +447 -311
- package/scripts/post-tool-use.js +2101 -1274
- package/scripts/pre-model-switch.js +1981 -1204
- package/scripts/pre-tool-use.js +2113 -1102
- package/scripts/query.js +1961 -1188
- package/scripts/reconcile.js +2082 -1242
- package/scripts/remediate.js +2152 -1279
- package/scripts/scan-worker.js +278 -176
- package/scripts/session-start.js +2050 -1277
- package/scripts/start-light.js +366 -241
- package/scripts/statusline.js +1961 -1188
- package/scripts/stop.js +448 -315
- package/scripts/sync.js +17422 -2018
- package/scripts/user-prompt-submit.js +2110 -1279
package/scripts/remediate.js
CHANGED
|
@@ -492,12 +492,13 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/remediation/entry.ts
|
|
495
|
-
import { readFileSync as
|
|
495
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
496
496
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/attached-derived.ts
|
|
499
499
|
import { rmSync } from "fs";
|
|
500
500
|
import { join } from "path";
|
|
501
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
501
502
|
var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
|
|
502
503
|
var ATTACHED_FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
503
504
|
|
|
@@ -598,6 +599,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
598
599
|
{
|
|
599
600
|
tag: "0022_audit_inspection_ms",
|
|
600
601
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
tag: "0023_secret_vault_user_authorized",
|
|
605
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
609
|
+
sql: "DROP INDEX IF EXISTS `idx_finding_resolution_key`;--> statement-breakpoint\nCREATE INDEX `idx_finding_resolution_key_created` ON `finding_resolution` (`finding_key`,`created_at`);"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
613
|
+
sql: "ALTER TABLE `audit_events` ADD `source_tool` text GENERATED ALWAYS AS (json_extract(attributes, '$.source_tool')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `repo` text GENERATED ALWAYS AS (json_extract(attributes, '$.repo')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `file_path` text GENERATED ALWAYS AS (json_extract(attributes, '$.file_path')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `tool_name` text GENERATED ALWAYS AS (json_extract(attributes, '$.tool_name')) VIRTUAL;"
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
617
|
+
sql: "ALTER TABLE `audit_events` ADD `service_tier` text GENERATED ALWAYS AS (json_extract(attributes, '$.service_tier')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_1h_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_1h_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_5m_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_5m_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `web_search_requests` integer GENERATED ALWAYS AS (json_extract(attributes, '$.web_search_requests')) VIRTUAL;"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
tag: "0027_audit_llm_usage_index",
|
|
621
|
+
sql: "CREATE INDEX `idx_audit_llm_usage` ON `audit_events` (`started_at`,`root_session_id`,`provider`,`model`,`service_tier`,`input_tokens`,`output_tokens`,`cache_creation_input_tokens`,`cache_read_input_tokens`,`ephemeral_1h_input_tokens`,`ephemeral_5m_input_tokens`,`web_search_requests`) WHERE event_type = 'llm_call' AND attributes IS NOT NULL;"
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
tag: "0028_activity_session_probe_indexes",
|
|
625
|
+
sql: "CREATE INDEX `idx_audit_session_prompt` ON `audit_events` (`root_session_id`) WHERE event_type = 'prompt';--> statement-breakpoint\nCREATE INDEX `idx_audit_session_share` ON `audit_events` (`root_session_id`) WHERE event_type = 'share';--> statement-breakpoint\nCREATE INDEX `idx_audit_ended_at` ON `audit_events` (`ended_at`,`root_session_id`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\nCREATE INDEX `idx_audit_session_ended` ON `audit_events` (`root_session_id`,`ended_at`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\n-- Expression index for the activity list's turns rollup: a live-captured\n-- session's turns are the DISTINCT `run_key` across its `llm_call` leaves, and\n-- carrying the extracted key in the index answers that count from the index\n-- alone instead of parsing every leaf's attribute bag. Written by hand, as\n-- 0013's `idx_audit_code_change_path` was: drizzle-kit cannot emit an\n-- expression containing a comma, so this index is not declared in sqlite.ts.\nCREATE INDEX `idx_audit_session_run_key` ON `audit_events` (`root_session_id`, json_extract(`attributes`, '$.run_key')) WHERE `event_type` = 'llm_call';\n"
|
|
601
626
|
}
|
|
602
627
|
];
|
|
603
628
|
|
|
@@ -22137,6 +22162,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22137
22162
|
AttachTokenExpired,
|
|
22138
22163
|
external_exports.object({ status: printable(64) })
|
|
22139
22164
|
]);
|
|
22165
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22166
|
+
var DeviceCommand = external_exports.object({
|
|
22167
|
+
id: printable(128).min(1),
|
|
22168
|
+
kind: DeviceCommandKind,
|
|
22169
|
+
issuedAt: printable(64).min(1),
|
|
22170
|
+
expiresAt: printable(64).min(1)
|
|
22171
|
+
}).strict();
|
|
22172
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22173
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22174
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22175
|
+
external_exports.object({
|
|
22176
|
+
outcome: external_exports.literal("reported"),
|
|
22177
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22178
|
+
}).strict(),
|
|
22179
|
+
external_exports.object({
|
|
22180
|
+
outcome: external_exports.literal("failed"),
|
|
22181
|
+
reason: DeviceCommandFailureReason,
|
|
22182
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22183
|
+
}).strict()
|
|
22184
|
+
]);
|
|
22140
22185
|
|
|
22141
22186
|
// ../../packages/schema/src/zod/registry.ts
|
|
22142
22187
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22303,7 +22348,7 @@ var PackManifest = external_exports.object({
|
|
|
22303
22348
|
}).meta({ id: "PackManifest" });
|
|
22304
22349
|
|
|
22305
22350
|
// ../../packages/schema/src/zod/detection.ts
|
|
22306
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22351
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22307
22352
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22308
22353
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22309
22354
|
var DetectionCounts = external_exports.object({
|
|
@@ -22440,14 +22485,17 @@ function optional2(key, parsed2, raw) {
|
|
|
22440
22485
|
function isStringArray(value) {
|
|
22441
22486
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22442
22487
|
}
|
|
22488
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22489
|
+
function resolveOrigin(origin) {
|
|
22490
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22491
|
+
}
|
|
22443
22492
|
function summaryToDetectionListItem(s) {
|
|
22444
22493
|
return {
|
|
22445
22494
|
id: `${s.namespace}/${s.packId}`,
|
|
22446
22495
|
name: s.name,
|
|
22447
22496
|
version: s.version,
|
|
22448
22497
|
enabled: s.enabled,
|
|
22449
|
-
origin:
|
|
22450
|
-
// v1: every installed pack is library origin
|
|
22498
|
+
origin: resolveOrigin(s.origin),
|
|
22451
22499
|
namespace: s.namespace,
|
|
22452
22500
|
packId: s.packId,
|
|
22453
22501
|
ruleCount: s.ruleCount,
|
|
@@ -22499,7 +22547,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22499
22547
|
name: row.name,
|
|
22500
22548
|
version: row.version,
|
|
22501
22549
|
enabled: row.enabled,
|
|
22502
|
-
origin:
|
|
22550
|
+
origin: resolveOrigin(row.origin),
|
|
22503
22551
|
namespace: row.namespace,
|
|
22504
22552
|
packId: row.packId,
|
|
22505
22553
|
ruleCount: row.rules.length,
|
|
@@ -22519,16 +22567,20 @@ function splitDetectionId(id) {
|
|
|
22519
22567
|
}
|
|
22520
22568
|
function buildDetectionsList(summaries, query) {
|
|
22521
22569
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22570
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22522
22571
|
const counts = {
|
|
22523
22572
|
all: summaries.length,
|
|
22524
|
-
library: summaries.length,
|
|
22525
|
-
|
|
22526
|
-
|
|
22573
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22574
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22575
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22576
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22577
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22578
|
+
// OriginEnum.
|
|
22527
22579
|
customized: 0,
|
|
22528
22580
|
updates: withUpdate.length
|
|
22529
22581
|
};
|
|
22530
22582
|
const filter = query.filter;
|
|
22531
|
-
let filtered = filter === "custom"
|
|
22583
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22532
22584
|
if (query.q) {
|
|
22533
22585
|
const q = query.q.toLowerCase();
|
|
22534
22586
|
filtered = filtered.filter(
|
|
@@ -22608,8 +22660,9 @@ var Event = external_exports.object({
|
|
|
22608
22660
|
metadata: EventMetadata.optional()
|
|
22609
22661
|
}).meta({ id: "Event" });
|
|
22610
22662
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22663
|
+
var INGEST_BATCH_MAX = 100;
|
|
22611
22664
|
var IngestBatch = external_exports.object({
|
|
22612
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22665
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22613
22666
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22614
22667
|
// additionally rejects any event whose contentHash the store has already
|
|
22615
22668
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23165,382 +23218,11 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23165
23218
|
message: "At least one field must be provided"
|
|
23166
23219
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23167
23220
|
|
|
23168
|
-
// ../../packages/schema/src/zod/vault.ts
|
|
23169
|
-
var POINTER_FORMAT_VERSION = 2;
|
|
23170
|
-
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23171
|
-
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23172
|
-
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23173
|
-
);
|
|
23174
|
-
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23175
|
-
function pointerTokenScanner() {
|
|
23176
|
-
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23177
|
-
}
|
|
23178
|
-
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23179
|
-
var ParsedPointer = external_exports.object({
|
|
23180
|
-
category: DetectionCategory,
|
|
23181
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23182
|
-
pointerId: external_exports.string(),
|
|
23183
|
-
tag: external_exports.string()
|
|
23184
|
-
});
|
|
23185
|
-
var VaultEntry = external_exports.object({
|
|
23186
|
-
pointerId: external_exports.string(),
|
|
23187
|
-
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23188
|
-
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23189
|
-
// independently of the vault encryption key below.
|
|
23190
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23191
|
-
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23192
|
-
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23193
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23194
|
-
// Fixed at first mint and never updated: the same value detected later under a
|
|
23195
|
-
// different rule's category keeps the category it was minted with, so one
|
|
23196
|
-
// value always produces exactly one wire token.
|
|
23197
|
-
category: DetectionCategory,
|
|
23198
|
-
ruleId: external_exports.string(),
|
|
23199
|
-
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23200
|
-
maskedMatch: external_exports.string(),
|
|
23201
|
-
provider: external_exports.string().optional(),
|
|
23202
|
-
ciphertext: external_exports.string(),
|
|
23203
|
-
nonce: external_exports.string(),
|
|
23204
|
-
authTag: external_exports.string(),
|
|
23205
|
-
// How many times this value has been detected on this machine — the reuse
|
|
23206
|
-
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23207
|
-
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23208
|
-
firstSeen: external_exports.string(),
|
|
23209
|
-
lastSeen: external_exports.string()
|
|
23210
|
-
});
|
|
23211
|
-
var PointerDescriptor = external_exports.object({
|
|
23212
|
-
category: DetectionCategory,
|
|
23213
|
-
provider: external_exports.string().optional(),
|
|
23214
|
-
maskedMatch: external_exports.string(),
|
|
23215
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23216
|
-
firstSeen: external_exports.string(),
|
|
23217
|
-
lastSeen: external_exports.string()
|
|
23218
|
-
});
|
|
23219
|
-
var PointerIdentity = external_exports.object({
|
|
23220
|
-
ruleId: external_exports.string(),
|
|
23221
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23222
|
-
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23223
|
-
});
|
|
23224
|
-
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23225
|
-
var VaultDerefReason = external_exports.enum([
|
|
23226
|
-
"display",
|
|
23227
|
-
"explicit-reveal",
|
|
23228
|
-
"view-render",
|
|
23229
|
-
"model-input",
|
|
23230
|
-
"remediation",
|
|
23231
|
-
"purge"
|
|
23232
|
-
]);
|
|
23233
|
-
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23234
|
-
var BATCHED_DEREF_REASONS = ["display", "view-render"];
|
|
23235
|
-
function isBatchedDerefReason(reason) {
|
|
23236
|
-
return BATCHED_DEREF_REASONS.includes(reason);
|
|
23237
|
-
}
|
|
23238
|
-
var VaultDeref = external_exports.object({
|
|
23239
|
-
id: external_exports.guid(),
|
|
23240
|
-
pointerId: external_exports.string(),
|
|
23241
|
-
at: external_exports.string(),
|
|
23242
|
-
target: DetokenizeTarget,
|
|
23243
|
-
reason: VaultDerefReason,
|
|
23244
|
-
outcome: VaultDerefOutcome,
|
|
23245
|
-
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23246
|
-
grantId: external_exports.string().optional(),
|
|
23247
|
-
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23248
|
-
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23249
|
-
pointerCount: external_exports.number().int().positive().default(1)
|
|
23250
|
-
});
|
|
23251
|
-
var VaultSightingKind = external_exports.enum([
|
|
23252
|
-
"prompt",
|
|
23253
|
-
"tool-input",
|
|
23254
|
-
"tool-output",
|
|
23255
|
-
"file",
|
|
23256
|
-
"transcript"
|
|
23257
|
-
]);
|
|
23258
|
-
var VaultSighting = external_exports.object({
|
|
23259
|
-
location: external_exports.string(),
|
|
23260
|
-
kind: VaultSightingKind,
|
|
23261
|
-
firstSeen: external_exports.string(),
|
|
23262
|
-
lastSeen: external_exports.string()
|
|
23263
|
-
});
|
|
23264
|
-
var VaultInventoryEntry = external_exports.object({
|
|
23265
|
-
pointerId: external_exports.string(),
|
|
23266
|
-
category: DetectionCategory,
|
|
23267
|
-
provider: external_exports.string().optional(),
|
|
23268
|
-
maskedMatch: external_exports.string(),
|
|
23269
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23270
|
-
firstSeen: external_exports.string(),
|
|
23271
|
-
lastSeen: external_exports.string(),
|
|
23272
|
-
// The active reveal-to-model grant covering this value, when one exists —
|
|
23273
|
-
// the inventory badges it, the row links to revocation.
|
|
23274
|
-
revealGrantId: external_exports.string().nullable(),
|
|
23275
|
-
sightings: external_exports.array(VaultSighting)
|
|
23276
|
-
});
|
|
23277
|
-
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23278
|
-
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23279
|
-
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23280
|
-
var ListVaultInventoryQuery = external_exports.object({
|
|
23281
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23282
|
-
// Opaque; names the last row of the page just served.
|
|
23283
|
-
cursor: external_exports.string().optional()
|
|
23284
|
-
});
|
|
23285
|
-
var ListVaultInventoryResponse = external_exports.object({
|
|
23286
|
-
// Vaulted values across the whole store, not just this page — cursor-
|
|
23287
|
-
// independent, so paging never changes what the count claims.
|
|
23288
|
-
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23289
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23290
|
-
// `null` once the last page is reached.
|
|
23291
|
-
nextCursor: external_exports.string().nullable()
|
|
23292
|
-
});
|
|
23293
|
-
var ListVaultReuseQuery = external_exports.object({
|
|
23294
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23295
|
-
cursor: external_exports.string().optional()
|
|
23296
|
-
});
|
|
23297
|
-
var ListVaultReuseResponse = external_exports.object({
|
|
23298
|
-
// Reused values across the whole store — the number the section's claim
|
|
23299
|
-
// ("values detected in more than one place") is about.
|
|
23300
|
-
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23301
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23302
|
-
nextCursor: external_exports.string().nullable()
|
|
23303
|
-
});
|
|
23304
|
-
var ListVaultDerefsQuery = external_exports.object({
|
|
23305
|
-
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23306
|
-
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23307
|
-
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23308
|
-
// over a Server Action, which preserves the type, never as a URL param.
|
|
23309
|
-
includeBatched: external_exports.boolean().optional(),
|
|
23310
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23311
|
-
cursor: external_exports.string().optional()
|
|
23312
|
-
});
|
|
23313
|
-
var ListVaultDerefsResponse = external_exports.object({
|
|
23314
|
-
items: external_exports.array(VaultDeref),
|
|
23315
|
-
nextCursor: external_exports.string().nullable(),
|
|
23316
|
-
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23317
|
-
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23318
|
-
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23319
|
-
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23320
|
-
});
|
|
23321
|
-
var VaultKeyCustody = external_exports.string();
|
|
23322
|
-
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23323
|
-
var VAULT_CONSENT_VERSION = 1;
|
|
23324
|
-
var VaultConsent = external_exports.object({
|
|
23325
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23326
|
-
version: external_exports.number().int().positive()
|
|
23327
|
-
});
|
|
23328
|
-
function isVaultConsentValid(consent) {
|
|
23329
|
-
return consent?.version === VAULT_CONSENT_VERSION;
|
|
23330
|
-
}
|
|
23331
|
-
|
|
23332
|
-
// ../../packages/schema/src/zod/local.ts
|
|
23333
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION = 6;
|
|
23334
|
-
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23335
|
-
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23336
|
-
var ControlPlaneConnection = external_exports.object({
|
|
23337
|
-
endpoint: external_exports.string().min(1),
|
|
23338
|
-
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23339
|
-
label: external_exports.string().min(1).optional(),
|
|
23340
|
-
attachedAt: external_exports.iso.datetime()
|
|
23341
|
-
}).meta({ id: "ControlPlaneConnection" });
|
|
23342
|
-
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23343
|
-
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23344
|
-
var ModelJudgeConsent = external_exports.object({
|
|
23345
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23346
|
-
payloadVersion: external_exports.number().int().positive()
|
|
23347
|
-
});
|
|
23348
|
-
var HistorySyncConsent = external_exports.object({
|
|
23349
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23350
|
-
payloadVersion: external_exports.number().int().positive(),
|
|
23351
|
-
endpoint: external_exports.string()
|
|
23352
|
-
});
|
|
23353
|
-
var WorkspaceSettings = external_exports.object({
|
|
23354
|
-
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23355
|
-
runMode: RunMode.default("standalone"),
|
|
23356
|
-
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23357
|
-
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23358
|
-
controlPlane: ControlPlaneConnection.optional(),
|
|
23359
|
-
policy: SimpleDetectionPolicy.default("redact"),
|
|
23360
|
-
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23361
|
-
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23362
|
-
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23363
|
-
// Shares writes.
|
|
23364
|
-
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23365
|
-
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23366
|
-
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23367
|
-
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23368
|
-
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23369
|
-
// purging the vault is the eraser.
|
|
23370
|
-
vaultConsent: VaultConsent.optional(),
|
|
23371
|
-
// Where the vault master key lives.
|
|
23372
|
-
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23373
|
-
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23374
|
-
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23375
|
-
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23376
|
-
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23377
|
-
// Records that the user consented to sending findings to the model API for
|
|
23378
|
-
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23379
|
-
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23380
|
-
// covers the current payload and must be re-granted.
|
|
23381
|
-
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23382
|
-
// Records that the user consented to sending the activity already recorded on
|
|
23383
|
-
// this machine to the deployment it is attached to, along with the payload
|
|
23384
|
-
// shape and the endpoint they agreed to. Absent until granted, and a grant for
|
|
23385
|
-
// a different endpoint or an older payload no longer counts.
|
|
23386
|
-
historySyncConsent: HistorySyncConsent.optional()
|
|
23387
|
-
});
|
|
23388
|
-
function defaultWorkspaceSettings() {
|
|
23389
|
-
return WorkspaceSettings.parse({});
|
|
23390
|
-
}
|
|
23391
|
-
function isAttached(settings) {
|
|
23392
|
-
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23393
|
-
}
|
|
23394
|
-
function toInventoryRow(input2, id, now) {
|
|
23395
|
-
return {
|
|
23396
|
-
id,
|
|
23397
|
-
objectType: input2.objectType,
|
|
23398
|
-
location: input2.location ?? null,
|
|
23399
|
-
title: input2.title ?? null,
|
|
23400
|
-
hostId: input2.hostId ?? null,
|
|
23401
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23402
|
-
firstSeen: now,
|
|
23403
|
-
lastSeen: now
|
|
23404
|
-
};
|
|
23405
|
-
}
|
|
23406
|
-
function toSourceProjectRow(input2, id, now) {
|
|
23407
|
-
return {
|
|
23408
|
-
id,
|
|
23409
|
-
url: input2.url,
|
|
23410
|
-
name: input2.name ?? null,
|
|
23411
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23412
|
-
firstSeen: now,
|
|
23413
|
-
lastSeen: now
|
|
23414
|
-
};
|
|
23415
|
-
}
|
|
23416
|
-
function toAuditEventRow(input2) {
|
|
23417
|
-
return {
|
|
23418
|
-
id: input2.id,
|
|
23419
|
-
parentId: input2.parentId ?? null,
|
|
23420
|
-
rootSessionId: input2.rootSessionId ?? null,
|
|
23421
|
-
eventType: input2.eventType,
|
|
23422
|
-
hostId: input2.hostId ?? null,
|
|
23423
|
-
harnessId: input2.harnessId ?? null,
|
|
23424
|
-
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23425
|
-
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23426
|
-
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23427
|
-
severity: input2.severity ?? null,
|
|
23428
|
-
priority: input2.priority ?? null,
|
|
23429
|
-
content: input2.content ?? null,
|
|
23430
|
-
contentHash: input2.contentHash ?? null,
|
|
23431
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23432
|
-
};
|
|
23433
|
-
}
|
|
23434
|
-
function toClassifiedDataRow(input2, id) {
|
|
23435
|
-
return {
|
|
23436
|
-
id,
|
|
23437
|
-
class: input2.class,
|
|
23438
|
-
label: input2.label ?? null,
|
|
23439
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23440
|
-
};
|
|
23441
|
-
}
|
|
23442
|
-
function toInspectionDefinitionRow(input2, id) {
|
|
23443
|
-
return {
|
|
23444
|
-
id,
|
|
23445
|
-
ruleId: input2.ruleId,
|
|
23446
|
-
name: input2.name,
|
|
23447
|
-
category: input2.category,
|
|
23448
|
-
severity: input2.severity,
|
|
23449
|
-
definition: input2.definition,
|
|
23450
|
-
version: input2.version
|
|
23451
|
-
};
|
|
23452
|
-
}
|
|
23453
|
-
function toInspectionFindingRow(input2) {
|
|
23454
|
-
return {
|
|
23455
|
-
id: input2.id,
|
|
23456
|
-
auditEventId: input2.auditEventId,
|
|
23457
|
-
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23458
|
-
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23459
|
-
spanStart: input2.span.start,
|
|
23460
|
-
spanEnd: input2.span.end,
|
|
23461
|
-
maskedMatch: input2.maskedMatch,
|
|
23462
|
-
actionTaken: input2.actionTaken,
|
|
23463
|
-
confidence: input2.confidence,
|
|
23464
|
-
findingKey: input2.findingKey ?? null,
|
|
23465
|
-
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23466
|
-
};
|
|
23467
|
-
}
|
|
23468
|
-
function toCaptureAttributes(event) {
|
|
23469
|
-
const metadata = event.metadata;
|
|
23470
|
-
return {
|
|
23471
|
-
source_tool: event.sourceTool,
|
|
23472
|
-
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23473
|
-
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23474
|
-
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23475
|
-
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23476
|
-
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23477
|
-
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23478
|
-
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23479
|
-
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23480
|
-
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23481
|
-
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23482
|
-
// has ever populated either), but every legacy metadata key still rides
|
|
23483
|
-
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23484
|
-
// `.catchall(z.unknown())` carries the long tail.
|
|
23485
|
-
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23486
|
-
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23487
|
-
};
|
|
23488
|
-
}
|
|
23489
|
-
function captureDefinitionVersion(finding) {
|
|
23490
|
-
return `capture/${finding.category}/${finding.severity}`;
|
|
23491
|
-
}
|
|
23492
|
-
function toCaptureDefinitionInput(finding) {
|
|
23493
|
-
return {
|
|
23494
|
-
ruleId: finding.ruleId,
|
|
23495
|
-
version: captureDefinitionVersion(finding),
|
|
23496
|
-
name: finding.ruleId,
|
|
23497
|
-
category: finding.category,
|
|
23498
|
-
severity: finding.severity,
|
|
23499
|
-
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
23500
|
-
};
|
|
23501
|
-
}
|
|
23502
|
-
|
|
23503
|
-
// ../../packages/schema/src/zod/managed.ts
|
|
23504
|
-
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23505
|
-
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23506
|
-
var ManagedSettingKey = external_exports.enum([
|
|
23507
|
-
"runMode",
|
|
23508
|
-
"historicalAccess",
|
|
23509
|
-
"vaultConsent",
|
|
23510
|
-
"vaultKeyCustody",
|
|
23511
|
-
"vaultInlineReveal",
|
|
23512
|
-
"modelJudgeConsent",
|
|
23513
|
-
"dataSharesInPlace"
|
|
23514
|
-
]).meta({ id: "ManagedSettingKey" });
|
|
23515
|
-
var ManagedSettingsValues = external_exports.object({
|
|
23516
|
-
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23517
|
-
controlPlane: external_exports.object({
|
|
23518
|
-
endpoint: external_exports.string().min(1),
|
|
23519
|
-
label: external_exports.string().min(1).optional()
|
|
23520
|
-
}).optional(),
|
|
23521
|
-
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23522
|
-
vaultConsent: external_exports.boolean().optional(),
|
|
23523
|
-
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23524
|
-
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23525
|
-
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23526
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23527
|
-
}).meta({ id: "ManagedSettingsValues" });
|
|
23528
|
-
var ManagedSettings = external_exports.object({
|
|
23529
|
-
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23530
|
-
// Shown on every locked control, so the user can tell an administrative
|
|
23531
|
-
// decision from a bug. Absent renders as a generic "your organization".
|
|
23532
|
-
organization: external_exports.string().min(1).optional(),
|
|
23533
|
-
// What the administrator pinned.
|
|
23534
|
-
values: ManagedSettingsValues.default({}),
|
|
23535
|
-
// Which of those the user may not change. A key here with no matching value
|
|
23536
|
-
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23537
|
-
// the user may still override. The two are separable on purpose.
|
|
23538
|
-
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23539
|
-
}).meta({ id: "ManagedSettings" });
|
|
23540
|
-
|
|
23541
23221
|
// ../../packages/schema/src/zod/policy.ts
|
|
23542
23222
|
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23543
23223
|
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23224
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23225
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23544
23226
|
var Policy = external_exports.object({
|
|
23545
23227
|
id: external_exports.guid(),
|
|
23546
23228
|
scope: PolicyScope,
|
|
@@ -23550,7 +23232,27 @@ var Policy = external_exports.object({
|
|
|
23550
23232
|
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23551
23233
|
// Display name — optional so older policy rows without name still parse.
|
|
23552
23234
|
// Added for the findings API (policy.name column migration).
|
|
23553
|
-
name: external_exports.string().optional()
|
|
23235
|
+
name: external_exports.string().optional(),
|
|
23236
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23237
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23238
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23239
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23240
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23241
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23242
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23243
|
+
//
|
|
23244
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23245
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23246
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23247
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23248
|
+
// — the CRUD routes neither accept nor set it.
|
|
23249
|
+
//
|
|
23250
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23251
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23252
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23253
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23254
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23255
|
+
provenance: PolicyProvenance.optional()
|
|
23554
23256
|
}).meta({ id: "Policy" });
|
|
23555
23257
|
var PolicyBundle = external_exports.object({
|
|
23556
23258
|
version: external_exports.string(),
|
|
@@ -23602,6 +23304,12 @@ var PolicyBundle = external_exports.object({
|
|
|
23602
23304
|
customKeywords: external_exports.array(external_exports.string()),
|
|
23603
23305
|
fetchedAt: external_exports.iso.datetime()
|
|
23604
23306
|
}).meta({ id: "PolicyBundle" });
|
|
23307
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23308
|
+
...Object.keys(PolicyBundle.shape),
|
|
23309
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23310
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23311
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23312
|
+
].sort().join(",");
|
|
23605
23313
|
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23606
23314
|
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23607
23315
|
var CATEGORY_PEAK_SEVERITY = {
|
|
@@ -23622,9 +23330,11 @@ function severityFloorPolicy(category) {
|
|
|
23622
23330
|
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23623
23331
|
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23624
23332
|
}
|
|
23625
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23626
23333
|
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23627
23334
|
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23335
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23336
|
+
id: "RedactFallback"
|
|
23337
|
+
});
|
|
23628
23338
|
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23629
23339
|
var BUILTIN_POLICY_SPECS = {
|
|
23630
23340
|
monitor: {
|
|
@@ -23661,6 +23371,42 @@ var BUILTIN_POLICY_SPECS = {
|
|
|
23661
23371
|
function builtinPolicyToAction(id) {
|
|
23662
23372
|
return BUILTIN_POLICY_SPECS[id].action;
|
|
23663
23373
|
}
|
|
23374
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23375
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23376
|
+
];
|
|
23377
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23378
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23379
|
+
);
|
|
23380
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23381
|
+
...BELOW_PALETTE,
|
|
23382
|
+
...PALETTE_WEAKEST_FIRST
|
|
23383
|
+
];
|
|
23384
|
+
function actionRank(action) {
|
|
23385
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23386
|
+
}
|
|
23387
|
+
function isActionAtLeast(action, floor) {
|
|
23388
|
+
return actionRank(action) >= actionRank(floor);
|
|
23389
|
+
}
|
|
23390
|
+
function strongerAction(a, b) {
|
|
23391
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23392
|
+
}
|
|
23393
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23394
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23395
|
+
}
|
|
23396
|
+
var PackPolicyFloor = external_exports.object({
|
|
23397
|
+
/**
|
|
23398
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23399
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23400
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23401
|
+
*/
|
|
23402
|
+
floor: BuiltinPolicyId,
|
|
23403
|
+
/**
|
|
23404
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23405
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23406
|
+
* re-assignable locally in either direction.
|
|
23407
|
+
*/
|
|
23408
|
+
locked: external_exports.boolean()
|
|
23409
|
+
}).describe("PackPolicyFloor");
|
|
23664
23410
|
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23665
23411
|
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23666
23412
|
);
|
|
@@ -23718,6 +23464,404 @@ var PolicyStatsResponse = external_exports.object({
|
|
|
23718
23464
|
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23719
23465
|
}).meta({ id: "PolicyStatsResponse" });
|
|
23720
23466
|
|
|
23467
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
23468
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
23469
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23470
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23471
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23472
|
+
);
|
|
23473
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23474
|
+
function pointerTokenScanner() {
|
|
23475
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23476
|
+
}
|
|
23477
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23478
|
+
var ParsedPointer = external_exports.object({
|
|
23479
|
+
category: DetectionCategory,
|
|
23480
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23481
|
+
pointerId: external_exports.string(),
|
|
23482
|
+
tag: external_exports.string()
|
|
23483
|
+
});
|
|
23484
|
+
var VaultEntry = external_exports.object({
|
|
23485
|
+
pointerId: external_exports.string(),
|
|
23486
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23487
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23488
|
+
// independently of the vault encryption key below.
|
|
23489
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23490
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23491
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23492
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23493
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
23494
|
+
// different rule's category keeps the category it was minted with, so one
|
|
23495
|
+
// value always produces exactly one wire token.
|
|
23496
|
+
category: DetectionCategory,
|
|
23497
|
+
ruleId: external_exports.string(),
|
|
23498
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23499
|
+
maskedMatch: external_exports.string(),
|
|
23500
|
+
provider: external_exports.string().optional(),
|
|
23501
|
+
ciphertext: external_exports.string(),
|
|
23502
|
+
nonce: external_exports.string(),
|
|
23503
|
+
authTag: external_exports.string(),
|
|
23504
|
+
// How many times this value has been detected on this machine — the reuse
|
|
23505
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23506
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23507
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23508
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23509
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23510
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23511
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23512
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23513
|
+
// the value does not expire.
|
|
23514
|
+
userAuthorized: external_exports.boolean(),
|
|
23515
|
+
firstSeen: external_exports.string(),
|
|
23516
|
+
lastSeen: external_exports.string()
|
|
23517
|
+
});
|
|
23518
|
+
var PointerDescriptor = external_exports.object({
|
|
23519
|
+
category: DetectionCategory,
|
|
23520
|
+
provider: external_exports.string().optional(),
|
|
23521
|
+
maskedMatch: external_exports.string(),
|
|
23522
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23523
|
+
firstSeen: external_exports.string(),
|
|
23524
|
+
lastSeen: external_exports.string()
|
|
23525
|
+
});
|
|
23526
|
+
var PointerIdentity = external_exports.object({
|
|
23527
|
+
ruleId: external_exports.string(),
|
|
23528
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23529
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23530
|
+
});
|
|
23531
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23532
|
+
var VaultDerefReason = external_exports.enum([
|
|
23533
|
+
"display",
|
|
23534
|
+
"explicit-reveal",
|
|
23535
|
+
"view-render",
|
|
23536
|
+
"model-input",
|
|
23537
|
+
"remediation",
|
|
23538
|
+
"purge"
|
|
23539
|
+
]);
|
|
23540
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23541
|
+
var BATCHED_DEREF_REASONS = ["display", "view-render"];
|
|
23542
|
+
function isBatchedDerefReason(reason) {
|
|
23543
|
+
return BATCHED_DEREF_REASONS.includes(reason);
|
|
23544
|
+
}
|
|
23545
|
+
var VaultDeref = external_exports.object({
|
|
23546
|
+
id: external_exports.guid(),
|
|
23547
|
+
pointerId: external_exports.string(),
|
|
23548
|
+
at: external_exports.string(),
|
|
23549
|
+
target: DetokenizeTarget,
|
|
23550
|
+
reason: VaultDerefReason,
|
|
23551
|
+
outcome: VaultDerefOutcome,
|
|
23552
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23553
|
+
grantId: external_exports.string().optional(),
|
|
23554
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23555
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23556
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
23557
|
+
});
|
|
23558
|
+
var VaultSightingKind = external_exports.enum([
|
|
23559
|
+
"prompt",
|
|
23560
|
+
"tool-input",
|
|
23561
|
+
"tool-output",
|
|
23562
|
+
"file",
|
|
23563
|
+
"transcript"
|
|
23564
|
+
]);
|
|
23565
|
+
var VaultSighting = external_exports.object({
|
|
23566
|
+
location: external_exports.string(),
|
|
23567
|
+
kind: VaultSightingKind,
|
|
23568
|
+
firstSeen: external_exports.string(),
|
|
23569
|
+
lastSeen: external_exports.string()
|
|
23570
|
+
});
|
|
23571
|
+
var VaultInventoryEntry = external_exports.object({
|
|
23572
|
+
pointerId: external_exports.string(),
|
|
23573
|
+
category: DetectionCategory,
|
|
23574
|
+
provider: external_exports.string().optional(),
|
|
23575
|
+
maskedMatch: external_exports.string(),
|
|
23576
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23577
|
+
firstSeen: external_exports.string(),
|
|
23578
|
+
lastSeen: external_exports.string(),
|
|
23579
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
23580
|
+
// the inventory badges it, the row links to revocation.
|
|
23581
|
+
revealGrantId: external_exports.string().nullable(),
|
|
23582
|
+
sightings: external_exports.array(VaultSighting)
|
|
23583
|
+
});
|
|
23584
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23585
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23586
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23587
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
23588
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23589
|
+
// Opaque; names the last row of the page just served.
|
|
23590
|
+
cursor: external_exports.string().optional()
|
|
23591
|
+
});
|
|
23592
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
23593
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
23594
|
+
// independent, so paging never changes what the count claims.
|
|
23595
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23596
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23597
|
+
// `null` once the last page is reached.
|
|
23598
|
+
nextCursor: external_exports.string().nullable()
|
|
23599
|
+
});
|
|
23600
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
23601
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23602
|
+
cursor: external_exports.string().optional()
|
|
23603
|
+
});
|
|
23604
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
23605
|
+
// Reused values across the whole store — the number the section's claim
|
|
23606
|
+
// ("values detected in more than one place") is about.
|
|
23607
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23608
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23609
|
+
nextCursor: external_exports.string().nullable()
|
|
23610
|
+
});
|
|
23611
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
23612
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23613
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23614
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23615
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
23616
|
+
includeBatched: external_exports.boolean().optional(),
|
|
23617
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23618
|
+
cursor: external_exports.string().optional()
|
|
23619
|
+
});
|
|
23620
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
23621
|
+
items: external_exports.array(VaultDeref),
|
|
23622
|
+
nextCursor: external_exports.string().nullable(),
|
|
23623
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23624
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23625
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23626
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23627
|
+
});
|
|
23628
|
+
var VaultKeyCustody = external_exports.string();
|
|
23629
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23630
|
+
var VAULT_CONSENT_VERSION = 1;
|
|
23631
|
+
var VaultConsent = external_exports.object({
|
|
23632
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23633
|
+
version: external_exports.number().int().positive()
|
|
23634
|
+
});
|
|
23635
|
+
function isVaultConsentValid(consent) {
|
|
23636
|
+
return consent?.version === VAULT_CONSENT_VERSION;
|
|
23637
|
+
}
|
|
23638
|
+
|
|
23639
|
+
// ../../packages/schema/src/zod/local.ts
|
|
23640
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23641
|
+
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23642
|
+
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23643
|
+
var ControlPlaneConnection = external_exports.object({
|
|
23644
|
+
endpoint: external_exports.string().min(1),
|
|
23645
|
+
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23646
|
+
label: external_exports.string().min(1).optional(),
|
|
23647
|
+
attachedAt: external_exports.iso.datetime()
|
|
23648
|
+
}).meta({ id: "ControlPlaneConnection" });
|
|
23649
|
+
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23650
|
+
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23651
|
+
var ModelJudgeConsent = external_exports.object({
|
|
23652
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23653
|
+
payloadVersion: external_exports.number().int().positive()
|
|
23654
|
+
});
|
|
23655
|
+
var HistorySyncConsent = external_exports.object({
|
|
23656
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23657
|
+
payloadVersion: external_exports.number().int().positive(),
|
|
23658
|
+
endpoint: external_exports.string()
|
|
23659
|
+
});
|
|
23660
|
+
var WorkspaceSettings = external_exports.object({
|
|
23661
|
+
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23662
|
+
runMode: RunMode.default("standalone"),
|
|
23663
|
+
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23664
|
+
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23665
|
+
controlPlane: ControlPlaneConnection.optional(),
|
|
23666
|
+
policy: SimpleDetectionPolicy.default("redact"),
|
|
23667
|
+
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23668
|
+
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23669
|
+
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23670
|
+
// Shares writes.
|
|
23671
|
+
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23672
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23673
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23674
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23675
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23676
|
+
// purging the vault is the eraser.
|
|
23677
|
+
vaultConsent: VaultConsent.optional(),
|
|
23678
|
+
// Where the vault master key lives.
|
|
23679
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23680
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23681
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23682
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23683
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23684
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23685
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23686
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23687
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23688
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23689
|
+
//
|
|
23690
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23691
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23692
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23693
|
+
// word and stays out of the stored value.
|
|
23694
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23695
|
+
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23696
|
+
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23697
|
+
// Records that the user consented to sending findings to the model API for
|
|
23698
|
+
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23699
|
+
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23700
|
+
// covers the current payload and must be re-granted.
|
|
23701
|
+
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23702
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23703
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23704
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23705
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23706
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23707
|
+
// payload no longer counts.
|
|
23708
|
+
historySyncConsent: HistorySyncConsent.optional()
|
|
23709
|
+
});
|
|
23710
|
+
function defaultWorkspaceSettings() {
|
|
23711
|
+
return WorkspaceSettings.parse({});
|
|
23712
|
+
}
|
|
23713
|
+
function isAttached(settings) {
|
|
23714
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23715
|
+
}
|
|
23716
|
+
function toInventoryRow(input2, id, now) {
|
|
23717
|
+
return {
|
|
23718
|
+
id,
|
|
23719
|
+
objectType: input2.objectType,
|
|
23720
|
+
location: input2.location ?? null,
|
|
23721
|
+
title: input2.title ?? null,
|
|
23722
|
+
hostId: input2.hostId ?? null,
|
|
23723
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23724
|
+
firstSeen: now,
|
|
23725
|
+
lastSeen: now
|
|
23726
|
+
};
|
|
23727
|
+
}
|
|
23728
|
+
function toSourceProjectRow(input2, id, now) {
|
|
23729
|
+
return {
|
|
23730
|
+
id,
|
|
23731
|
+
url: input2.url,
|
|
23732
|
+
name: input2.name ?? null,
|
|
23733
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23734
|
+
firstSeen: now,
|
|
23735
|
+
lastSeen: now
|
|
23736
|
+
};
|
|
23737
|
+
}
|
|
23738
|
+
function toAuditEventRow(input2) {
|
|
23739
|
+
return {
|
|
23740
|
+
id: input2.id,
|
|
23741
|
+
parentId: input2.parentId ?? null,
|
|
23742
|
+
rootSessionId: input2.rootSessionId ?? null,
|
|
23743
|
+
eventType: input2.eventType,
|
|
23744
|
+
hostId: input2.hostId ?? null,
|
|
23745
|
+
harnessId: input2.harnessId ?? null,
|
|
23746
|
+
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23747
|
+
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23748
|
+
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23749
|
+
severity: input2.severity ?? null,
|
|
23750
|
+
priority: input2.priority ?? null,
|
|
23751
|
+
content: input2.content ?? null,
|
|
23752
|
+
contentHash: input2.contentHash ?? null,
|
|
23753
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23754
|
+
};
|
|
23755
|
+
}
|
|
23756
|
+
function toClassifiedDataRow(input2, id) {
|
|
23757
|
+
return {
|
|
23758
|
+
id,
|
|
23759
|
+
class: input2.class,
|
|
23760
|
+
label: input2.label ?? null,
|
|
23761
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23762
|
+
};
|
|
23763
|
+
}
|
|
23764
|
+
function toInspectionDefinitionRow(input2, id) {
|
|
23765
|
+
return {
|
|
23766
|
+
id,
|
|
23767
|
+
ruleId: input2.ruleId,
|
|
23768
|
+
name: input2.name,
|
|
23769
|
+
category: input2.category,
|
|
23770
|
+
severity: input2.severity,
|
|
23771
|
+
definition: input2.definition,
|
|
23772
|
+
version: input2.version
|
|
23773
|
+
};
|
|
23774
|
+
}
|
|
23775
|
+
function toInspectionFindingRow(input2) {
|
|
23776
|
+
return {
|
|
23777
|
+
id: input2.id,
|
|
23778
|
+
auditEventId: input2.auditEventId,
|
|
23779
|
+
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23780
|
+
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23781
|
+
spanStart: input2.span.start,
|
|
23782
|
+
spanEnd: input2.span.end,
|
|
23783
|
+
maskedMatch: input2.maskedMatch,
|
|
23784
|
+
actionTaken: input2.actionTaken,
|
|
23785
|
+
confidence: input2.confidence,
|
|
23786
|
+
findingKey: input2.findingKey ?? null,
|
|
23787
|
+
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23788
|
+
};
|
|
23789
|
+
}
|
|
23790
|
+
function toCaptureAttributes(event) {
|
|
23791
|
+
const metadata = event.metadata;
|
|
23792
|
+
return {
|
|
23793
|
+
source_tool: event.sourceTool,
|
|
23794
|
+
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23795
|
+
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23796
|
+
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23797
|
+
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23798
|
+
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23799
|
+
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23800
|
+
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23801
|
+
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23802
|
+
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23803
|
+
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23804
|
+
// has ever populated either), but every legacy metadata key still rides
|
|
23805
|
+
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23806
|
+
// `.catchall(z.unknown())` carries the long tail.
|
|
23807
|
+
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23808
|
+
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23809
|
+
};
|
|
23810
|
+
}
|
|
23811
|
+
function captureDefinitionVersion(finding) {
|
|
23812
|
+
return `capture/${finding.category}/${finding.severity}`;
|
|
23813
|
+
}
|
|
23814
|
+
function toCaptureDefinitionInput(finding) {
|
|
23815
|
+
return {
|
|
23816
|
+
ruleId: finding.ruleId,
|
|
23817
|
+
version: captureDefinitionVersion(finding),
|
|
23818
|
+
name: finding.ruleId,
|
|
23819
|
+
category: finding.category,
|
|
23820
|
+
severity: finding.severity,
|
|
23821
|
+
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
23822
|
+
};
|
|
23823
|
+
}
|
|
23824
|
+
|
|
23825
|
+
// ../../packages/schema/src/zod/managed.ts
|
|
23826
|
+
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23827
|
+
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23828
|
+
var ManagedSettingKey = external_exports.enum([
|
|
23829
|
+
"runMode",
|
|
23830
|
+
"historicalAccess",
|
|
23831
|
+
"vaultConsent",
|
|
23832
|
+
"vaultKeyCustody",
|
|
23833
|
+
"vaultInlineReveal",
|
|
23834
|
+
"modelJudgeConsent",
|
|
23835
|
+
"dataSharesInPlace",
|
|
23836
|
+
"redactFallback"
|
|
23837
|
+
]).meta({ id: "ManagedSettingKey" });
|
|
23838
|
+
var ManagedSettingsValues = external_exports.object({
|
|
23839
|
+
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23840
|
+
controlPlane: external_exports.object({
|
|
23841
|
+
endpoint: external_exports.string().min(1),
|
|
23842
|
+
label: external_exports.string().min(1).optional()
|
|
23843
|
+
}).optional(),
|
|
23844
|
+
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23845
|
+
vaultConsent: external_exports.boolean().optional(),
|
|
23846
|
+
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23847
|
+
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23848
|
+
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23849
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23850
|
+
redactFallback: RedactFallback.optional()
|
|
23851
|
+
}).meta({ id: "ManagedSettingsValues" });
|
|
23852
|
+
var ManagedSettings = external_exports.object({
|
|
23853
|
+
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23854
|
+
// Shown on every locked control, so the user can tell an administrative
|
|
23855
|
+
// decision from a bug. Absent renders as a generic "your organization".
|
|
23856
|
+
organization: external_exports.string().min(1).optional(),
|
|
23857
|
+
// What the administrator pinned.
|
|
23858
|
+
values: ManagedSettingsValues.default({}),
|
|
23859
|
+
// Which of those the user may not change. A key here with no matching value
|
|
23860
|
+
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23861
|
+
// the user may still override. The two are separable on purpose.
|
|
23862
|
+
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23863
|
+
}).meta({ id: "ManagedSettings" });
|
|
23864
|
+
|
|
23721
23865
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23722
23866
|
var ProjectFileInput = external_exports.object({
|
|
23723
23867
|
path: external_exports.string().min(1),
|
|
@@ -23963,10 +24107,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
23963
24107
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
23964
24108
|
|
|
23965
24109
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24110
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24111
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
23966
24112
|
var SaveSettingsInput = external_exports.object({
|
|
23967
24113
|
historicalAccess: external_exports.string(),
|
|
23968
|
-
modelJudgeConsent:
|
|
23969
|
-
historySyncConsent:
|
|
24114
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24115
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
23970
24116
|
vaultConsent: external_exports.string(),
|
|
23971
24117
|
vaultInlineReveal: external_exports.string()
|
|
23972
24118
|
});
|
|
@@ -24116,9 +24262,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24116
24262
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24117
24263
|
return reasons;
|
|
24118
24264
|
}
|
|
24119
|
-
function buildReviewInfo(trust, transports) {
|
|
24265
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24120
24266
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24121
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24267
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24122
24268
|
}
|
|
24123
24269
|
function distinctTransports(transports) {
|
|
24124
24270
|
return Array.from(new Set(transports));
|
|
@@ -24336,8 +24482,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
|
|
|
24336
24482
|
}
|
|
24337
24483
|
|
|
24338
24484
|
// ../../packages/persistence/src/database.ts
|
|
24339
|
-
import { randomUUID as
|
|
24340
|
-
import { join as
|
|
24485
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24486
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24341
24487
|
import { DatabaseSync } from "node:sqlite";
|
|
24342
24488
|
|
|
24343
24489
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24602,6 +24748,10 @@ function allRows(stmt, params) {
|
|
|
24602
24748
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24603
24749
|
return stmt.all(params);
|
|
24604
24750
|
}
|
|
24751
|
+
function* iterateRows(stmt, params) {
|
|
24752
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24753
|
+
for (const row of rows) yield row;
|
|
24754
|
+
}
|
|
24605
24755
|
function getRow(stmt, params) {
|
|
24606
24756
|
if (params === void 0) return stmt.get();
|
|
24607
24757
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -25070,10 +25220,17 @@ function ensureSyncedAtColumn(db, table2) {
|
|
|
25070
25220
|
if (!columns.includes("sync_claimed_at")) {
|
|
25071
25221
|
db.exec(`ALTER TABLE ${table2} ADD COLUMN sync_claimed_at integer`);
|
|
25072
25222
|
}
|
|
25223
|
+
if (!columns.includes("outbox_owed")) {
|
|
25224
|
+
db.exec(`ALTER TABLE ${table2} ADD COLUMN outbox_owed integer`);
|
|
25225
|
+
}
|
|
25073
25226
|
db.exec(
|
|
25074
25227
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
25075
25228
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
25076
25229
|
);
|
|
25230
|
+
db.exec(
|
|
25231
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25232
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25233
|
+
);
|
|
25077
25234
|
db.exec(
|
|
25078
25235
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
25079
25236
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25178,7 +25335,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25178
25335
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25179
25336
|
var DAY_MS = 864e5;
|
|
25180
25337
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25181
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25182
25338
|
function defaultTimeZone() {
|
|
25183
25339
|
try {
|
|
25184
25340
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25233,6 +25389,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25233
25389
|
error: "error",
|
|
25234
25390
|
active: "active"
|
|
25235
25391
|
};
|
|
25392
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25236
25393
|
function safeParseStringArray(raw) {
|
|
25237
25394
|
if (!raw) return [];
|
|
25238
25395
|
const parsed2 = safeJson(raw, null);
|
|
@@ -25306,6 +25463,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25306
25463
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25307
25464
|
json_extract(attributes, '$.internal') AS internal,
|
|
25308
25465
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25466
|
+
var LLM_USAGE_SELECT = `
|
|
25467
|
+
SELECT root_session_id AS sessionId,
|
|
25468
|
+
provider,
|
|
25469
|
+
model,
|
|
25470
|
+
service_tier AS serviceTier,
|
|
25471
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25472
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25473
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25474
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25475
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25476
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25477
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25478
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25479
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25480
|
+
function usageLeaves(rows) {
|
|
25481
|
+
return rows.map((row) => {
|
|
25482
|
+
const attributes = {
|
|
25483
|
+
input_tokens: row.inputTokens,
|
|
25484
|
+
output_tokens: row.outputTokens,
|
|
25485
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25486
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25487
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25488
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25489
|
+
web_search_requests: row.webSearchRequests
|
|
25490
|
+
};
|
|
25491
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25492
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25493
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25494
|
+
return { sessionId: row.sessionId, attributes };
|
|
25495
|
+
});
|
|
25496
|
+
}
|
|
25309
25497
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25310
25498
|
var HAS_ACTIVITY = `EXISTS (
|
|
25311
25499
|
SELECT 1 FROM audit_events c
|
|
@@ -25331,16 +25519,17 @@ var SqliteActivityRepository = class {
|
|
|
25331
25519
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25332
25520
|
const liveNow = countScalar(
|
|
25333
25521
|
this.db,
|
|
25334
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25522
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25335
25523
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25336
|
-
AND
|
|
25337
|
-
|
|
25338
|
-
|
|
25339
|
-
|
|
25340
|
-
|
|
25341
|
-
|
|
25342
|
-
|
|
25343
|
-
|
|
25524
|
+
AND s.id IN (
|
|
25525
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25526
|
+
UNION
|
|
25527
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25528
|
+
WHERE started_at >= ?
|
|
25529
|
+
UNION
|
|
25530
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25531
|
+
WHERE ended_at >= ?)`,
|
|
25532
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25344
25533
|
);
|
|
25345
25534
|
const toolCallsToday = countScalar(
|
|
25346
25535
|
this.db,
|
|
@@ -25470,7 +25659,7 @@ var SqliteActivityRepository = class {
|
|
|
25470
25659
|
this.db.prepare(
|
|
25471
25660
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25472
25661
|
FROM audit_events
|
|
25473
|
-
WHERE id = ? OR root_session_id = ?
|
|
25662
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25474
25663
|
ORDER BY started_at ASC, id ASC`
|
|
25475
25664
|
),
|
|
25476
25665
|
[sessionId, sessionId]
|
|
@@ -25483,14 +25672,14 @@ var SqliteActivityRepository = class {
|
|
|
25483
25672
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25484
25673
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25485
25674
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25486
|
-
FROM audit_events
|
|
25675
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25487
25676
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25488
25677
|
),
|
|
25489
25678
|
[sessionId]
|
|
25490
25679
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25491
25680
|
const primaryModel = getRow(
|
|
25492
25681
|
this.db.prepare(
|
|
25493
|
-
`SELECT model, provider FROM audit_events
|
|
25682
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25494
25683
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25495
25684
|
ORDER BY started_at ASC, id ASC
|
|
25496
25685
|
LIMIT 1`
|
|
@@ -25501,7 +25690,7 @@ var SqliteActivityRepository = class {
|
|
|
25501
25690
|
this.db.prepare(
|
|
25502
25691
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25503
25692
|
count(*) AS n
|
|
25504
|
-
FROM audit_events
|
|
25693
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25505
25694
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25506
25695
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25507
25696
|
),
|
|
@@ -25509,7 +25698,7 @@ var SqliteActivityRepository = class {
|
|
|
25509
25698
|
);
|
|
25510
25699
|
const modelRows = allRows(
|
|
25511
25700
|
this.db.prepare(
|
|
25512
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25701
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25513
25702
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25514
25703
|
ORDER BY model`
|
|
25515
25704
|
),
|
|
@@ -25518,7 +25707,7 @@ var SqliteActivityRepository = class {
|
|
|
25518
25707
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25519
25708
|
const commits = countScalar(
|
|
25520
25709
|
this.db,
|
|
25521
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25710
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25522
25711
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25523
25712
|
[sessionId]
|
|
25524
25713
|
);
|
|
@@ -25554,25 +25743,57 @@ var SqliteActivityRepository = class {
|
|
|
25554
25743
|
return Promise.resolve(session);
|
|
25555
25744
|
}
|
|
25556
25745
|
/**
|
|
25557
|
-
* Cross-session token report — every `llm_call`
|
|
25558
|
-
* `started_at >= fromMs`
|
|
25559
|
-
* USD cost DERIVED at read time via the shared
|
|
25560
|
-
*
|
|
25561
|
-
*
|
|
25562
|
-
*
|
|
25746
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25747
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25748
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25749
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25750
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25751
|
+
*
|
|
25752
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25753
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25754
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25755
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25756
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25757
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25758
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25759
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25760
|
+
* recompute the columns it could have read. The index is one every open
|
|
25761
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25762
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25763
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25764
|
+
* per call, no bag parsed.
|
|
25563
25765
|
*/
|
|
25564
25766
|
tokenReports(fromMs) {
|
|
25565
|
-
const
|
|
25566
|
-
|
|
25767
|
+
const rows = allRows(
|
|
25768
|
+
this.db.prepare(
|
|
25769
|
+
`${LLM_USAGE_SELECT}
|
|
25770
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25771
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25772
|
+
${LLM_USAGE_GROUP}`
|
|
25773
|
+
),
|
|
25774
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25775
|
+
);
|
|
25776
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25567
25777
|
}
|
|
25568
25778
|
/**
|
|
25569
|
-
* One session's token report — its `llm_call`
|
|
25570
|
-
* model) with derived cost, or `null` when the session made no
|
|
25571
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25572
|
-
* breakdown + estimated cost.
|
|
25779
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25780
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25781
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25782
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25783
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25784
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25573
25785
|
*/
|
|
25574
25786
|
tokenReportForSession(sessionId) {
|
|
25575
|
-
const
|
|
25787
|
+
const rows = allRows(
|
|
25788
|
+
this.db.prepare(
|
|
25789
|
+
`${LLM_USAGE_SELECT}
|
|
25790
|
+
FROM audit_events
|
|
25791
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25792
|
+
${LLM_USAGE_GROUP}`
|
|
25793
|
+
),
|
|
25794
|
+
[sessionId]
|
|
25795
|
+
);
|
|
25796
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25576
25797
|
return Promise.resolve(reports[0] ?? null);
|
|
25577
25798
|
}
|
|
25578
25799
|
/**
|
|
@@ -25596,42 +25817,6 @@ var SqliteActivityRepository = class {
|
|
|
25596
25817
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25597
25818
|
return Promise.resolve([...seen]);
|
|
25598
25819
|
}
|
|
25599
|
-
/**
|
|
25600
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25601
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25602
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25603
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25604
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25605
|
-
*/
|
|
25606
|
-
readLlmCallLeaves(opts = {}) {
|
|
25607
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25608
|
-
const params = [];
|
|
25609
|
-
if (opts.sessionId !== void 0) {
|
|
25610
|
-
conditions.push("root_session_id = ?");
|
|
25611
|
-
params.push(opts.sessionId);
|
|
25612
|
-
}
|
|
25613
|
-
if (opts.fromMs !== void 0) {
|
|
25614
|
-
conditions.push("started_at >= ?");
|
|
25615
|
-
params.push(opts.fromMs);
|
|
25616
|
-
}
|
|
25617
|
-
const rows = allRows(
|
|
25618
|
-
this.db.prepare(
|
|
25619
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25620
|
-
FROM audit_events
|
|
25621
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25622
|
-
),
|
|
25623
|
-
params
|
|
25624
|
-
);
|
|
25625
|
-
return mapRowsTolerant(
|
|
25626
|
-
rows.filter(
|
|
25627
|
-
(row) => row.sessionId !== null
|
|
25628
|
-
),
|
|
25629
|
-
(row) => ({
|
|
25630
|
-
sessionId: row.sessionId,
|
|
25631
|
-
attributes: JSON.parse(row.attributes)
|
|
25632
|
-
})
|
|
25633
|
-
);
|
|
25634
|
-
}
|
|
25635
25820
|
/**
|
|
25636
25821
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25637
25822
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25646,20 +25831,23 @@ var SqliteActivityRepository = class {
|
|
|
25646
25831
|
const inClause = placeholders(sessionIds.length);
|
|
25647
25832
|
const lastActivityRows = allRows(
|
|
25648
25833
|
this.db.prepare(
|
|
25649
|
-
`SELECT
|
|
25650
|
-
|
|
25651
|
-
|
|
25834
|
+
`SELECT ids.value AS id,
|
|
25835
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25836
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25837
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25838
|
+
FROM json_each(?) AS ids`
|
|
25652
25839
|
),
|
|
25653
|
-
sessionIds
|
|
25840
|
+
[JSON.stringify(sessionIds)]
|
|
25654
25841
|
);
|
|
25655
25842
|
for (const row of lastActivityRows) {
|
|
25656
|
-
if (row.id === null) continue;
|
|
25657
25843
|
const entry = result.get(row.id);
|
|
25658
|
-
|
|
25844
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25845
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25659
25846
|
}
|
|
25660
25847
|
const turnsRows = allRows(
|
|
25661
25848
|
this.db.prepare(
|
|
25662
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25849
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25850
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25663
25851
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25664
25852
|
GROUP BY root_session_id`
|
|
25665
25853
|
),
|
|
@@ -25674,7 +25862,7 @@ var SqliteActivityRepository = class {
|
|
|
25674
25862
|
this.db.prepare(
|
|
25675
25863
|
`SELECT root_session_id AS id,
|
|
25676
25864
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25677
|
-
FROM audit_events
|
|
25865
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25678
25866
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25679
25867
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25680
25868
|
GROUP BY root_session_id`
|
|
@@ -25704,7 +25892,7 @@ var SqliteActivityRepository = class {
|
|
|
25704
25892
|
this.db.prepare(
|
|
25705
25893
|
`SELECT root_session_id AS id,
|
|
25706
25894
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25707
|
-
FROM audit_events
|
|
25895
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25708
25896
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25709
25897
|
GROUP BY root_session_id`
|
|
25710
25898
|
),
|
|
@@ -26733,7 +26921,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26733
26921
|
|
|
26734
26922
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26735
26923
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26736
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26737
26924
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26738
26925
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26739
26926
|
function compareLocationOrder(a, b) {
|
|
@@ -26762,6 +26949,25 @@ function deriveInstanceStatus(row) {
|
|
|
26762
26949
|
latestResolutionStatus: row.latest_status
|
|
26763
26950
|
});
|
|
26764
26951
|
}
|
|
26952
|
+
function toFlatFindingRow(r) {
|
|
26953
|
+
return {
|
|
26954
|
+
id: r.id,
|
|
26955
|
+
ruleId: r.rule_id,
|
|
26956
|
+
category: r.category,
|
|
26957
|
+
severity: r.severity,
|
|
26958
|
+
maskedMatch: r.masked_match,
|
|
26959
|
+
actionTaken: r.action_taken,
|
|
26960
|
+
confidence: r.confidence,
|
|
26961
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26962
|
+
sourceTool: r.source_tool,
|
|
26963
|
+
repo: r.repo ?? "",
|
|
26964
|
+
file: r.file ?? "",
|
|
26965
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26966
|
+
eventId: r.event_id,
|
|
26967
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26968
|
+
status: deriveInstanceStatus(r)
|
|
26969
|
+
};
|
|
26970
|
+
}
|
|
26765
26971
|
function encodeGroupCursor(group) {
|
|
26766
26972
|
const payload = {
|
|
26767
26973
|
sev: group.severity,
|
|
@@ -26837,7 +27043,7 @@ var SqliteFindingsRepository = class {
|
|
|
26837
27043
|
this.db.prepare(
|
|
26838
27044
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26839
27045
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26840
|
-
|
|
27046
|
+
e.source_tool AS source_tool,
|
|
26841
27047
|
e.event_type AS kind
|
|
26842
27048
|
FROM audit_events e
|
|
26843
27049
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -26945,56 +27151,11 @@ var SqliteFindingsRepository = class {
|
|
|
26945
27151
|
predicate,
|
|
26946
27152
|
params: sessionParams
|
|
26947
27153
|
});
|
|
26948
|
-
const rows =
|
|
26949
|
-
|
|
26950
|
-
|
|
26951
|
-
|
|
26952
|
-
|
|
26953
|
-
FROM (
|
|
26954
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
26955
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
26956
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
26957
|
-
e.started_at AS occurred_at,
|
|
26958
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
26959
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
26960
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
26961
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
26962
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
26963
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
26964
|
-
latest.status AS latest_status,
|
|
26965
|
-
ROW_NUMBER() OVER (
|
|
26966
|
-
PARTITION BY d.rule_id
|
|
26967
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
26968
|
-
) AS rn
|
|
26969
|
-
FROM inspection_findings f
|
|
26970
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
26971
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
26972
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
26973
|
-
ON latest.finding_key = f.finding_key
|
|
26974
|
-
${predicate}
|
|
26975
|
-
)
|
|
26976
|
-
WHERE rn <= :cap
|
|
26977
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
26978
|
-
),
|
|
26979
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
26980
|
-
);
|
|
26981
|
-
const groupable = rows.map((r) => ({
|
|
26982
|
-
id: r.id,
|
|
26983
|
-
ruleId: r.rule_id,
|
|
26984
|
-
category: r.category,
|
|
26985
|
-
severity: r.severity,
|
|
26986
|
-
maskedMatch: r.masked_match,
|
|
26987
|
-
actionTaken: r.action_taken,
|
|
26988
|
-
confidence: r.confidence,
|
|
26989
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26990
|
-
sourceTool: r.source_tool,
|
|
26991
|
-
repo: r.repo ?? "",
|
|
26992
|
-
file: r.file ?? "",
|
|
26993
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26994
|
-
eventId: r.event_id,
|
|
26995
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26996
|
-
status: deriveInstanceStatus(r)
|
|
26997
|
-
}));
|
|
27154
|
+
const rows = this.previewRows(aggregates, {
|
|
27155
|
+
sessionId: query.sessionId,
|
|
27156
|
+
from: query.from
|
|
27157
|
+
});
|
|
27158
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
26998
27159
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
26999
27160
|
const filterOpts = {
|
|
27000
27161
|
severity: query.severity,
|
|
@@ -27080,8 +27241,10 @@ var SqliteFindingsRepository = class {
|
|
|
27080
27241
|
*
|
|
27081
27242
|
* The scan runs from the top of the scope on every request, not from the
|
|
27082
27243
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
27083
|
-
* move as the caller pages. Rows
|
|
27084
|
-
*
|
|
27244
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27245
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27246
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27247
|
+
* fetched batches; only the page itself is retained.
|
|
27085
27248
|
*/
|
|
27086
27249
|
listFindingInstances(query) {
|
|
27087
27250
|
const opts = {
|
|
@@ -27097,6 +27260,10 @@ var SqliteFindingsRepository = class {
|
|
|
27097
27260
|
};
|
|
27098
27261
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27099
27262
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27263
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27264
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27265
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27266
|
+
};
|
|
27100
27267
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27101
27268
|
const items = [];
|
|
27102
27269
|
let total = 0;
|
|
@@ -27109,6 +27276,7 @@ var SqliteFindingsRepository = class {
|
|
|
27109
27276
|
accumulator.add(row);
|
|
27110
27277
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27111
27278
|
total += 1;
|
|
27279
|
+
if (!isPastCursor(row)) continue;
|
|
27112
27280
|
if (items.length < limit) {
|
|
27113
27281
|
items.push(toInstanceDetail(row));
|
|
27114
27282
|
last = row;
|
|
@@ -27117,15 +27285,6 @@ var SqliteFindingsRepository = class {
|
|
|
27117
27285
|
}
|
|
27118
27286
|
}
|
|
27119
27287
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27120
|
-
if (cursor !== null) {
|
|
27121
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27122
|
-
return Promise.resolve({
|
|
27123
|
-
totals: { findings: total },
|
|
27124
|
-
facets: accumulator.facets(),
|
|
27125
|
-
items: resumed.items,
|
|
27126
|
-
nextCursor: resumed.nextCursor
|
|
27127
|
-
});
|
|
27128
|
-
}
|
|
27129
27288
|
return Promise.resolve({
|
|
27130
27289
|
totals: { findings: total },
|
|
27131
27290
|
facets: accumulator.facets(),
|
|
@@ -27133,35 +27292,6 @@ var SqliteFindingsRepository = class {
|
|
|
27133
27292
|
nextCursor
|
|
27134
27293
|
});
|
|
27135
27294
|
}
|
|
27136
|
-
/**
|
|
27137
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27138
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27139
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27140
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27141
|
-
*/
|
|
27142
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27143
|
-
const items = [];
|
|
27144
|
-
let last;
|
|
27145
|
-
let hasMore = false;
|
|
27146
|
-
for (const row of this.scanFindingRows({
|
|
27147
|
-
sessionId: query.sessionId,
|
|
27148
|
-
from: query.from,
|
|
27149
|
-
after: cursor
|
|
27150
|
-
})) {
|
|
27151
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27152
|
-
if (items.length < limit) {
|
|
27153
|
-
items.push(toInstanceDetail(row));
|
|
27154
|
-
last = row;
|
|
27155
|
-
} else {
|
|
27156
|
-
hasMore = true;
|
|
27157
|
-
break;
|
|
27158
|
-
}
|
|
27159
|
-
}
|
|
27160
|
-
return {
|
|
27161
|
-
items,
|
|
27162
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27163
|
-
};
|
|
27164
|
-
}
|
|
27165
27295
|
/**
|
|
27166
27296
|
* The same findings folded by location: repository, then file within it.
|
|
27167
27297
|
*
|
|
@@ -27244,25 +27374,111 @@ var SqliteFindingsRepository = class {
|
|
|
27244
27374
|
});
|
|
27245
27375
|
}
|
|
27246
27376
|
/**
|
|
27247
|
-
*
|
|
27377
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27378
|
+
*
|
|
27379
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27380
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27381
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27382
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27383
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27384
|
+
* store while the answer does not.
|
|
27385
|
+
*
|
|
27386
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27387
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27388
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27389
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27390
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27391
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27392
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27393
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27394
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27395
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27396
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27397
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27398
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27399
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27400
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27401
|
+
* sort of the scope, which is still that floor.
|
|
27402
|
+
*
|
|
27403
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27404
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27405
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27406
|
+
* built from.
|
|
27407
|
+
*/
|
|
27408
|
+
previewRows(aggregates, scope) {
|
|
27409
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27410
|
+
let remaining = 0;
|
|
27411
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27412
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27413
|
+
wanted.set(ruleId, n);
|
|
27414
|
+
remaining += n;
|
|
27415
|
+
}
|
|
27416
|
+
const rows = [];
|
|
27417
|
+
if (remaining === 0) return rows;
|
|
27418
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27419
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27420
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27421
|
+
const want = wanted.get(r.rule_id);
|
|
27422
|
+
if (want === void 0) continue;
|
|
27423
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27424
|
+
if (have >= want) continue;
|
|
27425
|
+
taken.set(r.rule_id, have + 1);
|
|
27426
|
+
rows.push(r);
|
|
27427
|
+
remaining -= 1;
|
|
27428
|
+
if (remaining === 0) break;
|
|
27429
|
+
}
|
|
27430
|
+
return rows;
|
|
27431
|
+
}
|
|
27432
|
+
/**
|
|
27433
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27248
27434
|
*
|
|
27249
27435
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27250
27436
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27251
|
-
* store is far more rows than any page.
|
|
27252
|
-
*
|
|
27253
|
-
*
|
|
27437
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27438
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27439
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27440
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27441
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27442
|
+
* batch and cost the square of the scope.
|
|
27254
27443
|
*
|
|
27255
|
-
*
|
|
27256
|
-
*
|
|
27257
|
-
*
|
|
27258
|
-
*
|
|
27259
|
-
*
|
|
27260
|
-
*
|
|
27261
|
-
*
|
|
27262
|
-
* dimension — see listFindingInstances.
|
|
27444
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27445
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27446
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27447
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27448
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27449
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27450
|
+
* page-2+ request would otherwise re-seek for.
|
|
27263
27451
|
*/
|
|
27264
27452
|
*scanFindingRows(scope) {
|
|
27265
|
-
const
|
|
27453
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27454
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27455
|
+
yield toFlatFindingRow(r);
|
|
27456
|
+
}
|
|
27457
|
+
}
|
|
27458
|
+
/**
|
|
27459
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27460
|
+
* joined to its event and definition, newest first.
|
|
27461
|
+
*
|
|
27462
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27463
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27464
|
+
*
|
|
27465
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27466
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27467
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27468
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27469
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27470
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27471
|
+
* ordered within the session — and the order falls out of the index.
|
|
27472
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27473
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27474
|
+
*
|
|
27475
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27476
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27477
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27478
|
+
* table would be materialized before the first row streamed.
|
|
27479
|
+
*/
|
|
27480
|
+
findingScanSql(scope) {
|
|
27481
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27266
27482
|
const params = [];
|
|
27267
27483
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27268
27484
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27276,58 +27492,24 @@ var SqliteFindingsRepository = class {
|
|
|
27276
27492
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27277
27493
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27278
27494
|
e.started_at AS occurred_at,
|
|
27279
|
-
|
|
27280
|
-
|
|
27281
|
-
|
|
27282
|
-
|
|
27495
|
+
e.source_tool AS source_tool,
|
|
27496
|
+
e.repo AS repo,
|
|
27497
|
+
e.file_path AS file,
|
|
27498
|
+
e.tool_name AS tool_name,
|
|
27283
27499
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27284
27500
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27285
27501
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27286
|
-
FROM
|
|
27287
|
-
JOIN
|
|
27288
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27502
|
+
FROM audit_events e
|
|
27503
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27504
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27289
27505
|
WHERE ${conditions.join(" AND ")}
|
|
27290
|
-
|
|
27291
|
-
|
|
27292
|
-
LIMIT ?`;
|
|
27293
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27294
|
-
for (; ; ) {
|
|
27295
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27296
|
-
...params,
|
|
27297
|
-
after.startedAtMs,
|
|
27298
|
-
after.startedAtMs,
|
|
27299
|
-
after.id,
|
|
27300
|
-
SCAN_BATCH_ROWS
|
|
27301
|
-
]);
|
|
27302
|
-
for (const r of rows) {
|
|
27303
|
-
yield {
|
|
27304
|
-
id: r.id,
|
|
27305
|
-
ruleId: r.rule_id,
|
|
27306
|
-
category: r.category,
|
|
27307
|
-
severity: r.severity,
|
|
27308
|
-
maskedMatch: r.masked_match,
|
|
27309
|
-
actionTaken: r.action_taken,
|
|
27310
|
-
confidence: r.confidence,
|
|
27311
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27312
|
-
sourceTool: r.source_tool,
|
|
27313
|
-
repo: r.repo ?? "",
|
|
27314
|
-
file: r.file ?? "",
|
|
27315
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27316
|
-
eventId: r.event_id,
|
|
27317
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27318
|
-
status: deriveInstanceStatus(r)
|
|
27319
|
-
};
|
|
27320
|
-
}
|
|
27321
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27322
|
-
const lastRow = rows[rows.length - 1];
|
|
27323
|
-
if (lastRow === void 0) return;
|
|
27324
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27325
|
-
}
|
|
27506
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27507
|
+
return { sql, params };
|
|
27326
27508
|
}
|
|
27327
27509
|
groupAggregates(withSearchText, scope) {
|
|
27328
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27329
|
-
group_concat(DISTINCT
|
|
27330
|
-
group_concat(DISTINCT 'via ' ||
|
|
27510
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27511
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27512
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27331
27513
|
const rows = this.db.prepare(
|
|
27332
27514
|
`SELECT rule_id,
|
|
27333
27515
|
sum(tuple_count) AS instance_count,
|
|
@@ -27345,7 +27527,7 @@ var SqliteFindingsRepository = class {
|
|
|
27345
27527
|
coalesce(latest.status, '') AS status_tuple,
|
|
27346
27528
|
count(*) AS tuple_count,
|
|
27347
27529
|
max(e.started_at) AS latest_at,
|
|
27348
|
-
group_concat(DISTINCT
|
|
27530
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27349
27531
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27350
27532
|
${innerSearchColumns}
|
|
27351
27533
|
FROM inspection_findings f
|
|
@@ -27476,6 +27658,8 @@ function isoDay(ms) {
|
|
|
27476
27658
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27477
27659
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27478
27660
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27661
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27662
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27479
27663
|
var SKIPPED = -1;
|
|
27480
27664
|
var ROW_COLUMNS = `id,
|
|
27481
27665
|
parent_id AS parentId,
|
|
@@ -27515,6 +27699,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27515
27699
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27516
27700
|
LIMIT :limit`
|
|
27517
27701
|
);
|
|
27702
|
+
this.captureRowsStmt = db.prepare(
|
|
27703
|
+
`SELECT ${ROW_COLUMNS}
|
|
27704
|
+
FROM audit_events
|
|
27705
|
+
WHERE synced_at IS NULL
|
|
27706
|
+
AND sync_claimed_at IS NULL
|
|
27707
|
+
AND outbox_owed = 1
|
|
27708
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27709
|
+
AND started_at < :before
|
|
27710
|
+
ORDER BY started_at
|
|
27711
|
+
LIMIT :limit`
|
|
27712
|
+
);
|
|
27713
|
+
this.markOwedStmt = db.prepare(
|
|
27714
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27715
|
+
);
|
|
27518
27716
|
this.stampStmt = db.prepare(
|
|
27519
27717
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27520
27718
|
);
|
|
@@ -27546,6 +27744,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27546
27744
|
FROM audit_events
|
|
27547
27745
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27548
27746
|
);
|
|
27747
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27748
|
+
`SELECT COUNT(*) AS skipped
|
|
27749
|
+
FROM audit_events
|
|
27750
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27751
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27752
|
+
);
|
|
27549
27753
|
this.fingerprintStmt = db.prepare(
|
|
27550
27754
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27551
27755
|
FROM history_sync WHERE id = 1`
|
|
@@ -27555,6 +27759,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27555
27759
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27556
27760
|
WHERE id = 1`
|
|
27557
27761
|
);
|
|
27762
|
+
this.disownCapturesStmt = db.prepare(
|
|
27763
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27764
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27765
|
+
);
|
|
27558
27766
|
this.rearmStmt = db.prepare(
|
|
27559
27767
|
`UPDATE audit_events SET synced_at = NULL
|
|
27560
27768
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27627,6 +27835,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27627
27835
|
closeWindowStmt;
|
|
27628
27836
|
releaseBoundaryStmt;
|
|
27629
27837
|
freezeBoundaryStmt;
|
|
27838
|
+
captureRowsStmt;
|
|
27839
|
+
markOwedStmt;
|
|
27840
|
+
captureSkipCountStmt;
|
|
27841
|
+
disownCapturesStmt;
|
|
27630
27842
|
partitionStmt;
|
|
27631
27843
|
claimRowStmt;
|
|
27632
27844
|
releaseRowStmt;
|
|
@@ -27660,6 +27872,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27660
27872
|
pendingRows(sessionId, limit, before) {
|
|
27661
27873
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27662
27874
|
}
|
|
27875
|
+
/**
|
|
27876
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27877
|
+
*
|
|
27878
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27879
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27880
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27881
|
+
* the live path.
|
|
27882
|
+
*/
|
|
27883
|
+
pendingCaptureRows(limit, before) {
|
|
27884
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27885
|
+
}
|
|
27886
|
+
/**
|
|
27887
|
+
* Record that a capture is OWED to the deployment.
|
|
27888
|
+
*
|
|
27889
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27890
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27891
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27892
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27893
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27894
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27895
|
+
* offered to anyone.
|
|
27896
|
+
*
|
|
27897
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27898
|
+
* out of the drain's read.
|
|
27899
|
+
*/
|
|
27900
|
+
markCaptureOwed(id) {
|
|
27901
|
+
this.markOwedStmt.run({ id });
|
|
27902
|
+
}
|
|
27663
27903
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27664
27904
|
markSynced(ids, atMs) {
|
|
27665
27905
|
this.stampAll(ids, atMs);
|
|
@@ -27743,10 +27983,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27743
27983
|
this.countsStmt,
|
|
27744
27984
|
{ before }
|
|
27745
27985
|
);
|
|
27986
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27746
27987
|
return {
|
|
27747
27988
|
pending: row?.pending ?? 0,
|
|
27748
27989
|
sent: row?.sent ?? 0,
|
|
27749
|
-
skipped: row?.skipped ?? 0
|
|
27990
|
+
skipped: row?.skipped ?? 0,
|
|
27991
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27750
27992
|
};
|
|
27751
27993
|
}
|
|
27752
27994
|
/**
|
|
@@ -27787,7 +28029,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27787
28029
|
withTransaction(
|
|
27788
28030
|
this.db,
|
|
27789
28031
|
() => {
|
|
28032
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27790
28033
|
this.rearmStmt.run();
|
|
28034
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
28035
|
+
this.disownCapturesStmt.run();
|
|
28036
|
+
}
|
|
27791
28037
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27792
28038
|
},
|
|
27793
28039
|
"IMMEDIATE"
|
|
@@ -27984,7 +28230,259 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
27984
28230
|
};
|
|
27985
28231
|
|
|
27986
28232
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
27987
|
-
import { createHash as createHash2, randomUUID as
|
|
28233
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28234
|
+
|
|
28235
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28236
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28237
|
+
import { join as join6 } from "path";
|
|
28238
|
+
|
|
28239
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28240
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28241
|
+
import { mkdir } from "fs/promises";
|
|
28242
|
+
import { homedir } from "os";
|
|
28243
|
+
import { join as join4 } from "path";
|
|
28244
|
+
function defaultDataDir() {
|
|
28245
|
+
return join4(homedir(), ".aka");
|
|
28246
|
+
}
|
|
28247
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28248
|
+
return join4(base, "settings");
|
|
28249
|
+
}
|
|
28250
|
+
function dataDir(base = defaultDataDir()) {
|
|
28251
|
+
return join4(base, "data");
|
|
28252
|
+
}
|
|
28253
|
+
function dbPath(base = defaultDataDir()) {
|
|
28254
|
+
return join4(dataDir(base), "aka.db");
|
|
28255
|
+
}
|
|
28256
|
+
function keysDir(base = defaultDataDir()) {
|
|
28257
|
+
return join4(base, "keys");
|
|
28258
|
+
}
|
|
28259
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
28260
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
28261
|
+
tightenDir(dir);
|
|
28262
|
+
}
|
|
28263
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28264
|
+
ensureDataDirSync(dir);
|
|
28265
|
+
}
|
|
28266
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28267
|
+
const moves = [
|
|
28268
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28269
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28270
|
+
];
|
|
28271
|
+
for (const { name, dest } of moves) {
|
|
28272
|
+
try {
|
|
28273
|
+
ensureDataDirSync(dest);
|
|
28274
|
+
const moved = join4(dest, name);
|
|
28275
|
+
renameSync3(join4(base, name), moved);
|
|
28276
|
+
tightenFile(moved);
|
|
28277
|
+
} catch {
|
|
28278
|
+
}
|
|
28279
|
+
}
|
|
28280
|
+
}
|
|
28281
|
+
|
|
28282
|
+
// ../../packages/persistence/src/settings.ts
|
|
28283
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28284
|
+
import { join as join5 } from "path";
|
|
28285
|
+
|
|
28286
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28287
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28288
|
+
import {
|
|
28289
|
+
closeSync,
|
|
28290
|
+
existsSync as existsSync2,
|
|
28291
|
+
openSync,
|
|
28292
|
+
readFileSync as readFileSync2,
|
|
28293
|
+
rmSync as rmSync5,
|
|
28294
|
+
statSync as statSync3,
|
|
28295
|
+
writeFileSync as writeFileSync2
|
|
28296
|
+
} from "fs";
|
|
28297
|
+
import { hostname as hostname3 } from "os";
|
|
28298
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28299
|
+
|
|
28300
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28301
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28302
|
+
import { posix, win32 } from "path";
|
|
28303
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28304
|
+
if (platform2 === "darwin") {
|
|
28305
|
+
return [
|
|
28306
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28307
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28308
|
+
];
|
|
28309
|
+
}
|
|
28310
|
+
if (platform2 === "win32") {
|
|
28311
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28312
|
+
}
|
|
28313
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28314
|
+
}
|
|
28315
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28316
|
+
for (const path of paths) {
|
|
28317
|
+
let text;
|
|
28318
|
+
try {
|
|
28319
|
+
text = readFileSync3(path, "utf8");
|
|
28320
|
+
} catch {
|
|
28321
|
+
continue;
|
|
28322
|
+
}
|
|
28323
|
+
const record2 = parseJsonObject(text);
|
|
28324
|
+
if (!record2) continue;
|
|
28325
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
28326
|
+
if (parsed2.success) return parsed2.data;
|
|
28327
|
+
}
|
|
28328
|
+
return null;
|
|
28329
|
+
}
|
|
28330
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28331
|
+
if (!managed) return settings;
|
|
28332
|
+
const { values } = managed;
|
|
28333
|
+
const merged = { ...settings };
|
|
28334
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28335
|
+
if (values.controlPlane !== void 0) {
|
|
28336
|
+
merged.controlPlane = {
|
|
28337
|
+
...values.controlPlane,
|
|
28338
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28339
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28340
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28341
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28342
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28343
|
+
};
|
|
28344
|
+
}
|
|
28345
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28346
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28347
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28348
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28349
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28350
|
+
if (values.vaultConsent !== void 0) {
|
|
28351
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28352
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28353
|
+
// at the current version otherwise.
|
|
28354
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28355
|
+
) : void 0;
|
|
28356
|
+
}
|
|
28357
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28358
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28359
|
+
acknowledgedAt: now().toISOString(),
|
|
28360
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28361
|
+
} : void 0;
|
|
28362
|
+
}
|
|
28363
|
+
return merged;
|
|
28364
|
+
}
|
|
28365
|
+
|
|
28366
|
+
// ../../packages/persistence/src/settings.ts
|
|
28367
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28368
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28369
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28370
|
+
}
|
|
28371
|
+
function readUserSettings(base) {
|
|
28372
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28373
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28374
|
+
try {
|
|
28375
|
+
return WorkspaceSettings.parse(record2);
|
|
28376
|
+
} catch {
|
|
28377
|
+
return defaultWorkspaceSettings();
|
|
28378
|
+
}
|
|
28379
|
+
}
|
|
28380
|
+
function readJson(file2) {
|
|
28381
|
+
let text;
|
|
28382
|
+
try {
|
|
28383
|
+
text = readFileSync4(file2, "utf8");
|
|
28384
|
+
} catch {
|
|
28385
|
+
return null;
|
|
28386
|
+
}
|
|
28387
|
+
return parseJsonObject(text) ?? null;
|
|
28388
|
+
}
|
|
28389
|
+
|
|
28390
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28391
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28392
|
+
switch (refusal) {
|
|
28393
|
+
case "lock":
|
|
28394
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28395
|
+
case "disable":
|
|
28396
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28397
|
+
case "floor":
|
|
28398
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28399
|
+
}
|
|
28400
|
+
}
|
|
28401
|
+
var PolicyFloorError = class extends Error {
|
|
28402
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28403
|
+
pack;
|
|
28404
|
+
/**
|
|
28405
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28406
|
+
* clearing the assignment, or switching the detection off.
|
|
28407
|
+
*/
|
|
28408
|
+
attempted;
|
|
28409
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28410
|
+
floor;
|
|
28411
|
+
refusal;
|
|
28412
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28413
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28414
|
+
this.name = "PolicyFloorError";
|
|
28415
|
+
this.pack = pack;
|
|
28416
|
+
this.attempted = attempted;
|
|
28417
|
+
this.floor = floor;
|
|
28418
|
+
this.refusal = refusal;
|
|
28419
|
+
}
|
|
28420
|
+
};
|
|
28421
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28422
|
+
try {
|
|
28423
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28424
|
+
const parsed2 = JSON.parse(raw);
|
|
28425
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
28426
|
+
return PolicyBundle.parse(parsed2.bundle);
|
|
28427
|
+
} catch {
|
|
28428
|
+
return null;
|
|
28429
|
+
}
|
|
28430
|
+
}
|
|
28431
|
+
function indexEnabled(policies) {
|
|
28432
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28433
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28434
|
+
for (const policy of policies) {
|
|
28435
|
+
if (!policy.enabled) continue;
|
|
28436
|
+
if ("ruleId" in policy.target) {
|
|
28437
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28438
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28439
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28440
|
+
}
|
|
28441
|
+
}
|
|
28442
|
+
return { byRuleId, byCategory };
|
|
28443
|
+
}
|
|
28444
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28445
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28446
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28447
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28448
|
+
return policies.some((policy) => {
|
|
28449
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28450
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28451
|
+
});
|
|
28452
|
+
}
|
|
28453
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28454
|
+
const floors = openControlPlaneFloors(base);
|
|
28455
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28456
|
+
}
|
|
28457
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28458
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28459
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28460
|
+
if (bundle === null) return null;
|
|
28461
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28462
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28463
|
+
}
|
|
28464
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28465
|
+
let action = null;
|
|
28466
|
+
for (const rule of rules) {
|
|
28467
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28468
|
+
if (resolved === void 0) continue;
|
|
28469
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28470
|
+
}
|
|
28471
|
+
if (action === null) return null;
|
|
28472
|
+
return {
|
|
28473
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28474
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28475
|
+
};
|
|
28476
|
+
}
|
|
28477
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28478
|
+
if (floor.locked) return "lock";
|
|
28479
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28480
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28481
|
+
}
|
|
28482
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28483
|
+
if (floor === null || enabled) return null;
|
|
28484
|
+
return "disable";
|
|
28485
|
+
}
|
|
27988
28486
|
|
|
27989
28487
|
// ../../packages/persistence/src/semver.ts
|
|
27990
28488
|
function parse3(version2) {
|
|
@@ -28078,8 +28576,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
28078
28576
|
return ids;
|
|
28079
28577
|
}
|
|
28080
28578
|
var SqliteInstalledPacksRepository = class {
|
|
28081
|
-
|
|
28579
|
+
/**
|
|
28580
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28581
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28582
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28583
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28584
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28585
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28586
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28587
|
+
* real `~/.aka`.
|
|
28588
|
+
*/
|
|
28589
|
+
constructor(db, baseDir) {
|
|
28082
28590
|
this.db = db;
|
|
28591
|
+
this.baseDir = baseDir;
|
|
28083
28592
|
this.insertMissingStmt = db.prepare(
|
|
28084
28593
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
28085
28594
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28101,11 +28610,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28101
28610
|
this.signatureStmt = db.prepare(
|
|
28102
28611
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28103
28612
|
);
|
|
28613
|
+
this.packRulesStmt = db.prepare(
|
|
28614
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28615
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28616
|
+
);
|
|
28104
28617
|
}
|
|
28105
28618
|
db;
|
|
28619
|
+
baseDir;
|
|
28106
28620
|
insertMissingStmt;
|
|
28107
28621
|
upsertAvailableStmt;
|
|
28108
28622
|
signatureStmt;
|
|
28623
|
+
packRulesStmt;
|
|
28109
28624
|
/**
|
|
28110
28625
|
* Record the running binary's detection inventory. Refreshes the
|
|
28111
28626
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28147,7 +28662,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28147
28662
|
let behind = false;
|
|
28148
28663
|
for (const row of rows) {
|
|
28149
28664
|
const params = {
|
|
28150
|
-
id:
|
|
28665
|
+
id: randomUUID4(),
|
|
28151
28666
|
namespace: row.namespace,
|
|
28152
28667
|
packId: row.packId,
|
|
28153
28668
|
version: row.version,
|
|
@@ -28159,7 +28674,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28159
28674
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28160
28675
|
this.upsertAvailableStmt.run({
|
|
28161
28676
|
...params,
|
|
28162
|
-
id:
|
|
28677
|
+
id: randomUUID4(),
|
|
28163
28678
|
recordedBy: meta4?.recordedBy ?? null
|
|
28164
28679
|
});
|
|
28165
28680
|
} else {
|
|
@@ -28405,9 +28920,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28405
28920
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28406
28921
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28407
28922
|
// caller can tell an edit from a no-such-detection.
|
|
28923
|
+
/**
|
|
28924
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
28925
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
28926
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
28927
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
28928
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
28929
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
28930
|
+
*/
|
|
28931
|
+
packFloorRules(namespace, packId) {
|
|
28932
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
28933
|
+
if (!row) return [];
|
|
28934
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
28935
|
+
}
|
|
28936
|
+
/**
|
|
28937
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
28938
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
28939
|
+
* repository constructed without a layout base).
|
|
28940
|
+
*
|
|
28941
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
28942
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
28943
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
28944
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
28945
|
+
*/
|
|
28946
|
+
policyFloor(namespace, packId) {
|
|
28947
|
+
if (this.baseDir === void 0) return null;
|
|
28948
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
28949
|
+
}
|
|
28950
|
+
/**
|
|
28951
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
28952
|
+
* entry only for a pack the control plane actually governs.
|
|
28953
|
+
*
|
|
28954
|
+
* A surface listing every detection asks per pack, and asking through
|
|
28955
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
28956
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
28957
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
28958
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
28959
|
+
* exactly as the single-pack read returns null for them.
|
|
28960
|
+
*/
|
|
28961
|
+
policyFloors(packs2) {
|
|
28962
|
+
const floors = /* @__PURE__ */ new Map();
|
|
28963
|
+
if (this.baseDir === void 0) return floors;
|
|
28964
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
28965
|
+
if (source === null) return floors;
|
|
28966
|
+
for (const pack of packs2) {
|
|
28967
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
28968
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
28969
|
+
}
|
|
28970
|
+
return floors;
|
|
28971
|
+
}
|
|
28408
28972
|
/**
|
|
28409
28973
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28410
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
28974
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
28975
|
+
*
|
|
28976
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
28977
|
+
* write below, and a detection the organization has authored a policy for is
|
|
28978
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
28979
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
28980
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
28981
|
+
* surface that offers the choice.
|
|
28411
28982
|
*/
|
|
28412
28983
|
setPolicy(namespace, packId, policyId) {
|
|
28413
28984
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28415,14 +28986,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28415
28986
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28416
28987
|
);
|
|
28417
28988
|
}
|
|
28989
|
+
const requested = policyId;
|
|
28990
|
+
const floor = this.policyFloor(namespace, packId);
|
|
28991
|
+
if (floor !== null) {
|
|
28992
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
28993
|
+
if (refusal !== null) {
|
|
28994
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
28995
|
+
}
|
|
28996
|
+
}
|
|
28418
28997
|
const res = this.db.prepare(
|
|
28419
28998
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28420
28999
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28421
29000
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28422
29001
|
return Number(res.changes) > 0;
|
|
28423
29002
|
}
|
|
28424
|
-
/**
|
|
29003
|
+
/**
|
|
29004
|
+
* Enable or disable one installed pack.
|
|
29005
|
+
*
|
|
29006
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
29007
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
29008
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
29009
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
29010
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
29011
|
+
* that lived in a page would leave the CLI free.
|
|
29012
|
+
*/
|
|
28425
29013
|
setEnabled(namespace, packId, enabled) {
|
|
29014
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29015
|
+
if (floor !== null) {
|
|
29016
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
29017
|
+
if (refusal !== null) {
|
|
29018
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
29019
|
+
}
|
|
29020
|
+
}
|
|
28426
29021
|
const res = this.db.prepare(
|
|
28427
29022
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28428
29023
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28508,7 +29103,7 @@ var SqliteInventoryRepository = class {
|
|
|
28508
29103
|
};
|
|
28509
29104
|
|
|
28510
29105
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28511
|
-
import { randomUUID as
|
|
29106
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28512
29107
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28513
29108
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28514
29109
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -28997,7 +29592,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
28997
29592
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
28998
29593
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
28999
29594
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
29000
|
-
).run({ id:
|
|
29595
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
29001
29596
|
}
|
|
29002
29597
|
return true;
|
|
29003
29598
|
}
|
|
@@ -29018,7 +29613,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29018
29613
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
29019
29614
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
29020
29615
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
29021
|
-
).run({ id:
|
|
29616
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
29022
29617
|
}
|
|
29023
29618
|
this.configRowsCache = void 0;
|
|
29024
29619
|
return "ok";
|
|
@@ -29315,7 +29910,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29315
29910
|
};
|
|
29316
29911
|
|
|
29317
29912
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29318
|
-
import { randomUUID as
|
|
29913
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29319
29914
|
var SqlitePoliciesRepository = class {
|
|
29320
29915
|
constructor(db) {
|
|
29321
29916
|
this.db = db;
|
|
@@ -29350,7 +29945,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29350
29945
|
failOpenTransaction(this.db, () => {
|
|
29351
29946
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29352
29947
|
stmt.run({
|
|
29353
|
-
id:
|
|
29948
|
+
id: randomUUID6(),
|
|
29354
29949
|
target: JSON.stringify({ category }),
|
|
29355
29950
|
action,
|
|
29356
29951
|
now: Date.now()
|
|
@@ -29370,7 +29965,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29370
29965
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29371
29966
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29372
29967
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29373
|
-
).run({ id:
|
|
29968
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29374
29969
|
}
|
|
29375
29970
|
// Caps every global per-category policy currently set to block/redact down
|
|
29376
29971
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29438,7 +30033,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29438
30033
|
};
|
|
29439
30034
|
|
|
29440
30035
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29441
|
-
import { randomUUID as
|
|
30036
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29442
30037
|
var SqliteProjectFilesRepository = class {
|
|
29443
30038
|
constructor(db) {
|
|
29444
30039
|
this.db = db;
|
|
@@ -29470,7 +30065,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29470
30065
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29471
30066
|
for (const file2 of scan2.files) {
|
|
29472
30067
|
this.upsertStmt.run({
|
|
29473
|
-
id:
|
|
30068
|
+
id: randomUUID7(),
|
|
29474
30069
|
projectId,
|
|
29475
30070
|
path: file2.path,
|
|
29476
30071
|
name: file2.name,
|
|
@@ -29484,9 +30079,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29484
30079
|
};
|
|
29485
30080
|
|
|
29486
30081
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29487
|
-
import { randomUUID as
|
|
30082
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29488
30083
|
var SqliteResolutionsRepository = class {
|
|
29489
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
30084
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29490
30085
|
this.db = db;
|
|
29491
30086
|
this.now = now;
|
|
29492
30087
|
this.newId = newId;
|
|
@@ -29699,7 +30294,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29699
30294
|
};
|
|
29700
30295
|
|
|
29701
30296
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29702
|
-
import { randomUUID as
|
|
30297
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29703
30298
|
function pageLimit(requested, fallback) {
|
|
29704
30299
|
if (requested === void 0) return fallback;
|
|
29705
30300
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29745,12 +30340,14 @@ var SELECT_COLUMNS = `
|
|
|
29745
30340
|
ciphertext,
|
|
29746
30341
|
nonce,
|
|
29747
30342
|
auth_tag AS authTag,
|
|
30343
|
+
user_authorized AS userAuthorized,
|
|
29748
30344
|
occurrence_count AS occurrenceCount,
|
|
29749
30345
|
first_seen AS firstSeen,
|
|
29750
30346
|
last_seen AS lastSeen`;
|
|
29751
30347
|
function toRow(raw) {
|
|
29752
|
-
const { provider, ...rest } = raw;
|
|
29753
|
-
|
|
30348
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30349
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30350
|
+
return provider === null ? row : { ...row, provider };
|
|
29754
30351
|
}
|
|
29755
30352
|
var SqliteSecretVaultRepository = class {
|
|
29756
30353
|
constructor(db) {
|
|
@@ -29760,17 +30357,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29760
30357
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29761
30358
|
format_version, category, rule_id, masked_match, provider,
|
|
29762
30359
|
ciphertext, nonce, auth_tag,
|
|
29763
|
-
occurrence_count, first_seen, last_seen
|
|
30360
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29764
30361
|
) VALUES (
|
|
29765
30362
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29766
30363
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29767
30364
|
:ciphertext, :nonce, :authTag,
|
|
29768
|
-
1, :now, :now
|
|
30365
|
+
:userAuthorized, 1, :now, :now
|
|
29769
30366
|
)`
|
|
29770
30367
|
);
|
|
29771
30368
|
this.bumpStmt = db.prepare(
|
|
29772
30369
|
`UPDATE secret_vault
|
|
29773
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30370
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30371
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29774
30372
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29775
30373
|
);
|
|
29776
30374
|
this.byPointerStmt = db.prepare(
|
|
@@ -29790,6 +30388,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29790
30388
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29791
30389
|
WHERE pointer_id = :pointerId`
|
|
29792
30390
|
);
|
|
30391
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29793
30392
|
this.derefStmt = db.prepare(
|
|
29794
30393
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29795
30394
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29803,6 +30402,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29803
30402
|
listStmt;
|
|
29804
30403
|
replaceCiphertextStmt;
|
|
29805
30404
|
refreshFingerprintStmt;
|
|
30405
|
+
deleteByPointerStmt;
|
|
29806
30406
|
derefStmt;
|
|
29807
30407
|
/**
|
|
29808
30408
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29811,6 +30411,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29811
30411
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29812
30412
|
* wire token. `minted` is true only when this call created the row.
|
|
29813
30413
|
*
|
|
30414
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30415
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30416
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30417
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30418
|
+
*
|
|
29814
30419
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29815
30420
|
* writers cannot both decide they are minting.
|
|
29816
30421
|
*/
|
|
@@ -29837,13 +30442,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29837
30442
|
ciphertext: input2.ciphertext,
|
|
29838
30443
|
nonce: input2.nonce,
|
|
29839
30444
|
authTag: input2.authTag,
|
|
30445
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29840
30446
|
now
|
|
29841
30447
|
})
|
|
29842
30448
|
);
|
|
29843
30449
|
minted = true;
|
|
29844
30450
|
return;
|
|
29845
30451
|
}
|
|
29846
|
-
this.bumpStmt.run({
|
|
30452
|
+
this.bumpStmt.run({
|
|
30453
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30454
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30455
|
+
now
|
|
30456
|
+
});
|
|
29847
30457
|
},
|
|
29848
30458
|
"IMMEDIATE"
|
|
29849
30459
|
);
|
|
@@ -29903,6 +30513,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29903
30513
|
);
|
|
29904
30514
|
return destroyed;
|
|
29905
30515
|
}
|
|
30516
|
+
/**
|
|
30517
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30518
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30519
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30520
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30521
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30522
|
+
* it.
|
|
30523
|
+
*
|
|
30524
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30525
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30526
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30527
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30528
|
+
* entry still sitting in the vault.
|
|
30529
|
+
*
|
|
30530
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30531
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30532
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30533
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30534
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30535
|
+
*/
|
|
30536
|
+
deleteByPointerIds(pointerIds) {
|
|
30537
|
+
if (pointerIds.length === 0) return [];
|
|
30538
|
+
const deleted = [];
|
|
30539
|
+
withTransaction(
|
|
30540
|
+
this.db,
|
|
30541
|
+
() => {
|
|
30542
|
+
for (const pointerId of pointerIds) {
|
|
30543
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30544
|
+
deleted.push(pointerId);
|
|
30545
|
+
}
|
|
30546
|
+
}
|
|
30547
|
+
},
|
|
30548
|
+
"IMMEDIATE"
|
|
30549
|
+
);
|
|
30550
|
+
return deleted;
|
|
30551
|
+
}
|
|
29906
30552
|
/**
|
|
29907
30553
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
29908
30554
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -29915,7 +30561,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29915
30561
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
29916
30562
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
29917
30563
|
).run({
|
|
29918
|
-
id:
|
|
30564
|
+
id: randomUUID9(),
|
|
29919
30565
|
pointerId: entry.pointerId,
|
|
29920
30566
|
location: entry.location,
|
|
29921
30567
|
kind: entry.kind,
|
|
@@ -30428,15 +31074,15 @@ var SqliteSecurityRepository = class {
|
|
|
30428
31074
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30429
31075
|
const rows = allRows(
|
|
30430
31076
|
this.db.prepare(
|
|
30431
|
-
`SELECT
|
|
31077
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30432
31078
|
FROM inspection_findings f
|
|
30433
31079
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30434
31080
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30435
31081
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30436
|
-
AND
|
|
30437
|
-
AND
|
|
30438
|
-
GROUP BY repo
|
|
30439
|
-
ORDER BY c DESC, repo
|
|
31082
|
+
AND e.repo IS NOT NULL
|
|
31083
|
+
AND e.repo != ''
|
|
31084
|
+
GROUP BY e.repo
|
|
31085
|
+
ORDER BY c DESC, e.repo
|
|
30440
31086
|
LIMIT :limit`
|
|
30441
31087
|
),
|
|
30442
31088
|
{ from, to: now, limit }
|
|
@@ -30498,7 +31144,7 @@ var SqliteSecurityRepository = class {
|
|
|
30498
31144
|
`SELECT f.finding_key AS finding_key,
|
|
30499
31145
|
d.rule_id AS rule_id,
|
|
30500
31146
|
d.severity AS severity,
|
|
30501
|
-
|
|
31147
|
+
e.file_path AS path,
|
|
30502
31148
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30503
31149
|
latest.resolved_at AS latest_resolved_at
|
|
30504
31150
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30551,7 +31197,7 @@ var SqliteSecurityRepository = class {
|
|
|
30551
31197
|
};
|
|
30552
31198
|
|
|
30553
31199
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30554
|
-
import { randomUUID as
|
|
31200
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30555
31201
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30556
31202
|
var IN_CHUNK = 500;
|
|
30557
31203
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30639,7 +31285,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30639
31285
|
callSiteCount,
|
|
30640
31286
|
transports: distinctTransports(transports),
|
|
30641
31287
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30642
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31288
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30643
31289
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30644
31290
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30645
31291
|
};
|
|
@@ -30666,7 +31312,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30666
31312
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30667
31313
|
transports: distinctTransports(transports),
|
|
30668
31314
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30669
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31315
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30670
31316
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30671
31317
|
note: dest.note,
|
|
30672
31318
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30695,7 +31341,11 @@ var SqliteSharesRepository = class {
|
|
|
30695
31341
|
FROM share_destination d
|
|
30696
31342
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30697
31343
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30698
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31344
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31345
|
+
AND NOT EXISTS (
|
|
31346
|
+
SELECT 1 FROM egress_decision_override o
|
|
31347
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31348
|
+
)`
|
|
30699
31349
|
);
|
|
30700
31350
|
const kindCounts = countBy(
|
|
30701
31351
|
this.db,
|
|
@@ -30807,7 +31457,7 @@ var SqliteSharesRepository = class {
|
|
|
30807
31457
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30808
31458
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30809
31459
|
).run({
|
|
30810
|
-
id:
|
|
31460
|
+
id: randomUUID10(),
|
|
30811
31461
|
destinationId,
|
|
30812
31462
|
host: dest.host,
|
|
30813
31463
|
decision,
|
|
@@ -30956,7 +31606,7 @@ var SqliteSharesRepository = class {
|
|
|
30956
31606
|
let destinationId = destIds.get(hit.host);
|
|
30957
31607
|
if (destinationId === void 0) {
|
|
30958
31608
|
destStmt.run({
|
|
30959
|
-
id:
|
|
31609
|
+
id: randomUUID10(),
|
|
30960
31610
|
kind: hit.kind,
|
|
30961
31611
|
name: hit.name,
|
|
30962
31612
|
host: hit.host,
|
|
@@ -30972,7 +31622,7 @@ var SqliteSharesRepository = class {
|
|
|
30972
31622
|
let endpointId = endpointIds.get(endpointKey);
|
|
30973
31623
|
if (endpointId === void 0) {
|
|
30974
31624
|
endpointStmt.run({
|
|
30975
|
-
id:
|
|
31625
|
+
id: randomUUID10(),
|
|
30976
31626
|
destinationId,
|
|
30977
31627
|
method: hit.method,
|
|
30978
31628
|
transport: hit.transport,
|
|
@@ -30985,7 +31635,7 @@ var SqliteSharesRepository = class {
|
|
|
30985
31635
|
endpointIds.set(endpointKey, endpointId);
|
|
30986
31636
|
}
|
|
30987
31637
|
siteStmt.run({
|
|
30988
|
-
id:
|
|
31638
|
+
id: randomUUID10(),
|
|
30989
31639
|
endpointId,
|
|
30990
31640
|
project: input2.project,
|
|
30991
31641
|
projectKey: input2.projectKey,
|
|
@@ -31350,6 +32000,7 @@ function purgeSampleData(db) {
|
|
|
31350
32000
|
}
|
|
31351
32001
|
|
|
31352
32002
|
// ../../packages/persistence/src/database.ts
|
|
32003
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31353
32004
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31354
32005
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31355
32006
|
);
|
|
@@ -31397,7 +32048,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31397
32048
|
discardStore(file2, backup);
|
|
31398
32049
|
return backup;
|
|
31399
32050
|
}
|
|
31400
|
-
function openAndInitialize(file2) {
|
|
32051
|
+
function openAndInitialize(file2, base) {
|
|
31401
32052
|
let db = openWithPragmas(file2);
|
|
31402
32053
|
try {
|
|
31403
32054
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31410,7 +32061,7 @@ function openAndInitialize(file2) {
|
|
|
31410
32061
|
applyMigrations(db, file2);
|
|
31411
32062
|
tightenPerms(file2);
|
|
31412
32063
|
const policies = new SqlitePoliciesRepository(db);
|
|
31413
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
32064
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31414
32065
|
const repositories = {
|
|
31415
32066
|
events: new SqliteEventsRepository(db),
|
|
31416
32067
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31446,7 +32097,7 @@ function openAndInitialize(file2) {
|
|
|
31446
32097
|
}
|
|
31447
32098
|
function openLocalDatabase(dir) {
|
|
31448
32099
|
ensureDataDirSync(dir);
|
|
31449
|
-
const file2 =
|
|
32100
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31450
32101
|
reapStalePartials(file2);
|
|
31451
32102
|
const {
|
|
31452
32103
|
db,
|
|
@@ -31474,7 +32125,13 @@ function openLocalDatabase(dir) {
|
|
|
31474
32125
|
inspectionDefinitions,
|
|
31475
32126
|
inspectionFindings,
|
|
31476
32127
|
configInventory
|
|
31477
|
-
} = openAndInitialize(
|
|
32128
|
+
} = openAndInitialize(
|
|
32129
|
+
file2,
|
|
32130
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32131
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32132
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32133
|
+
dirname2(dir)
|
|
32134
|
+
);
|
|
31478
32135
|
function captureRowId(event) {
|
|
31479
32136
|
return captureId(
|
|
31480
32137
|
event.metadata?.sessionId ?? null,
|
|
@@ -31487,6 +32144,21 @@ function openLocalDatabase(dir) {
|
|
|
31487
32144
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31488
32145
|
});
|
|
31489
32146
|
}
|
|
32147
|
+
function markCaptureOwed(event) {
|
|
32148
|
+
failOpenTransaction(db, () => {
|
|
32149
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32150
|
+
});
|
|
32151
|
+
}
|
|
32152
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32153
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32154
|
+
if (stampable.length === 0) return;
|
|
32155
|
+
failOpenTransaction(db, () => {
|
|
32156
|
+
historySync.markSynced(
|
|
32157
|
+
stampable.map((event) => event.id),
|
|
32158
|
+
atMs
|
|
32159
|
+
);
|
|
32160
|
+
});
|
|
32161
|
+
}
|
|
31490
32162
|
function recordCapture(event, detected) {
|
|
31491
32163
|
failOpenTransaction(db, () => {
|
|
31492
32164
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31573,7 +32245,7 @@ function openLocalDatabase(dir) {
|
|
|
31573
32245
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
31574
32246
|
if (!definitionId) continue;
|
|
31575
32247
|
inspectionFindings.insertFinding({
|
|
31576
|
-
id:
|
|
32248
|
+
id: randomUUID11(),
|
|
31577
32249
|
auditEventId: record2.scanEvent.id,
|
|
31578
32250
|
inspectionDefinitionId: definitionId,
|
|
31579
32251
|
span: finding.span,
|
|
@@ -31669,6 +32341,8 @@ function openLocalDatabase(dir) {
|
|
|
31669
32341
|
inspectionFindings,
|
|
31670
32342
|
recordCapture,
|
|
31671
32343
|
markCaptureDelivered,
|
|
32344
|
+
markCaptureOwed,
|
|
32345
|
+
markAuditEventsDelivered,
|
|
31672
32346
|
ensureInventory,
|
|
31673
32347
|
recordConfigScan,
|
|
31674
32348
|
recordProjectFiles,
|
|
@@ -31707,20 +32381,6 @@ var UserGrantPolicyProvider = class {
|
|
|
31707
32381
|
}
|
|
31708
32382
|
};
|
|
31709
32383
|
|
|
31710
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31711
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31712
|
-
import {
|
|
31713
|
-
closeSync,
|
|
31714
|
-
existsSync as existsSync2,
|
|
31715
|
-
openSync,
|
|
31716
|
-
readFileSync as readFileSync2,
|
|
31717
|
-
rmSync as rmSync5,
|
|
31718
|
-
statSync as statSync3,
|
|
31719
|
-
writeFileSync as writeFileSync2
|
|
31720
|
-
} from "fs";
|
|
31721
|
-
import { hostname as hostname3 } from "os";
|
|
31722
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31723
|
-
|
|
31724
32384
|
// ../../packages/persistence/src/finding-key.ts
|
|
31725
32385
|
import { createHash as createHash3 } from "crypto";
|
|
31726
32386
|
function normalizeFilePath(filePath) {
|
|
@@ -31733,13 +32393,13 @@ function computeFindingKey(input2) {
|
|
|
31733
32393
|
|
|
31734
32394
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31735
32395
|
import { createHmac, randomBytes } from "crypto";
|
|
31736
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31737
|
-
import { join as
|
|
32396
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32397
|
+
import { join as join8 } from "path";
|
|
31738
32398
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31739
32399
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
31740
32400
|
var KEY_MATERIAL_BYTES = 32;
|
|
31741
32401
|
function keyFilePath(dataDir2) {
|
|
31742
|
-
return
|
|
32402
|
+
return join8(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
31743
32403
|
}
|
|
31744
32404
|
function parseKeyFile(raw) {
|
|
31745
32405
|
const parsed2 = JSON.parse(raw);
|
|
@@ -31777,7 +32437,7 @@ var FloorUnreadableError = class extends Error {
|
|
|
31777
32437
|
}
|
|
31778
32438
|
};
|
|
31779
32439
|
function storedKeyVersionFloor(dataDir2) {
|
|
31780
|
-
const file2 =
|
|
32440
|
+
const file2 = join8(dataDir2, DB_FILENAME);
|
|
31781
32441
|
if (!existsSync3(file2)) return 0;
|
|
31782
32442
|
let db;
|
|
31783
32443
|
try {
|
|
@@ -31832,7 +32492,7 @@ function occupantMessage(file2, kind) {
|
|
|
31832
32492
|
function readFingerprintKey(dataDir2) {
|
|
31833
32493
|
let raw;
|
|
31834
32494
|
try {
|
|
31835
|
-
raw =
|
|
32495
|
+
raw = readFileSync6(keyFilePath(dataDir2), "utf8");
|
|
31836
32496
|
} catch (err) {
|
|
31837
32497
|
if (err.code === "ENOENT") return null;
|
|
31838
32498
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -31856,146 +32516,12 @@ function fingerprintValue(key, raw) {
|
|
|
31856
32516
|
|
|
31857
32517
|
// ../../packages/persistence/src/history-preview.ts
|
|
31858
32518
|
import { existsSync as existsSync4 } from "fs";
|
|
31859
|
-
import { join as
|
|
32519
|
+
import { join as join9 } from "path";
|
|
31860
32520
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31861
32521
|
|
|
31862
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31863
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31864
|
-
import { mkdir } from "fs/promises";
|
|
31865
|
-
import { homedir } from "os";
|
|
31866
|
-
import { join as join7 } from "path";
|
|
31867
|
-
function defaultDataDir() {
|
|
31868
|
-
return join7(homedir(), ".aka");
|
|
31869
|
-
}
|
|
31870
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31871
|
-
return join7(base, "settings");
|
|
31872
|
-
}
|
|
31873
|
-
function dataDir(base = defaultDataDir()) {
|
|
31874
|
-
return join7(base, "data");
|
|
31875
|
-
}
|
|
31876
|
-
function dbPath(base = defaultDataDir()) {
|
|
31877
|
-
return join7(dataDir(base), "aka.db");
|
|
31878
|
-
}
|
|
31879
|
-
function keysDir(base = defaultDataDir()) {
|
|
31880
|
-
return join7(base, "keys");
|
|
31881
|
-
}
|
|
31882
|
-
async function ensureDataDir(dir = defaultDataDir()) {
|
|
31883
|
-
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
31884
|
-
tightenDir(dir);
|
|
31885
|
-
}
|
|
31886
|
-
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
31887
|
-
ensureDataDirSync(dir);
|
|
31888
|
-
}
|
|
31889
|
-
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
31890
|
-
const moves = [
|
|
31891
|
-
{ name: "config.json", dest: settingsDir(base) },
|
|
31892
|
-
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
31893
|
-
];
|
|
31894
|
-
for (const { name, dest } of moves) {
|
|
31895
|
-
try {
|
|
31896
|
-
ensureDataDirSync(dest);
|
|
31897
|
-
const moved = join7(dest, name);
|
|
31898
|
-
renameSync3(join7(base, name), moved);
|
|
31899
|
-
tightenFile(moved);
|
|
31900
|
-
} catch {
|
|
31901
|
-
}
|
|
31902
|
-
}
|
|
31903
|
-
}
|
|
31904
|
-
|
|
31905
|
-
// ../../packages/persistence/src/managed-settings.ts
|
|
31906
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
31907
|
-
import { posix, win32 } from "path";
|
|
31908
|
-
function managedSettingsPaths(platform2 = process.platform) {
|
|
31909
|
-
if (platform2 === "darwin") {
|
|
31910
|
-
return [
|
|
31911
|
-
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
31912
|
-
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
31913
|
-
];
|
|
31914
|
-
}
|
|
31915
|
-
if (platform2 === "win32") {
|
|
31916
|
-
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
31917
|
-
}
|
|
31918
|
-
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
31919
|
-
}
|
|
31920
|
-
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
31921
|
-
for (const path of paths) {
|
|
31922
|
-
let text;
|
|
31923
|
-
try {
|
|
31924
|
-
text = readFileSync4(path, "utf8");
|
|
31925
|
-
} catch {
|
|
31926
|
-
continue;
|
|
31927
|
-
}
|
|
31928
|
-
const record2 = parseJsonObject(text);
|
|
31929
|
-
if (!record2) continue;
|
|
31930
|
-
const parsed2 = ManagedSettings.safeParse(record2);
|
|
31931
|
-
if (parsed2.success) return parsed2.data;
|
|
31932
|
-
}
|
|
31933
|
-
return null;
|
|
31934
|
-
}
|
|
31935
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
31936
|
-
if (!managed) return settings;
|
|
31937
|
-
const { values } = managed;
|
|
31938
|
-
const merged = { ...settings };
|
|
31939
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
31940
|
-
if (values.controlPlane !== void 0) {
|
|
31941
|
-
merged.controlPlane = {
|
|
31942
|
-
...values.controlPlane,
|
|
31943
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31944
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31945
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31946
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31947
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31948
|
-
};
|
|
31949
|
-
}
|
|
31950
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
31951
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
31952
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
31953
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
31954
|
-
if (values.vaultConsent !== void 0) {
|
|
31955
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
31956
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31957
|
-
// at the current version otherwise.
|
|
31958
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31959
|
-
) : void 0;
|
|
31960
|
-
}
|
|
31961
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
31962
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
31963
|
-
acknowledgedAt: now().toISOString(),
|
|
31964
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
31965
|
-
} : void 0;
|
|
31966
|
-
}
|
|
31967
|
-
return merged;
|
|
31968
|
-
}
|
|
31969
|
-
|
|
31970
|
-
// ../../packages/persistence/src/settings.ts
|
|
31971
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
31972
|
-
import { join as join8 } from "path";
|
|
31973
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
31974
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
31975
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
31976
|
-
}
|
|
31977
|
-
function readUserSettings(base) {
|
|
31978
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
31979
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
31980
|
-
try {
|
|
31981
|
-
return WorkspaceSettings.parse(record2);
|
|
31982
|
-
} catch {
|
|
31983
|
-
return defaultWorkspaceSettings();
|
|
31984
|
-
}
|
|
31985
|
-
}
|
|
31986
|
-
function readJson(file2) {
|
|
31987
|
-
let text;
|
|
31988
|
-
try {
|
|
31989
|
-
text = readFileSync5(file2, "utf8");
|
|
31990
|
-
} catch {
|
|
31991
|
-
return null;
|
|
31992
|
-
}
|
|
31993
|
-
return parseJsonObject(text) ?? null;
|
|
31994
|
-
}
|
|
31995
|
-
|
|
31996
32522
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
31997
32523
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31998
|
-
import { dirname as
|
|
32524
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
31999
32525
|
|
|
32000
32526
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
32001
32527
|
import {
|
|
@@ -32108,8 +32634,8 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
32108
32634
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
32109
32635
|
import { execFileSync } from "child_process";
|
|
32110
32636
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32111
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
32112
|
-
import { join as
|
|
32637
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32638
|
+
import { join as join11 } from "path";
|
|
32113
32639
|
var VAULT_OCCUPANT_REASON = {
|
|
32114
32640
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
32115
32641
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -32208,7 +32734,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32208
32734
|
throw asError(err);
|
|
32209
32735
|
}
|
|
32210
32736
|
try {
|
|
32211
|
-
writeFileSync3(
|
|
32737
|
+
writeFileSync3(join11(lock, LOCK_OWNER_FILE), `${owner}
|
|
32212
32738
|
`, { mode: DATA_FILE_MODE });
|
|
32213
32739
|
return true;
|
|
32214
32740
|
} catch (err) {
|
|
@@ -32217,7 +32743,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32217
32743
|
}
|
|
32218
32744
|
}
|
|
32219
32745
|
function acquireRotationLock(keysDir2) {
|
|
32220
|
-
const lock =
|
|
32746
|
+
const lock = join11(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
32221
32747
|
const owner = randomBytes2(16).toString("hex");
|
|
32222
32748
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
32223
32749
|
let held;
|
|
@@ -32244,7 +32770,7 @@ function acquireRotationLock(keysDir2) {
|
|
|
32244
32770
|
}
|
|
32245
32771
|
function releaseRotationLock(lease) {
|
|
32246
32772
|
try {
|
|
32247
|
-
if (
|
|
32773
|
+
if (readFileSync7(join11(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
32248
32774
|
} catch {
|
|
32249
32775
|
return;
|
|
32250
32776
|
}
|
|
@@ -32265,7 +32791,7 @@ var FileKeyProvider = class {
|
|
|
32265
32791
|
this.#keysDir = keysDir2;
|
|
32266
32792
|
}
|
|
32267
32793
|
get filePath() {
|
|
32268
|
-
return
|
|
32794
|
+
return join11(this.#keysDir, VAULT_KEY_FILENAME);
|
|
32269
32795
|
}
|
|
32270
32796
|
loadOrCreate() {
|
|
32271
32797
|
return asAsync(() => {
|
|
@@ -32295,7 +32821,7 @@ var FileKeyProvider = class {
|
|
|
32295
32821
|
#read() {
|
|
32296
32822
|
let raw;
|
|
32297
32823
|
try {
|
|
32298
|
-
raw =
|
|
32824
|
+
raw = readFileSync7(this.filePath, "utf8");
|
|
32299
32825
|
} catch (err) {
|
|
32300
32826
|
if (err.code === "ENOENT") return null;
|
|
32301
32827
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -32582,7 +33108,14 @@ var SecretVault = class {
|
|
|
32582
33108
|
const existing = this.#repo.byValueFingerprint(valueFingerprint);
|
|
32583
33109
|
const now = this.#now();
|
|
32584
33110
|
if (existing) {
|
|
32585
|
-
this.#repo.upsert(
|
|
33111
|
+
this.#repo.upsert(
|
|
33112
|
+
{
|
|
33113
|
+
...existing,
|
|
33114
|
+
provider: existing.provider ?? void 0,
|
|
33115
|
+
userAuthorized: meta4.userAuthorized === true
|
|
33116
|
+
},
|
|
33117
|
+
now
|
|
33118
|
+
);
|
|
32586
33119
|
return await this.#emitToken(existing.keyVersion, existing.pointerId, existing.category);
|
|
32587
33120
|
}
|
|
32588
33121
|
const { material, version: version2 } = await this.#keys.loadOrCreate();
|
|
@@ -32604,6 +33137,7 @@ var SecretVault = class {
|
|
|
32604
33137
|
ruleId: meta4.ruleId,
|
|
32605
33138
|
maskedMatch: meta4.maskedMatch,
|
|
32606
33139
|
provider: meta4.provider,
|
|
33140
|
+
userAuthorized: meta4.userAuthorized === true,
|
|
32607
33141
|
ciphertext: sealed.ciphertext.toString("base64"),
|
|
32608
33142
|
nonce: sealed.nonce.toString("base64"),
|
|
32609
33143
|
authTag: sealed.authTag.toString("base64")
|
|
@@ -32923,11 +33457,11 @@ var SecretVault = class {
|
|
|
32923
33457
|
|
|
32924
33458
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32925
33459
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32926
|
-
import { join as
|
|
33460
|
+
import { join as join12 } from "path";
|
|
32927
33461
|
var MARKER = "warn-era-capped";
|
|
32928
33462
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32929
33463
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32930
|
-
const marker =
|
|
33464
|
+
const marker = join12(dataDir2, MARKER);
|
|
32931
33465
|
if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
|
|
32932
33466
|
const capped = db.policies.capCategoryActions();
|
|
32933
33467
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
@@ -32937,7 +33471,7 @@ function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
|
32937
33471
|
|
|
32938
33472
|
// ../../packages/plugin-sdk/src/config.ts
|
|
32939
33473
|
import { existsSync as existsSync7 } from "fs";
|
|
32940
|
-
import { join as
|
|
33474
|
+
import { join as join13 } from "path";
|
|
32941
33475
|
|
|
32942
33476
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
32943
33477
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -32991,7 +33525,7 @@ function resolveProvider() {
|
|
|
32991
33525
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32992
33526
|
try {
|
|
32993
33527
|
ensureLayoutDirSync(base);
|
|
32994
|
-
const settingsFile =
|
|
33528
|
+
const settingsFile = join13(settingsDir(base), "settings.json");
|
|
32995
33529
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
32996
33530
|
} catch {
|
|
32997
33531
|
}
|
|
@@ -33015,9 +33549,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
33015
33549
|
}
|
|
33016
33550
|
|
|
33017
33551
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
33018
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
33552
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
33019
33553
|
import { homedir as homedir2 } from "os";
|
|
33020
|
-
import { basename as basename3, join as
|
|
33554
|
+
import { basename as basename3, join as join15 } from "path";
|
|
33021
33555
|
|
|
33022
33556
|
// ../../packages/detections/src/egress/registry.ts
|
|
33023
33557
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -36106,8 +36640,8 @@ function bundledDetections() {
|
|
|
36106
36640
|
}
|
|
36107
36641
|
|
|
36108
36642
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
36109
|
-
import { existsSync as existsSync8, readFileSync as
|
|
36110
|
-
import { basename as basename2, dirname as
|
|
36643
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
36644
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join14, sep as sep2 } from "path";
|
|
36111
36645
|
function resolveRepo(cwd) {
|
|
36112
36646
|
try {
|
|
36113
36647
|
const root = findGitRoot(cwd);
|
|
@@ -36129,36 +36663,36 @@ function resolveWorktreeRoot(cwd) {
|
|
|
36129
36663
|
function findGitRoot(start) {
|
|
36130
36664
|
let dir = start;
|
|
36131
36665
|
for (; ; ) {
|
|
36132
|
-
if (existsSync8(
|
|
36133
|
-
const parent =
|
|
36666
|
+
if (existsSync8(join14(dir, ".git"))) return dir;
|
|
36667
|
+
const parent = dirname4(dir);
|
|
36134
36668
|
if (parent === dir) return void 0;
|
|
36135
36669
|
dir = parent;
|
|
36136
36670
|
}
|
|
36137
36671
|
}
|
|
36138
36672
|
function resolveGitContext(root) {
|
|
36139
|
-
const dotGit =
|
|
36673
|
+
const dotGit = join14(root, ".git");
|
|
36140
36674
|
try {
|
|
36141
36675
|
if (statSync6(dotGit).isDirectory()) {
|
|
36142
|
-
return { configPath:
|
|
36676
|
+
return { configPath: join14(dotGit, "config"), headRoot: root };
|
|
36143
36677
|
}
|
|
36144
36678
|
} catch {
|
|
36145
36679
|
return void 0;
|
|
36146
36680
|
}
|
|
36147
36681
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
36148
36682
|
if (!target) return void 0;
|
|
36149
|
-
const gitdir = isAbsolute(target) ? target :
|
|
36150
|
-
if (existsSync8(
|
|
36151
|
-
return { configPath:
|
|
36683
|
+
const gitdir = isAbsolute(target) ? target : join14(root, target);
|
|
36684
|
+
if (existsSync8(join14(gitdir, "config"))) {
|
|
36685
|
+
return { configPath: join14(gitdir, "config"), headRoot: root };
|
|
36152
36686
|
}
|
|
36153
|
-
const commonRaw = safeRead(
|
|
36687
|
+
const commonRaw = safeRead(join14(gitdir, "commondir"))?.trim();
|
|
36154
36688
|
if (!commonRaw) return void 0;
|
|
36155
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
36156
|
-
const headRoot = basename2(commonGitDir) === ".git" ?
|
|
36157
|
-
return { configPath:
|
|
36689
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join14(gitdir, commonRaw);
|
|
36690
|
+
const headRoot = basename2(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
|
|
36691
|
+
return { configPath: join14(commonGitDir, "config"), headRoot };
|
|
36158
36692
|
}
|
|
36159
36693
|
function safeRead(path) {
|
|
36160
36694
|
try {
|
|
36161
|
-
return
|
|
36695
|
+
return readFileSync8(path, "utf8");
|
|
36162
36696
|
} catch {
|
|
36163
36697
|
return void 0;
|
|
36164
36698
|
}
|
|
@@ -36703,8 +37237,8 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
36703
37237
|
|
|
36704
37238
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
36705
37239
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
36706
|
-
import { readFileSync as
|
|
36707
|
-
import { join as
|
|
37240
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
37241
|
+
import { join as join16 } from "path";
|
|
36708
37242
|
|
|
36709
37243
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
36710
37244
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -36715,20 +37249,55 @@ import {
|
|
|
36715
37249
|
fstatSync,
|
|
36716
37250
|
mkdirSync as mkdirSync2,
|
|
36717
37251
|
openSync as openSync2,
|
|
36718
|
-
readFileSync as
|
|
37252
|
+
readFileSync as readFileSync11,
|
|
36719
37253
|
readSync,
|
|
36720
37254
|
writeFileSync as writeFileSync5
|
|
36721
37255
|
} from "fs";
|
|
36722
|
-
import { join as
|
|
37256
|
+
import { join as join17 } from "path";
|
|
36723
37257
|
var TAIL_BYTES = 256 * 1024;
|
|
36724
37258
|
|
|
36725
37259
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
36726
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
36727
|
-
import { join as
|
|
37260
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
37261
|
+
import { join as join18 } from "path";
|
|
36728
37262
|
|
|
36729
37263
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
36730
37264
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
36731
|
-
import { basename as basename4, dirname as
|
|
37265
|
+
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
37266
|
+
|
|
37267
|
+
// ../../packages/plugin-sdk/src/policy-resolver.ts
|
|
37268
|
+
function createPolicyResolver(bundle) {
|
|
37269
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
37270
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
37271
|
+
let reversible = /* @__PURE__ */ new Set();
|
|
37272
|
+
try {
|
|
37273
|
+
for (const policy of bundle.policies) {
|
|
37274
|
+
if (!policy.enabled) continue;
|
|
37275
|
+
if ("ruleId" in policy.target) {
|
|
37276
|
+
if (!byRule.has(policy.target.ruleId)) byRule.set(policy.target.ruleId, policy.action);
|
|
37277
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
37278
|
+
byCategory.set(policy.target.category, policy.action);
|
|
37279
|
+
}
|
|
37280
|
+
}
|
|
37281
|
+
reversible = new Set(bundle.reversibleRuleIds ?? []);
|
|
37282
|
+
} catch {
|
|
37283
|
+
byRule.clear();
|
|
37284
|
+
byCategory.clear();
|
|
37285
|
+
reversible = /* @__PURE__ */ new Set();
|
|
37286
|
+
}
|
|
37287
|
+
return {
|
|
37288
|
+
actionFor(ruleId, category) {
|
|
37289
|
+
const byRuleAction = byRule.get(ruleId);
|
|
37290
|
+
if (byRuleAction !== void 0) return byRuleAction;
|
|
37291
|
+
const byCategoryAction = byCategory.get(category);
|
|
37292
|
+
if (byCategoryAction !== void 0) return byCategoryAction;
|
|
37293
|
+
const fallback = DEFAULT_ACTIONS[category];
|
|
37294
|
+
return fallback ?? "log";
|
|
37295
|
+
},
|
|
37296
|
+
isReversible(ruleId) {
|
|
37297
|
+
return reversible.has(ruleId);
|
|
37298
|
+
}
|
|
37299
|
+
};
|
|
37300
|
+
}
|
|
36732
37301
|
|
|
36733
37302
|
// ../../packages/plugin-sdk/src/posture.ts
|
|
36734
37303
|
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
@@ -36742,7 +37311,7 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
36742
37311
|
|
|
36743
37312
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
36744
37313
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
36745
|
-
import { basename as basename5, join as
|
|
37314
|
+
import { basename as basename5, join as join19 } from "path";
|
|
36746
37315
|
|
|
36747
37316
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
36748
37317
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -36783,7 +37352,6 @@ function safeMaskedMatch(rawMatch) {
|
|
|
36783
37352
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
36784
37353
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
36785
37354
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
36786
|
-
var ACTION_PRIORITY = ["block", "redact", "warn", "log", "allow"];
|
|
36787
37355
|
function startTiming() {
|
|
36788
37356
|
try {
|
|
36789
37357
|
return performance.now();
|
|
@@ -36820,28 +37388,22 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
36820
37388
|
bundlesPacked = true;
|
|
36821
37389
|
}
|
|
36822
37390
|
const policyMode = settings.policy;
|
|
37391
|
+
const redactFallback = settings.redactFallback;
|
|
36823
37392
|
const dataDir2 = opts?.dataDir;
|
|
36824
|
-
let policies = [];
|
|
36825
37393
|
let rules = [];
|
|
36826
37394
|
let scanner;
|
|
36827
37395
|
let bundleExceptions = [];
|
|
36828
37396
|
let initialized = false;
|
|
36829
|
-
|
|
36830
|
-
|
|
36831
|
-
|
|
37397
|
+
let resolver = createPolicyResolver({
|
|
37398
|
+
version: "",
|
|
37399
|
+
policies: [],
|
|
37400
|
+
customKeywords: [],
|
|
37401
|
+
fetchedAt: ""
|
|
37402
|
+
});
|
|
36832
37403
|
async function ensureInitialized() {
|
|
36833
37404
|
if (initialized) return;
|
|
36834
37405
|
const bundle = await gateway.getPolicyBundle();
|
|
36835
|
-
|
|
36836
|
-
for (const p of policies) {
|
|
36837
|
-
if (!p.enabled) continue;
|
|
36838
|
-
if ("ruleId" in p.target) {
|
|
36839
|
-
if (!ruleActionIndex.has(p.target.ruleId)) ruleActionIndex.set(p.target.ruleId, p.action);
|
|
36840
|
-
} else if (!categoryActionIndex.has(p.target.category)) {
|
|
36841
|
-
categoryActionIndex.set(p.target.category, p.action);
|
|
36842
|
-
}
|
|
36843
|
-
}
|
|
36844
|
-
reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
|
|
37406
|
+
resolver = createPolicyResolver(bundle);
|
|
36845
37407
|
const bundledProbeKeys = new Set(
|
|
36846
37408
|
getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
|
|
36847
37409
|
);
|
|
@@ -36894,33 +37456,28 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
36894
37456
|
return cachedKey;
|
|
36895
37457
|
}
|
|
36896
37458
|
function resolveAction(ruleId, category) {
|
|
36897
|
-
|
|
36898
|
-
|
|
36899
|
-
|
|
36900
|
-
if (byCategory !== void 0) return byCategory;
|
|
36901
|
-
const fallback = DEFAULT_ACTIONS[category];
|
|
36902
|
-
return fallback ?? "log";
|
|
36903
|
-
}
|
|
36904
|
-
function actionForFinding(finding, excepted) {
|
|
37459
|
+
return resolver.actionFor(ruleId, category);
|
|
37460
|
+
}
|
|
37461
|
+
function actionForFinding(finding, excepted, rewritable = true) {
|
|
36905
37462
|
if (excepted?.has(finding)) return "allow";
|
|
36906
37463
|
const action = resolveAction(finding.ruleId, finding.category);
|
|
37464
|
+
if (!rewritable && action === "redact") return builtinPolicyToAction(redactFallback);
|
|
36907
37465
|
if (ENFORCEMENT_CEILING_ENABLED && policyMode === "warn" && (action === "block" || action === "redact")) {
|
|
36908
37466
|
return "warn";
|
|
36909
37467
|
}
|
|
36910
37468
|
return action;
|
|
36911
37469
|
}
|
|
36912
|
-
function decide(findings, text, excepted) {
|
|
37470
|
+
function decide(findings, text, excepted, rewritable = true) {
|
|
36913
37471
|
if (findings.length === 0) return { action: "log", text, findings: [] };
|
|
36914
|
-
const actionFor = (finding) => actionForFinding(finding, excepted);
|
|
37472
|
+
const actionFor = (finding) => actionForFinding(finding, excepted, rewritable);
|
|
36915
37473
|
let worst = "log";
|
|
36916
37474
|
for (const finding of findings) {
|
|
36917
|
-
|
|
36918
|
-
if (ACTION_PRIORITY.indexOf(action) < ACTION_PRIORITY.indexOf(worst)) worst = action;
|
|
37475
|
+
worst = strongerAction(worst, actionFor(finding));
|
|
36919
37476
|
}
|
|
36920
37477
|
if (worst === "block") return { action: "block", text: null, findings };
|
|
36921
37478
|
if (worst === "redact") {
|
|
36922
37479
|
const redactFindings = findings.filter((f) => actionFor(f) === "redact");
|
|
36923
|
-
const reversibleFindings = redactFindings.filter((f) =>
|
|
37480
|
+
const reversibleFindings = redactFindings.filter((f) => resolver.isReversible(f.ruleId));
|
|
36924
37481
|
return {
|
|
36925
37482
|
action: "redact",
|
|
36926
37483
|
text: redact(text, redactFindings),
|
|
@@ -36996,7 +37553,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
36996
37553
|
return { excepted: /* @__PURE__ */ new Set(), exceptionIds: [] };
|
|
36997
37554
|
}
|
|
36998
37555
|
}
|
|
36999
|
-
async function recordBlockedDetections(decision, excepted, ctx, fpCache) {
|
|
37556
|
+
async function recordBlockedDetections(decision, excepted, ctx, fpCache, rewritable = true) {
|
|
37000
37557
|
const references = [];
|
|
37001
37558
|
try {
|
|
37002
37559
|
if (decision.action !== "block" && decision.action !== "redact") return references;
|
|
@@ -37004,7 +37561,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37004
37561
|
if (!key) return references;
|
|
37005
37562
|
const seen = /* @__PURE__ */ new Set();
|
|
37006
37563
|
for (const finding of decision.findings) {
|
|
37007
|
-
const action = actionForFinding(finding, excepted);
|
|
37564
|
+
const action = actionForFinding(finding, excepted, rewritable);
|
|
37008
37565
|
if (action !== "block" && action !== "redact") continue;
|
|
37009
37566
|
const fp = fingerprintOf(key, finding, fpCache);
|
|
37010
37567
|
const pair = `${finding.ruleId}:${fp}`;
|
|
@@ -37031,7 +37588,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37031
37588
|
}
|
|
37032
37589
|
return references;
|
|
37033
37590
|
}
|
|
37034
|
-
async function evaluate(text, context, ctx) {
|
|
37591
|
+
async function evaluate(text, context, ctx, rewritable = true) {
|
|
37035
37592
|
try {
|
|
37036
37593
|
await ensureInitialized();
|
|
37037
37594
|
if (!scanner) throw new Error("the runtime initialized without a scanner");
|
|
@@ -37040,8 +37597,14 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37040
37597
|
const findings = dropShieldedFindings(matched, shielded.spans);
|
|
37041
37598
|
const fpCache = /* @__PURE__ */ new Map();
|
|
37042
37599
|
const { excepted, exceptionIds } = await applyExceptions(findings, ctx, fpCache);
|
|
37043
|
-
const decision = decide(findings, text, excepted);
|
|
37044
|
-
const blockedReferences = await recordBlockedDetections(
|
|
37600
|
+
const decision = decide(findings, text, excepted, rewritable);
|
|
37601
|
+
const blockedReferences = await recordBlockedDetections(
|
|
37602
|
+
decision,
|
|
37603
|
+
excepted,
|
|
37604
|
+
ctx,
|
|
37605
|
+
fpCache,
|
|
37606
|
+
rewritable
|
|
37607
|
+
);
|
|
37045
37608
|
if (blockedReferences.length > 0) decision.blockedReferences = blockedReferences;
|
|
37046
37609
|
return { decision, excepted, exceptionIds };
|
|
37047
37610
|
} catch {
|
|
@@ -37065,12 +37628,16 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37065
37628
|
sourceTool: input2.sourceTool,
|
|
37066
37629
|
metadata: input2.metadata,
|
|
37067
37630
|
preAuthorizedGrantIds: opts2.preAuthorizedGrantIds
|
|
37068
|
-
}
|
|
37631
|
+
},
|
|
37632
|
+
opts2.rewritable
|
|
37069
37633
|
);
|
|
37070
37634
|
if (opts2.persist === "with-findings" && decision.findings.length === 0) return decision;
|
|
37071
37635
|
try {
|
|
37072
37636
|
const contentHash = contentHashOf(input2.text);
|
|
37073
|
-
const
|
|
37637
|
+
const maskedFindings = decision.findings.filter(
|
|
37638
|
+
(match) => isActionAtLeast(actionForFinding(match, excepted, opts2.rewritable), "redact")
|
|
37639
|
+
);
|
|
37640
|
+
const storedContent = maskedFindings.length > 0 ? redact(input2.text, maskedFindings) : input2.text;
|
|
37074
37641
|
const inspectionMs = elapsedMs(timingStartedAt);
|
|
37075
37642
|
const metadata = exceptionIds.length > 0 || inspectionMs !== void 0 ? {
|
|
37076
37643
|
...input2.metadata,
|
|
@@ -37107,7 +37674,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
37107
37674
|
severity: match.severity,
|
|
37108
37675
|
span: match.span,
|
|
37109
37676
|
maskedMatch,
|
|
37110
|
-
actionTaken: actionForFinding(match, excepted),
|
|
37677
|
+
actionTaken: actionForFinding(match, excepted, opts2.rewritable),
|
|
37111
37678
|
confidence: match.confidence,
|
|
37112
37679
|
...findingKey ? { findingKey } : {}
|
|
37113
37680
|
};
|
|
@@ -37147,7 +37714,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
37147
37714
|
|
|
37148
37715
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
37149
37716
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
37150
|
-
import { join as
|
|
37717
|
+
import { join as join20 } from "path";
|
|
37151
37718
|
|
|
37152
37719
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
37153
37720
|
function redactedPlaceholder(category) {
|
|
@@ -37209,14 +37776,26 @@ var SecretVaultGlue = class {
|
|
|
37209
37776
|
}
|
|
37210
37777
|
async tokenizeText(text, opts) {
|
|
37211
37778
|
try {
|
|
37212
|
-
const
|
|
37213
|
-
const
|
|
37214
|
-
const
|
|
37215
|
-
if (
|
|
37216
|
-
|
|
37779
|
+
const supplied = opts?.findings;
|
|
37780
|
+
const resolver = opts?.resolver;
|
|
37781
|
+
const scanned = supplied ?? this.#selfScan(text);
|
|
37782
|
+
if (scanned === null) {
|
|
37783
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
37784
|
+
}
|
|
37785
|
+
const findings = supplied === void 0 && resolver !== void 0 ? scanned.filter(
|
|
37786
|
+
(f) => isActionAtLeast(resolver.actionFor(f.ruleId, f.category), "redact")
|
|
37787
|
+
) : scanned;
|
|
37788
|
+
let reversible = opts?.reversible;
|
|
37789
|
+
if (resolver !== void 0 && reversible === void 0) {
|
|
37790
|
+
reversible = new Set(findings.filter((f) => resolver.isReversible(f.ruleId)));
|
|
37791
|
+
}
|
|
37792
|
+
const reversibleSet = reversible;
|
|
37793
|
+
const keeps = (finding) => reversibleSet === void 0 || reversibleSet.has(finding);
|
|
37794
|
+
if (findings.length === 0) return { text, pointers: [], degraded: [], redacted: [] };
|
|
37217
37795
|
const groups = groupSpans(text, findings);
|
|
37218
37796
|
const pointers = [];
|
|
37219
37797
|
const degraded = [];
|
|
37798
|
+
const redacted = [];
|
|
37220
37799
|
let out = text;
|
|
37221
37800
|
for (const group of [...groups].reverse()) {
|
|
37222
37801
|
const original = text.slice(group.start, group.end);
|
|
@@ -37230,6 +37809,7 @@ var SecretVaultGlue = class {
|
|
|
37230
37809
|
degraded.unshift({ category: group.category });
|
|
37231
37810
|
} else if (!keeps(finding)) {
|
|
37232
37811
|
replacement = redactedPlaceholder(finding.category);
|
|
37812
|
+
redacted.unshift({ category: finding.category });
|
|
37233
37813
|
} else {
|
|
37234
37814
|
replacement = await this.tokenizeValue(finding.rawMatch, {
|
|
37235
37815
|
ruleId: finding.ruleId,
|
|
@@ -37250,9 +37830,9 @@ var SecretVaultGlue = class {
|
|
|
37250
37830
|
}
|
|
37251
37831
|
}
|
|
37252
37832
|
}
|
|
37253
|
-
return { text: out, pointers, degraded };
|
|
37833
|
+
return { text: out, pointers, degraded, redacted };
|
|
37254
37834
|
} catch {
|
|
37255
|
-
return { text: "[REDACTED]", pointers: [], degraded: [] };
|
|
37835
|
+
return { text: "[REDACTED]", pointers: [], degraded: [], redacted: [] };
|
|
37256
37836
|
}
|
|
37257
37837
|
}
|
|
37258
37838
|
async detokenizeText(text, opts) {
|
|
@@ -37490,7 +38070,7 @@ function routeRemediationOption(option, handlers) {
|
|
|
37490
38070
|
|
|
37491
38071
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
37492
38072
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
37493
|
-
import { join as
|
|
38073
|
+
import { join as join21 } from "path";
|
|
37494
38074
|
var GENERIC_CONSOLE_PATH = "rotate via the provider's own console";
|
|
37495
38075
|
var CONSOLE_PATHS = {
|
|
37496
38076
|
anthropic: "console.anthropic.com \u2192 Settings \u2192 API keys",
|
|
@@ -37590,7 +38170,7 @@ function generateRotationChecklist(input2) {
|
|
|
37590
38170
|
try {
|
|
37591
38171
|
const target = resolveRotationChecklistTarget(input2.cwd);
|
|
37592
38172
|
targetDirectory = target.directory;
|
|
37593
|
-
const filePath =
|
|
38173
|
+
const filePath = join21(target.directory, "rotation-checklist.md");
|
|
37594
38174
|
writeRotationChecklist(input2.entries, target.directory);
|
|
37595
38175
|
return {
|
|
37596
38176
|
status: "written",
|
|
@@ -37701,9 +38281,9 @@ var RANK = Object.fromEntries(
|
|
|
37701
38281
|
);
|
|
37702
38282
|
|
|
37703
38283
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
37704
|
-
import { mkdtempSync, readFileSync as
|
|
38284
|
+
import { mkdtempSync, readFileSync as readFileSync13, rmdirSync, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
37705
38285
|
import { tmpdir } from "os";
|
|
37706
|
-
import { basename as basename6, dirname as
|
|
38286
|
+
import { basename as basename6, dirname as dirname6, join as join22 } from "path";
|
|
37707
38287
|
var SuppressionEntrySchema = external_exports.object({
|
|
37708
38288
|
ruleId: external_exports.string(),
|
|
37709
38289
|
category: DetectionCategory,
|
|
@@ -37907,7 +38487,7 @@ function renderRemediationDecision(findings, moreCount, registry2) {
|
|
|
37907
38487
|
}
|
|
37908
38488
|
|
|
37909
38489
|
// src/remediation/surfaced-redact.ts
|
|
37910
|
-
import { readFileSync as
|
|
38490
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
37911
38491
|
|
|
37912
38492
|
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
37913
38493
|
import { createHash as createHash5 } from "crypto";
|
|
@@ -37946,6 +38526,307 @@ function toEgressIngestRequest(input2) {
|
|
|
37946
38526
|
};
|
|
37947
38527
|
}
|
|
37948
38528
|
|
|
38529
|
+
// ../../packages/remote/src/http.ts
|
|
38530
|
+
import { request as httpRequest } from "http";
|
|
38531
|
+
import { request as httpsRequest } from "https";
|
|
38532
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
38533
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
38534
|
+
var RemoteRequestError = class extends Error {
|
|
38535
|
+
constructor(status) {
|
|
38536
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
38537
|
+
this.status = status;
|
|
38538
|
+
this.name = "RemoteRequestError";
|
|
38539
|
+
}
|
|
38540
|
+
status;
|
|
38541
|
+
};
|
|
38542
|
+
var RemoteRouteAbsent = class extends Error {
|
|
38543
|
+
constructor(route2) {
|
|
38544
|
+
super(`control plane does not serve ${route2}`);
|
|
38545
|
+
this.route = route2;
|
|
38546
|
+
this.name = "RemoteRouteAbsent";
|
|
38547
|
+
}
|
|
38548
|
+
route;
|
|
38549
|
+
};
|
|
38550
|
+
var RemoteRequestInvalid = class extends Error {
|
|
38551
|
+
constructor(route2, cause) {
|
|
38552
|
+
super(`refusing to send a malformed body to ${route2}`);
|
|
38553
|
+
this.cause = cause;
|
|
38554
|
+
this.name = "RemoteRequestInvalid";
|
|
38555
|
+
}
|
|
38556
|
+
cause;
|
|
38557
|
+
};
|
|
38558
|
+
var RemoteResponseInvalid = class extends Error {
|
|
38559
|
+
constructor(route2, detail) {
|
|
38560
|
+
super(`control plane answered ${route2} with ${detail}`);
|
|
38561
|
+
this.name = "RemoteResponseInvalid";
|
|
38562
|
+
}
|
|
38563
|
+
};
|
|
38564
|
+
var RemoteTransportError = class extends Error {
|
|
38565
|
+
/**
|
|
38566
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
38567
|
+
* refused.
|
|
38568
|
+
*
|
|
38569
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
38570
|
+
* all. It exists because two paths reject after a status has already been
|
|
38571
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
38572
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
38573
|
+
* outage, which sends the reader to look at their network instead of their
|
|
38574
|
+
* credential.
|
|
38575
|
+
*/
|
|
38576
|
+
constructor(reason, status) {
|
|
38577
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
38578
|
+
this.status = status;
|
|
38579
|
+
this.name = "RemoteTransportError";
|
|
38580
|
+
}
|
|
38581
|
+
status;
|
|
38582
|
+
};
|
|
38583
|
+
async function send(options) {
|
|
38584
|
+
const url2 = new URL(options.url);
|
|
38585
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
38586
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
38587
|
+
const requestOptions = {
|
|
38588
|
+
method: options.method,
|
|
38589
|
+
headers: {
|
|
38590
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
38591
|
+
// last they win, and two of the values below are ones no caller may
|
|
38592
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
38593
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
38594
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
38595
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
38596
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
38597
|
+
// conditional GET — is untouched by this order.
|
|
38598
|
+
...options.headers,
|
|
38599
|
+
// The credential. One header, matching what the deployment authenticates
|
|
38600
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
38601
|
+
// it can be logged by an intermediary for no gain.
|
|
38602
|
+
//
|
|
38603
|
+
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
38604
|
+
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
38605
|
+
// key differently from an absent one, and "the header is not there" is
|
|
38606
|
+
// the property the attach flow needs.
|
|
38607
|
+
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
38608
|
+
accept: "application/json",
|
|
38609
|
+
...options.body === void 0 ? {} : {
|
|
38610
|
+
"content-type": "application/json",
|
|
38611
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
38612
|
+
// character count is truncated by the receiver.
|
|
38613
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
38614
|
+
}
|
|
38615
|
+
}
|
|
38616
|
+
};
|
|
38617
|
+
return new Promise((resolve3, reject) => {
|
|
38618
|
+
let settled = false;
|
|
38619
|
+
const fail2 = (reason, status) => {
|
|
38620
|
+
if (settled) return;
|
|
38621
|
+
settled = true;
|
|
38622
|
+
reject(new RemoteTransportError(reason, status));
|
|
38623
|
+
};
|
|
38624
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
38625
|
+
const chunks = [];
|
|
38626
|
+
let size = 0;
|
|
38627
|
+
res.on("data", (chunk) => {
|
|
38628
|
+
size += chunk.length;
|
|
38629
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
38630
|
+
fail2(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
38631
|
+
res.destroy();
|
|
38632
|
+
req.destroy();
|
|
38633
|
+
return;
|
|
38634
|
+
}
|
|
38635
|
+
chunks.push(chunk);
|
|
38636
|
+
});
|
|
38637
|
+
res.on("aborted", () => {
|
|
38638
|
+
fail2("the response was aborted", res.statusCode);
|
|
38639
|
+
});
|
|
38640
|
+
res.on("end", () => {
|
|
38641
|
+
if (settled) return;
|
|
38642
|
+
settled = true;
|
|
38643
|
+
resolve3({
|
|
38644
|
+
status: res.statusCode ?? 0,
|
|
38645
|
+
headers: res.headers,
|
|
38646
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
38647
|
+
});
|
|
38648
|
+
});
|
|
38649
|
+
});
|
|
38650
|
+
const deadline = setTimeout(() => {
|
|
38651
|
+
fail2(`no response within ${String(timeoutMs)}ms`);
|
|
38652
|
+
req.destroy();
|
|
38653
|
+
}, timeoutMs);
|
|
38654
|
+
deadline.unref();
|
|
38655
|
+
req.on("upgrade", (_res, socket) => {
|
|
38656
|
+
fail2("the deployment answered with a protocol upgrade");
|
|
38657
|
+
socket.destroy();
|
|
38658
|
+
});
|
|
38659
|
+
req.on("close", () => {
|
|
38660
|
+
fail2("the connection closed before a response was read");
|
|
38661
|
+
clearTimeout(deadline);
|
|
38662
|
+
});
|
|
38663
|
+
req.on("error", (err) => {
|
|
38664
|
+
fail2(err.message);
|
|
38665
|
+
});
|
|
38666
|
+
if (options.body !== void 0) req.write(options.body);
|
|
38667
|
+
req.end();
|
|
38668
|
+
});
|
|
38669
|
+
}
|
|
38670
|
+
|
|
38671
|
+
// ../../packages/remote/src/client.ts
|
|
38672
|
+
var ROUTES = {
|
|
38673
|
+
events: "/v1/events",
|
|
38674
|
+
auditEvents: "/v1/audit-events",
|
|
38675
|
+
auditEventsBatch: "/v1/audit-events/batch",
|
|
38676
|
+
inventory: "/v1/inventory",
|
|
38677
|
+
storePosture: "/v1/store-posture",
|
|
38678
|
+
policyBundle: "/v1/policy-bundle",
|
|
38679
|
+
whoami: "/v1/plugin/whoami",
|
|
38680
|
+
shares: "/v1/shares",
|
|
38681
|
+
commands: "/v1/plugin/commands"
|
|
38682
|
+
};
|
|
38683
|
+
function ackRoute(id) {
|
|
38684
|
+
return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
|
|
38685
|
+
}
|
|
38686
|
+
function headerValue(response, name) {
|
|
38687
|
+
const raw = response.headers[name];
|
|
38688
|
+
if (raw === void 0) return void 0;
|
|
38689
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
38690
|
+
}
|
|
38691
|
+
function okBody(response) {
|
|
38692
|
+
if (response.status < 200 || response.status >= 300) {
|
|
38693
|
+
throw new RemoteRequestError(response.status);
|
|
38694
|
+
}
|
|
38695
|
+
return response.body;
|
|
38696
|
+
}
|
|
38697
|
+
function parsed(schema, body, route2) {
|
|
38698
|
+
let json2;
|
|
38699
|
+
try {
|
|
38700
|
+
json2 = JSON.parse(body);
|
|
38701
|
+
} catch {
|
|
38702
|
+
throw new RemoteResponseInvalid(route2, "a body that is not JSON");
|
|
38703
|
+
}
|
|
38704
|
+
const result = schema.safeParse(json2);
|
|
38705
|
+
if (!result.success) {
|
|
38706
|
+
throw new RemoteResponseInvalid(route2, "a body this client cannot read");
|
|
38707
|
+
}
|
|
38708
|
+
return result.data;
|
|
38709
|
+
}
|
|
38710
|
+
function withoutTrailingSlashes(endpoint) {
|
|
38711
|
+
let end = endpoint.length;
|
|
38712
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
38713
|
+
return endpoint.slice(0, end);
|
|
38714
|
+
}
|
|
38715
|
+
var SLASH = "/".charCodeAt(0);
|
|
38716
|
+
function createRemoteClient(options) {
|
|
38717
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
38718
|
+
const url2 = (route2) => `${base}${route2}`;
|
|
38719
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
38720
|
+
const sendOne = async (event) => {
|
|
38721
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
38722
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
38723
|
+
const response = await send({
|
|
38724
|
+
...common,
|
|
38725
|
+
method: "POST",
|
|
38726
|
+
url: url2(ROUTES.auditEvents),
|
|
38727
|
+
body: JSON.stringify(validated.data)
|
|
38728
|
+
});
|
|
38729
|
+
okBody(response);
|
|
38730
|
+
};
|
|
38731
|
+
return {
|
|
38732
|
+
async ingestEvents(batch) {
|
|
38733
|
+
const response = await send({
|
|
38734
|
+
...common,
|
|
38735
|
+
method: "POST",
|
|
38736
|
+
url: url2(ROUTES.events),
|
|
38737
|
+
body: JSON.stringify(batch)
|
|
38738
|
+
});
|
|
38739
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
38740
|
+
},
|
|
38741
|
+
async ingestInventory(context) {
|
|
38742
|
+
const response = await send({
|
|
38743
|
+
...common,
|
|
38744
|
+
method: "POST",
|
|
38745
|
+
url: url2(ROUTES.inventory),
|
|
38746
|
+
body: JSON.stringify(context)
|
|
38747
|
+
});
|
|
38748
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
38749
|
+
},
|
|
38750
|
+
async recordAuditEvent(event) {
|
|
38751
|
+
await sendOne(event);
|
|
38752
|
+
},
|
|
38753
|
+
async recordAuditEvents(events, opts) {
|
|
38754
|
+
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
38755
|
+
if (!validated.success) {
|
|
38756
|
+
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
38757
|
+
}
|
|
38758
|
+
const response = await send({
|
|
38759
|
+
...common,
|
|
38760
|
+
method: "POST",
|
|
38761
|
+
url: url2(ROUTES.auditEventsBatch),
|
|
38762
|
+
body: JSON.stringify(validated.data)
|
|
38763
|
+
});
|
|
38764
|
+
if (response.status === 404) {
|
|
38765
|
+
if (opts?.fallbackToSingleEvents !== true) {
|
|
38766
|
+
throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
|
|
38767
|
+
}
|
|
38768
|
+
for (const event of validated.data.events) await sendOne(event);
|
|
38769
|
+
return { accepted: validated.data.events.length };
|
|
38770
|
+
}
|
|
38771
|
+
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
38772
|
+
},
|
|
38773
|
+
async reportStorePosture(snapshot) {
|
|
38774
|
+
const response = await send({
|
|
38775
|
+
...common,
|
|
38776
|
+
method: "POST",
|
|
38777
|
+
url: url2(ROUTES.storePosture),
|
|
38778
|
+
body: JSON.stringify(snapshot)
|
|
38779
|
+
});
|
|
38780
|
+
okBody(response);
|
|
38781
|
+
},
|
|
38782
|
+
async getPolicyBundle(etag) {
|
|
38783
|
+
const response = await send({
|
|
38784
|
+
...common,
|
|
38785
|
+
method: "GET",
|
|
38786
|
+
url: url2(ROUTES.policyBundle),
|
|
38787
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
38788
|
+
});
|
|
38789
|
+
if (response.status === 304) {
|
|
38790
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
38791
|
+
}
|
|
38792
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
38793
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
38794
|
+
},
|
|
38795
|
+
async whoami() {
|
|
38796
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
38797
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
38798
|
+
},
|
|
38799
|
+
async recordProjectEgress(request) {
|
|
38800
|
+
const validated = EgressIngestRequest.safeParse(request);
|
|
38801
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
38802
|
+
const response = await send({
|
|
38803
|
+
...common,
|
|
38804
|
+
method: "POST",
|
|
38805
|
+
url: url2(ROUTES.shares),
|
|
38806
|
+
body: JSON.stringify(validated.data)
|
|
38807
|
+
});
|
|
38808
|
+
okBody(response);
|
|
38809
|
+
},
|
|
38810
|
+
async pollCommand() {
|
|
38811
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
|
|
38812
|
+
if (response.status === 404) return null;
|
|
38813
|
+
return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
|
|
38814
|
+
},
|
|
38815
|
+
async ackCommand(id, body) {
|
|
38816
|
+
const validated = DeviceCommandAckBody.safeParse(body);
|
|
38817
|
+
const route2 = ackRoute(id);
|
|
38818
|
+
if (!validated.success) throw new RemoteRequestInvalid(route2, validated.error);
|
|
38819
|
+
const response = await send({
|
|
38820
|
+
...common,
|
|
38821
|
+
method: "POST",
|
|
38822
|
+
url: url2(route2),
|
|
38823
|
+
body: JSON.stringify(validated.data)
|
|
38824
|
+
});
|
|
38825
|
+
okBody(response);
|
|
38826
|
+
}
|
|
38827
|
+
};
|
|
38828
|
+
}
|
|
38829
|
+
|
|
37949
38830
|
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
37950
38831
|
function statusOf(err) {
|
|
37951
38832
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
@@ -37964,12 +38845,27 @@ function classifyFailure(err) {
|
|
|
37964
38845
|
}
|
|
37965
38846
|
}
|
|
37966
38847
|
|
|
38848
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
38849
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
38850
|
+
function withTimeout(promise2, ms) {
|
|
38851
|
+
let timer;
|
|
38852
|
+
const timeout = new Promise((_, reject) => {
|
|
38853
|
+
timer = setTimeout(() => {
|
|
38854
|
+
reject(new Error("attached gateway request timed out"));
|
|
38855
|
+
}, ms);
|
|
38856
|
+
});
|
|
38857
|
+
promise2.catch(() => void 0);
|
|
38858
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
38859
|
+
clearTimeout(timer);
|
|
38860
|
+
});
|
|
38861
|
+
}
|
|
38862
|
+
|
|
37967
38863
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
37968
|
-
import { readFileSync as
|
|
37969
|
-
import { join as
|
|
38864
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
38865
|
+
import { join as join23 } from "path";
|
|
37970
38866
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
37971
38867
|
function forwardDropsPath(dataDir2) {
|
|
37972
|
-
return
|
|
38868
|
+
return join23(dataDir2, FORWARD_DROPS_FILENAME);
|
|
37973
38869
|
}
|
|
37974
38870
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
37975
38871
|
if (count <= 0) return;
|
|
@@ -37987,7 +38883,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
|
|
|
37987
38883
|
}
|
|
37988
38884
|
function readForwardDrops(dataDir2) {
|
|
37989
38885
|
try {
|
|
37990
|
-
const parsed2 = JSON.parse(
|
|
38886
|
+
const parsed2 = JSON.parse(readFileSync14(forwardDropsPath(dataDir2), "utf8"));
|
|
37991
38887
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
37992
38888
|
const record2 = parsed2;
|
|
37993
38889
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -38005,29 +38901,19 @@ function readForwardDrops(dataDir2) {
|
|
|
38005
38901
|
|
|
38006
38902
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
38007
38903
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
38008
|
-
import { readFileSync as
|
|
38904
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
38009
38905
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
38010
|
-
import { join as
|
|
38011
|
-
|
|
38012
|
-
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
38013
|
-
var REQUEST_TIMEOUT_MS = 2e3;
|
|
38014
|
-
function withTimeout(promise2, ms) {
|
|
38015
|
-
let timer;
|
|
38016
|
-
const timeout = new Promise((_, reject) => {
|
|
38017
|
-
timer = setTimeout(() => {
|
|
38018
|
-
reject(new Error("attached gateway request timed out"));
|
|
38019
|
-
}, ms);
|
|
38020
|
-
});
|
|
38021
|
-
promise2.catch(() => void 0);
|
|
38022
|
-
return Promise.race([promise2, timeout]).finally(() => {
|
|
38023
|
-
clearTimeout(timer);
|
|
38024
|
-
});
|
|
38025
|
-
}
|
|
38026
|
-
|
|
38027
|
-
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
38906
|
+
import { join as join24 } from "path";
|
|
38028
38907
|
function isInvalidRequest(err) {
|
|
38029
38908
|
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
38030
38909
|
}
|
|
38910
|
+
function isRouteAbsent(err) {
|
|
38911
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
|
|
38912
|
+
}
|
|
38913
|
+
function isServerRejection(err) {
|
|
38914
|
+
const status = statusOf(err);
|
|
38915
|
+
return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
|
|
38916
|
+
}
|
|
38031
38917
|
var FORWARD_BUDGET_MS = 1500;
|
|
38032
38918
|
var DECISION_PATH_BUDGET_MS = 800;
|
|
38033
38919
|
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
@@ -38055,7 +38941,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
38055
38941
|
}
|
|
38056
38942
|
function createForwardPolicy(deps) {
|
|
38057
38943
|
const now = deps.now ?? (() => Date.now());
|
|
38058
|
-
const file2 =
|
|
38944
|
+
const file2 = join24(deps.dir, STATE_FILENAME);
|
|
38059
38945
|
let state = null;
|
|
38060
38946
|
let loading = null;
|
|
38061
38947
|
async function readState() {
|
|
@@ -38095,6 +38981,20 @@ function createForwardPolicy(deps) {
|
|
|
38095
38981
|
} catch {
|
|
38096
38982
|
current = { ...CLOSED };
|
|
38097
38983
|
}
|
|
38984
|
+
const restoreOpenedAtMs = (openedAtMs) => persist({
|
|
38985
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
38986
|
+
openedAtMs,
|
|
38987
|
+
lastFailure: current.lastFailure
|
|
38988
|
+
});
|
|
38989
|
+
const recordFailure = (cause) => {
|
|
38990
|
+
const failures = current.consecutiveFailures + 1;
|
|
38991
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
38992
|
+
return persist({
|
|
38993
|
+
consecutiveFailures: failures,
|
|
38994
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
38995
|
+
lastFailure: cause
|
|
38996
|
+
});
|
|
38997
|
+
};
|
|
38098
38998
|
const at = now();
|
|
38099
38999
|
if (current.openedAtMs !== null) {
|
|
38100
39000
|
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
@@ -38113,15 +39013,20 @@ function createForwardPolicy(deps) {
|
|
|
38113
39013
|
}
|
|
38114
39014
|
return { ok: true, value };
|
|
38115
39015
|
} catch (err) {
|
|
38116
|
-
if (isInvalidRequest(err))
|
|
39016
|
+
if (isInvalidRequest(err)) {
|
|
39017
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
|
|
39018
|
+
return { ok: false, reason: "invalid-request" };
|
|
39019
|
+
}
|
|
39020
|
+
if (isRouteAbsent(err)) {
|
|
39021
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
|
|
39022
|
+
return { ok: false, reason: "route-absent" };
|
|
39023
|
+
}
|
|
39024
|
+
if (isServerRejection(err)) {
|
|
39025
|
+
await recordFailure("unreachable");
|
|
39026
|
+
return { ok: false, reason: "rejected" };
|
|
39027
|
+
}
|
|
38117
39028
|
const reason = classifyFailure(err);
|
|
38118
|
-
|
|
38119
|
-
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
38120
|
-
await persist({
|
|
38121
|
-
consecutiveFailures: failures,
|
|
38122
|
-
openedAtMs: shouldOpen ? now() : null,
|
|
38123
|
-
lastFailure: reason
|
|
38124
|
-
});
|
|
39029
|
+
await recordFailure(reason);
|
|
38125
39030
|
return { ok: false, reason };
|
|
38126
39031
|
}
|
|
38127
39032
|
}
|
|
@@ -38129,13 +39034,11 @@ function createForwardPolicy(deps) {
|
|
|
38129
39034
|
}
|
|
38130
39035
|
|
|
38131
39036
|
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
38132
|
-
|
|
38133
|
-
|
|
38134
|
-
|
|
38135
|
-
|
|
38136
|
-
|
|
38137
|
-
block: 4
|
|
38138
|
-
};
|
|
39037
|
+
function strongerOf(a, b) {
|
|
39038
|
+
if (a === null) return b;
|
|
39039
|
+
if (b === null) return a;
|
|
39040
|
+
return strongerAction(a, b);
|
|
39041
|
+
}
|
|
38139
39042
|
function ruleCategoryMap(wireRules, localRules) {
|
|
38140
39043
|
const map2 = /* @__PURE__ */ new Map();
|
|
38141
39044
|
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
@@ -38145,11 +39048,6 @@ function ruleCategoryMap(wireRules, localRules) {
|
|
|
38145
39048
|
}
|
|
38146
39049
|
return map2;
|
|
38147
39050
|
}
|
|
38148
|
-
function strongerOf(a, b) {
|
|
38149
|
-
if (a === null) return b;
|
|
38150
|
-
if (b === null) return a;
|
|
38151
|
-
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
38152
|
-
}
|
|
38153
39051
|
function policyKey(policy) {
|
|
38154
39052
|
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
38155
39053
|
}
|
|
@@ -38168,7 +39066,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
38168
39066
|
const floor = floorFor(policy, categoryByRuleId);
|
|
38169
39067
|
remoteCategoryAction.set(
|
|
38170
39068
|
policy.target.category,
|
|
38171
|
-
floor !== null &&
|
|
39069
|
+
floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
|
|
38172
39070
|
);
|
|
38173
39071
|
}
|
|
38174
39072
|
for (const policy of localPolicies) {
|
|
@@ -38185,7 +39083,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
38185
39083
|
}
|
|
38186
39084
|
merged.set(
|
|
38187
39085
|
key,
|
|
38188
|
-
remoteFloor !== null &&
|
|
39086
|
+
remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
|
|
38189
39087
|
);
|
|
38190
39088
|
}
|
|
38191
39089
|
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
@@ -38205,13 +39103,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
38205
39103
|
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
38206
39104
|
}
|
|
38207
39105
|
const effectiveFloor = strongerOf(floor, localFloor);
|
|
38208
|
-
const clamped = effectiveFloor !== null &&
|
|
39106
|
+
const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
|
|
38209
39107
|
const existing = merged.get(key);
|
|
38210
39108
|
if (existing === void 0) {
|
|
38211
39109
|
merged.set(key, clamped);
|
|
38212
39110
|
continue;
|
|
38213
39111
|
}
|
|
38214
|
-
if (
|
|
39112
|
+
if (actionRank(clamped.action) > actionRank(existing.action)) {
|
|
38215
39113
|
merged.set(key, clamped);
|
|
38216
39114
|
}
|
|
38217
39115
|
}
|
|
@@ -38244,6 +39142,8 @@ var AttachedDataGateway = class {
|
|
|
38244
39142
|
);
|
|
38245
39143
|
if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
|
|
38246
39144
|
this.deps.local.markCaptureDelivered(record2.event, Date.now());
|
|
39145
|
+
} else {
|
|
39146
|
+
this.deps.local.markCaptureOwed(record2.event);
|
|
38247
39147
|
}
|
|
38248
39148
|
}
|
|
38249
39149
|
async ensureInventory(ctx) {
|
|
@@ -38280,9 +39180,10 @@ var AttachedDataGateway = class {
|
|
|
38280
39180
|
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
38281
39181
|
async recordAuditEvent(event) {
|
|
38282
39182
|
await this.deps.local.recordAuditEvent(event);
|
|
38283
|
-
await this.deps.forward.run(
|
|
39183
|
+
const forwarded = await this.deps.forward.run(
|
|
38284
39184
|
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
38285
39185
|
);
|
|
39186
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
38286
39187
|
}
|
|
38287
39188
|
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
38288
39189
|
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
@@ -38291,44 +39192,170 @@ var AttachedDataGateway = class {
|
|
|
38291
39192
|
// which would write the event to the local store a second time.
|
|
38292
39193
|
async recordLlmCall(input2) {
|
|
38293
39194
|
await this.deps.local.recordLlmCall(input2);
|
|
38294
|
-
|
|
38295
|
-
|
|
38296
|
-
|
|
38297
|
-
)
|
|
39195
|
+
const event = llmAuditEvent(input2);
|
|
39196
|
+
const forwarded = await this.deps.forward.run(
|
|
39197
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
38298
39198
|
);
|
|
39199
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
38299
39200
|
}
|
|
38300
39201
|
/**
|
|
38301
|
-
* Forward one batch
|
|
39202
|
+
* Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
|
|
39203
|
+
*
|
|
39204
|
+
* This used to send one HTTP request per event, which is what made the batch
|
|
39205
|
+
* budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
|
|
39206
|
+
* away everything after them. The same rows now cross 50 at a time over
|
|
39207
|
+
* `POST /v1/audit-events/batch` — the route the attach-time drain has always
|
|
39208
|
+
* used — so the same budget admits ~750. The wire cap is the server's own
|
|
39209
|
+
* constant, sized against server cost, and the client REFUSES a longer array
|
|
39210
|
+
* client-side, so the chunking here is not a convention.
|
|
39211
|
+
*
|
|
39212
|
+
* Still serial, and still for the original reason: firing N requests at once
|
|
39213
|
+
* would trade a latency problem for a burst the plane's per-key rate limiting
|
|
39214
|
+
* answers with the refusals the breaker then counts. Fewer, fuller requests is
|
|
39215
|
+
* the fix; more concurrent ones is not.
|
|
39216
|
+
*
|
|
39217
|
+
* When the deadline passes the remainder is dropped rather than sent: the
|
|
39218
|
+
* local write has already succeeded, so every caller has a correct result to
|
|
39219
|
+
* return. What is dropped is COUNTED, everywhere it can happen — this path
|
|
39220
|
+
* returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
|
|
39221
|
+
* `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
|
|
39222
|
+
* the breaker closed, renders a healthy block, and discards the tail of every
|
|
39223
|
+
* batch indefinitely. The SAME tally also covers a single that fails inside
|
|
39224
|
+
* the per-item retry below — the breaker opening mid-retry is a failure the
|
|
39225
|
+
* breaker's own state DOES capture, but the events still in this chunk once
|
|
39226
|
+
* that happens are neither delivered nor otherwise counted anywhere, which is
|
|
39227
|
+
* the same invisibility with a different cause.
|
|
38302
39228
|
*
|
|
38303
|
-
*
|
|
38304
|
-
*
|
|
38305
|
-
*
|
|
38306
|
-
*
|
|
38307
|
-
*
|
|
39229
|
+
* `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
|
|
39230
|
+
* single-event ack and at fifty times the blast radius here:
|
|
39231
|
+
* `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
|
|
39232
|
+
* not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
|
|
39233
|
+
* fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
|
|
39234
|
+
* delivered and never re-offer the twenty the plane did not take. So success
|
|
39235
|
+
* is checked against `chunk.length`; anything short of it falls into the same
|
|
39236
|
+
* per-item pass as a refused chunk, which is the only way to recover the
|
|
39237
|
+
* rows that did not land, since the ack carries no per-row verdict to
|
|
39238
|
+
* resend by.
|
|
38308
39239
|
*
|
|
38309
|
-
*
|
|
38310
|
-
*
|
|
38311
|
-
*
|
|
39240
|
+
* That fallback ASSUMES a re-send of an already-landed row is a harmless
|
|
39241
|
+
* no-op rather than a second cost — an assumption this file cannot verify.
|
|
39242
|
+
* `AuditEventBatchAck` carries only `accepted`, unlike its sibling
|
|
39243
|
+
* `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
|
|
39244
|
+
* the batch size as the invariant `recordCapture` reads), so whether a
|
|
39245
|
+
* duplicate counts toward THIS route's `accepted` is not expressed
|
|
39246
|
+
* anywhere in this repo. If it follows its sibling's convention and does
|
|
39247
|
+
* NOT, a chunk containing even one already-delivered row — the ordinary
|
|
39248
|
+
* consequence of a lost stamp, which this file already treats as cheap —
|
|
39249
|
+
* answers short forever and enters the per-item pass on every pass it is
|
|
39250
|
+
* offered again. The cost of that is bounded rather than silent: the
|
|
39251
|
+
* pass converges (every row lands and stamps), so it is one wasted round
|
|
39252
|
+
* of singles rather than a stall, and it errs toward an extra resend
|
|
39253
|
+
* rather than toward the lost row the alternative risks.
|
|
38312
39254
|
*
|
|
38313
|
-
*
|
|
38314
|
-
*
|
|
38315
|
-
*
|
|
38316
|
-
*
|
|
38317
|
-
*
|
|
38318
|
-
*
|
|
39255
|
+
* BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
|
|
39256
|
+
* one transaction, so a full 2xx settles every event in it and a non-2xx
|
|
39257
|
+
* settles none — which is why the whole chunk is stamped together on a FULL
|
|
39258
|
+
* accept and none of it otherwise. THREE reasons do not deserve whole-chunk
|
|
39259
|
+
* treatment, alongside a short accept, and all are re-sent one event at a
|
|
39260
|
+
* time:
|
|
39261
|
+
*
|
|
39262
|
+
* `invalid-request` a chunk the client refused to send at all. One malformed
|
|
39263
|
+
* event would otherwise cost the 49 good ones beside it —
|
|
39264
|
+
* a new way to lose data introduced by the very change
|
|
39265
|
+
* meant to stop losing it.
|
|
39266
|
+
* `route-absent` a deployment that predates the batch route. The
|
|
39267
|
+
* single-event route is the one it serves, and re-sending
|
|
39268
|
+
* here rather than inside the client is what gives each
|
|
39269
|
+
* request its own budget instead of 50 inside one.
|
|
39270
|
+
* `rejected` the deployment's SERVER-side twin of `invalid-request` —
|
|
39271
|
+
* a 4xx body refusal from schema drift on the other side
|
|
39272
|
+
* of the wire. Settlement is batch-atomic on this reason
|
|
39273
|
+
* exactly as on the others, so leaving it out would cost
|
|
39274
|
+
* the whole chunk for one event the DEPLOYMENT considers
|
|
39275
|
+
* malformed, where the per-item form cost only that one.
|
|
39276
|
+
*
|
|
39277
|
+
* Every other reason (breaker-open, a refusal, a timeout) applies to the whole
|
|
39278
|
+
* chunk, and re-sending it item by item would just spend the budget failing 50
|
|
39279
|
+
* more times — for those, the blast radius stays exactly what it was before
|
|
39280
|
+
* batching.
|
|
38319
39281
|
*/
|
|
38320
39282
|
async forwardBatch(inputs, toEvent) {
|
|
38321
39283
|
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
38322
|
-
|
|
38323
|
-
|
|
38324
|
-
|
|
38325
|
-
|
|
38326
|
-
|
|
39284
|
+
const delivered = [];
|
|
39285
|
+
try {
|
|
39286
|
+
for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
|
|
39287
|
+
const now = Date.now();
|
|
39288
|
+
if (now >= deadline) {
|
|
39289
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
39290
|
+
return;
|
|
39291
|
+
}
|
|
39292
|
+
const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
|
|
39293
|
+
const forwarded = await this.deps.forward.run(
|
|
39294
|
+
() => this.deps.client.recordAuditEvents(
|
|
39295
|
+
chunk.map((event) => reKeyForForward(event, this.remoteInventory))
|
|
39296
|
+
)
|
|
39297
|
+
);
|
|
39298
|
+
if (forwarded.ok) {
|
|
39299
|
+
if (forwarded.value.accepted === chunk.length) {
|
|
39300
|
+
delivered.push(...chunk);
|
|
39301
|
+
continue;
|
|
39302
|
+
}
|
|
39303
|
+
} else if (
|
|
39304
|
+
// THREE reasons are worth a second pass, one at a time, and they are
|
|
39305
|
+
// the three settled BEFORE the control plane refused anything, or
|
|
39306
|
+
// (for `rejected`) refused the BODY rather than the connection.
|
|
39307
|
+
//
|
|
39308
|
+
// `invalid-request` — the CLIENT refused the body before any request
|
|
39309
|
+
// went out: a defect in one event, not an outage. Re-sending singly
|
|
39310
|
+
// isolates the bad one instead of charging its 49 neighbours for it.
|
|
39311
|
+
//
|
|
39312
|
+
// `route-absent` — the deployment predates the batch route and serves
|
|
39313
|
+
// only the single-event one. The retry IS the compatibility path, and
|
|
39314
|
+
// it has to live HERE rather than inside the client: each single gets
|
|
39315
|
+
// its own FORWARD_BUDGET_MS through `run`, whereas the client's own
|
|
39316
|
+
// fallback would spend 50 sequential round trips inside the ONE
|
|
39317
|
+
// budget wrapping this call — turning a working older deployment into
|
|
39318
|
+
// a timeout, three of those into an open breaker, and every row into
|
|
39319
|
+
// a silent drop while the status surface called an answering
|
|
39320
|
+
// deployment down.
|
|
39321
|
+
//
|
|
39322
|
+
// `rejected` — the deployment's own 4xx refusal of the body, the
|
|
39323
|
+
// server-side twin of `invalid-request`: isolating it the same way
|
|
39324
|
+
// costs one event instead of the whole chunk for a defect the
|
|
39325
|
+
// deployment considers local to one row.
|
|
39326
|
+
//
|
|
39327
|
+
// Every other reason (breaker-open, a refusal, a timeout) applies to
|
|
39328
|
+
// the whole chunk; re-sending it item by item would just spend the
|
|
39329
|
+
// budget failing 50 more times.
|
|
39330
|
+
forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
|
|
39331
|
+
) {
|
|
39332
|
+
continue;
|
|
39333
|
+
}
|
|
39334
|
+
for (const [j, event] of chunk.entries()) {
|
|
39335
|
+
const at = Date.now();
|
|
39336
|
+
if (at >= deadline) {
|
|
39337
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
39338
|
+
return;
|
|
39339
|
+
}
|
|
39340
|
+
const single = await this.deps.forward.run(
|
|
39341
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
39342
|
+
);
|
|
39343
|
+
if (single.ok) {
|
|
39344
|
+
delivered.push(event);
|
|
39345
|
+
continue;
|
|
39346
|
+
}
|
|
39347
|
+
if (single.reason === "breaker-open") {
|
|
39348
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
39349
|
+
return;
|
|
39350
|
+
}
|
|
39351
|
+
recordForwardDrops(this.deps.dataDir, 1, at);
|
|
39352
|
+
}
|
|
39353
|
+
}
|
|
39354
|
+
} finally {
|
|
39355
|
+
try {
|
|
39356
|
+
this.deps.local.markAuditEventsDelivered(delivered, Date.now());
|
|
39357
|
+
} catch {
|
|
38327
39358
|
}
|
|
38328
|
-
const input2 = inputs[i];
|
|
38329
|
-
await this.deps.forward.run(
|
|
38330
|
-
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
|
|
38331
|
-
);
|
|
38332
39359
|
}
|
|
38333
39360
|
}
|
|
38334
39361
|
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
@@ -38371,9 +39398,10 @@ var AttachedDataGateway = class {
|
|
|
38371
39398
|
// local store.
|
|
38372
39399
|
async recordConfigScan(record2) {
|
|
38373
39400
|
await this.deps.local.recordConfigScan(record2);
|
|
38374
|
-
await this.deps.forward.run(
|
|
39401
|
+
const forwarded = await this.deps.forward.run(
|
|
38375
39402
|
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
38376
39403
|
);
|
|
39404
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
|
|
38377
39405
|
}
|
|
38378
39406
|
async recordBlockedDetection(entry) {
|
|
38379
39407
|
return this.deps.local.recordBlockedDetection(entry);
|
|
@@ -38507,6 +39535,18 @@ var AttachedDataGateway = class {
|
|
|
38507
39535
|
// exactly what it did, leaving the whole control inert on every device
|
|
38508
39536
|
// while every test around it stayed green.
|
|
38509
39537
|
prohibitedModels: cached2.prohibitedModels
|
|
39538
|
+
// ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
|
|
39539
|
+
// merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
|
|
39540
|
+
// it emits, so an 'authored' policy arriving from the control plane
|
|
39541
|
+
// keeps that marker even where the clamp rebuilds it with a stronger
|
|
39542
|
+
// action. The device reads it in exactly one direction — the rules such a
|
|
39543
|
+
// policy targets are not locally re-assignable — so it sits on the
|
|
39544
|
+
// `prohibitedModels` side of the line for the same reason that field
|
|
39545
|
+
// does: it can only ever ADD a refusal, never relax one, and an unsigned
|
|
39546
|
+
// cache therefore has no relaxation to grant by carrying it. Dropping it
|
|
39547
|
+
// would be the silent failure rather than the safe one — the action would
|
|
39548
|
+
// still be enforced while the local override the organization authored
|
|
39549
|
+
// away quietly came back.
|
|
38510
39550
|
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
38511
39551
|
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
38512
39552
|
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
@@ -38546,10 +39586,10 @@ var AttachedDataGateway = class {
|
|
|
38546
39586
|
//
|
|
38547
39587
|
// Implementing these is what actually closes the skipped-local-maintenance
|
|
38548
39588
|
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
38549
|
-
// any object carrying all
|
|
39589
|
+
// any object carrying them all, so the composite qualifies and SessionStart
|
|
38550
39590
|
// runs maintenance on the device's real store.
|
|
38551
39591
|
//
|
|
38552
|
-
// ⚠
|
|
39592
|
+
// ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
38553
39593
|
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
38554
39594
|
// return value directly; declaring them `async` here would hand those call
|
|
38555
39595
|
// sites a Promise and silently break both.
|
|
@@ -38572,9 +39612,15 @@ var AttachedDataGateway = class {
|
|
|
38572
39612
|
// Delegated like the rest, and SYNCHRONOUS for the reason the note above
|
|
38573
39613
|
// gives: `recordCapture` calls it after the forward has already settled, on a
|
|
38574
39614
|
// path that has nothing left to await.
|
|
39615
|
+
markCaptureOwed(event) {
|
|
39616
|
+
this.deps.local.markCaptureOwed(event);
|
|
39617
|
+
}
|
|
38575
39618
|
markCaptureDelivered(event, atMs) {
|
|
38576
39619
|
this.deps.local.markCaptureDelivered(event, atMs);
|
|
38577
39620
|
}
|
|
39621
|
+
markAuditEventsDelivered(events, atMs) {
|
|
39622
|
+
this.deps.local.markAuditEventsDelivered(events, atMs);
|
|
39623
|
+
}
|
|
38578
39624
|
};
|
|
38579
39625
|
function reKeyForForward(event, remote) {
|
|
38580
39626
|
if (remote === null) {
|
|
@@ -38617,281 +39663,17 @@ function toolAuditEvent(input2) {
|
|
|
38617
39663
|
}
|
|
38618
39664
|
|
|
38619
39665
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
38620
|
-
import { readFileSync as
|
|
38621
|
-
import { join as
|
|
39666
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
39667
|
+
import { join as join25 } from "path";
|
|
38622
39668
|
|
|
38623
39669
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
38624
39670
|
import { createHash as createHash6 } from "crypto";
|
|
38625
39671
|
import { hostname as hostname5 } from "os";
|
|
38626
39672
|
|
|
38627
|
-
// ../../packages/
|
|
38628
|
-
|
|
38629
|
-
|
|
38630
|
-
var
|
|
38631
|
-
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
38632
|
-
var RemoteRequestError = class extends Error {
|
|
38633
|
-
constructor(status) {
|
|
38634
|
-
super(`control-plane request failed with status ${String(status)}`);
|
|
38635
|
-
this.status = status;
|
|
38636
|
-
this.name = "RemoteRequestError";
|
|
38637
|
-
}
|
|
38638
|
-
status;
|
|
38639
|
-
};
|
|
38640
|
-
var RemoteRequestInvalid = class extends Error {
|
|
38641
|
-
constructor(route2, cause) {
|
|
38642
|
-
super(`refusing to send a malformed body to ${route2}`);
|
|
38643
|
-
this.cause = cause;
|
|
38644
|
-
this.name = "RemoteRequestInvalid";
|
|
38645
|
-
}
|
|
38646
|
-
cause;
|
|
38647
|
-
};
|
|
38648
|
-
var RemoteResponseInvalid = class extends Error {
|
|
38649
|
-
constructor(route2, detail) {
|
|
38650
|
-
super(`control plane answered ${route2} with ${detail}`);
|
|
38651
|
-
this.name = "RemoteResponseInvalid";
|
|
38652
|
-
}
|
|
38653
|
-
};
|
|
38654
|
-
var RemoteTransportError = class extends Error {
|
|
38655
|
-
/**
|
|
38656
|
-
* The status the peer sent, when headers arrived and only the BODY was
|
|
38657
|
-
* refused.
|
|
38658
|
-
*
|
|
38659
|
-
* Undefined for the ordinary case this class was written for — no answer at
|
|
38660
|
-
* all. It exists because two paths reject after a status has already been
|
|
38661
|
-
* delivered: an oversized body and an aborted response. Discarding it there
|
|
38662
|
-
* reported a deployment answering 401 with a verbose body as a network
|
|
38663
|
-
* outage, which sends the reader to look at their network instead of their
|
|
38664
|
-
* credential.
|
|
38665
|
-
*/
|
|
38666
|
-
constructor(reason, status) {
|
|
38667
|
-
super(`control-plane request did not complete: ${reason}`);
|
|
38668
|
-
this.status = status;
|
|
38669
|
-
this.name = "RemoteTransportError";
|
|
38670
|
-
}
|
|
38671
|
-
status;
|
|
38672
|
-
};
|
|
38673
|
-
async function send(options) {
|
|
38674
|
-
const url2 = new URL(options.url);
|
|
38675
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
38676
|
-
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
38677
|
-
const requestOptions = {
|
|
38678
|
-
method: options.method,
|
|
38679
|
-
headers: {
|
|
38680
|
-
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
38681
|
-
// last they win, and two of the values below are ones no caller may
|
|
38682
|
-
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
38683
|
-
// byte count that stops a multi-byte body being truncated by the
|
|
38684
|
-
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
38685
|
-
// function, so "no caller does that today" is not the guarantee to rely
|
|
38686
|
-
// on. The one header any caller actually passes — `if-none-match` on the
|
|
38687
|
-
// conditional GET — is untouched by this order.
|
|
38688
|
-
...options.headers,
|
|
38689
|
-
// The credential. One header, matching what the deployment authenticates
|
|
38690
|
-
// on; a second copy in an `Authorization` header would be one more place
|
|
38691
|
-
// it can be logged by an intermediary for no gain.
|
|
38692
|
-
//
|
|
38693
|
-
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
38694
|
-
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
38695
|
-
// key differently from an absent one, and "the header is not there" is
|
|
38696
|
-
// the property the attach flow needs.
|
|
38697
|
-
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
38698
|
-
accept: "application/json",
|
|
38699
|
-
...options.body === void 0 ? {} : {
|
|
38700
|
-
"content-type": "application/json",
|
|
38701
|
-
// Byte length, not string length: a multi-byte body sent with a
|
|
38702
|
-
// character count is truncated by the receiver.
|
|
38703
|
-
"content-length": String(Buffer.byteLength(options.body))
|
|
38704
|
-
}
|
|
38705
|
-
}
|
|
38706
|
-
};
|
|
38707
|
-
return new Promise((resolve3, reject) => {
|
|
38708
|
-
let settled = false;
|
|
38709
|
-
const fail2 = (reason, status) => {
|
|
38710
|
-
if (settled) return;
|
|
38711
|
-
settled = true;
|
|
38712
|
-
reject(new RemoteTransportError(reason, status));
|
|
38713
|
-
};
|
|
38714
|
-
const req = send_(url2, requestOptions, (res) => {
|
|
38715
|
-
const chunks = [];
|
|
38716
|
-
let size = 0;
|
|
38717
|
-
res.on("data", (chunk) => {
|
|
38718
|
-
size += chunk.length;
|
|
38719
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
38720
|
-
fail2(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
38721
|
-
res.destroy();
|
|
38722
|
-
req.destroy();
|
|
38723
|
-
return;
|
|
38724
|
-
}
|
|
38725
|
-
chunks.push(chunk);
|
|
38726
|
-
});
|
|
38727
|
-
res.on("aborted", () => {
|
|
38728
|
-
fail2("the response was aborted", res.statusCode);
|
|
38729
|
-
});
|
|
38730
|
-
res.on("end", () => {
|
|
38731
|
-
if (settled) return;
|
|
38732
|
-
settled = true;
|
|
38733
|
-
resolve3({
|
|
38734
|
-
status: res.statusCode ?? 0,
|
|
38735
|
-
headers: res.headers,
|
|
38736
|
-
body: Buffer.concat(chunks).toString("utf8")
|
|
38737
|
-
});
|
|
38738
|
-
});
|
|
38739
|
-
});
|
|
38740
|
-
const deadline = setTimeout(() => {
|
|
38741
|
-
fail2(`no response within ${String(timeoutMs)}ms`);
|
|
38742
|
-
req.destroy();
|
|
38743
|
-
}, timeoutMs);
|
|
38744
|
-
deadline.unref();
|
|
38745
|
-
req.on("upgrade", (_res, socket) => {
|
|
38746
|
-
fail2("the deployment answered with a protocol upgrade");
|
|
38747
|
-
socket.destroy();
|
|
38748
|
-
});
|
|
38749
|
-
req.on("close", () => {
|
|
38750
|
-
fail2("the connection closed before a response was read");
|
|
38751
|
-
clearTimeout(deadline);
|
|
38752
|
-
});
|
|
38753
|
-
req.on("error", (err) => {
|
|
38754
|
-
fail2(err.message);
|
|
38755
|
-
});
|
|
38756
|
-
if (options.body !== void 0) req.write(options.body);
|
|
38757
|
-
req.end();
|
|
38758
|
-
});
|
|
38759
|
-
}
|
|
38760
|
-
|
|
38761
|
-
// ../../packages/remote/src/client.ts
|
|
38762
|
-
var ROUTES = {
|
|
38763
|
-
events: "/v1/events",
|
|
38764
|
-
auditEvents: "/v1/audit-events",
|
|
38765
|
-
auditEventsBatch: "/v1/audit-events/batch",
|
|
38766
|
-
inventory: "/v1/inventory",
|
|
38767
|
-
storePosture: "/v1/store-posture",
|
|
38768
|
-
policyBundle: "/v1/policy-bundle",
|
|
38769
|
-
whoami: "/v1/plugin/whoami",
|
|
38770
|
-
shares: "/v1/shares"
|
|
38771
|
-
};
|
|
38772
|
-
function headerValue(response, name) {
|
|
38773
|
-
const raw = response.headers[name];
|
|
38774
|
-
if (raw === void 0) return void 0;
|
|
38775
|
-
return Array.isArray(raw) ? raw[0] : raw;
|
|
38776
|
-
}
|
|
38777
|
-
function okBody(response) {
|
|
38778
|
-
if (response.status < 200 || response.status >= 300) {
|
|
38779
|
-
throw new RemoteRequestError(response.status);
|
|
38780
|
-
}
|
|
38781
|
-
return response.body;
|
|
38782
|
-
}
|
|
38783
|
-
function parsed(schema, body, route2) {
|
|
38784
|
-
let json2;
|
|
38785
|
-
try {
|
|
38786
|
-
json2 = JSON.parse(body);
|
|
38787
|
-
} catch {
|
|
38788
|
-
throw new RemoteResponseInvalid(route2, "a body that is not JSON");
|
|
38789
|
-
}
|
|
38790
|
-
const result = schema.safeParse(json2);
|
|
38791
|
-
if (!result.success) {
|
|
38792
|
-
throw new RemoteResponseInvalid(route2, "a body this client cannot read");
|
|
38793
|
-
}
|
|
38794
|
-
return result.data;
|
|
38795
|
-
}
|
|
38796
|
-
function withoutTrailingSlashes(endpoint) {
|
|
38797
|
-
let end = endpoint.length;
|
|
38798
|
-
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
38799
|
-
return endpoint.slice(0, end);
|
|
38800
|
-
}
|
|
38801
|
-
var SLASH = "/".charCodeAt(0);
|
|
38802
|
-
function createRemoteClient(options) {
|
|
38803
|
-
const base = withoutTrailingSlashes(options.endpoint);
|
|
38804
|
-
const url2 = (route2) => `${base}${route2}`;
|
|
38805
|
-
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
38806
|
-
const sendOne = async (event) => {
|
|
38807
|
-
const validated = RecordAuditEventRequest.safeParse(event);
|
|
38808
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
38809
|
-
const response = await send({
|
|
38810
|
-
...common,
|
|
38811
|
-
method: "POST",
|
|
38812
|
-
url: url2(ROUTES.auditEvents),
|
|
38813
|
-
body: JSON.stringify(validated.data)
|
|
38814
|
-
});
|
|
38815
|
-
okBody(response);
|
|
38816
|
-
};
|
|
38817
|
-
return {
|
|
38818
|
-
async ingestEvents(batch) {
|
|
38819
|
-
const response = await send({
|
|
38820
|
-
...common,
|
|
38821
|
-
method: "POST",
|
|
38822
|
-
url: url2(ROUTES.events),
|
|
38823
|
-
body: JSON.stringify(batch)
|
|
38824
|
-
});
|
|
38825
|
-
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
38826
|
-
},
|
|
38827
|
-
async ingestInventory(context) {
|
|
38828
|
-
const response = await send({
|
|
38829
|
-
...common,
|
|
38830
|
-
method: "POST",
|
|
38831
|
-
url: url2(ROUTES.inventory),
|
|
38832
|
-
body: JSON.stringify(context)
|
|
38833
|
-
});
|
|
38834
|
-
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
38835
|
-
},
|
|
38836
|
-
async recordAuditEvent(event) {
|
|
38837
|
-
await sendOne(event);
|
|
38838
|
-
},
|
|
38839
|
-
async recordAuditEvents(events) {
|
|
38840
|
-
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
38841
|
-
if (!validated.success) {
|
|
38842
|
-
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
38843
|
-
}
|
|
38844
|
-
const response = await send({
|
|
38845
|
-
...common,
|
|
38846
|
-
method: "POST",
|
|
38847
|
-
url: url2(ROUTES.auditEventsBatch),
|
|
38848
|
-
body: JSON.stringify(validated.data)
|
|
38849
|
-
});
|
|
38850
|
-
if (response.status === 404) {
|
|
38851
|
-
for (const event of validated.data.events) await sendOne(event);
|
|
38852
|
-
return { accepted: validated.data.events.length };
|
|
38853
|
-
}
|
|
38854
|
-
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
38855
|
-
},
|
|
38856
|
-
async reportStorePosture(snapshot) {
|
|
38857
|
-
const response = await send({
|
|
38858
|
-
...common,
|
|
38859
|
-
method: "POST",
|
|
38860
|
-
url: url2(ROUTES.storePosture),
|
|
38861
|
-
body: JSON.stringify(snapshot)
|
|
38862
|
-
});
|
|
38863
|
-
okBody(response);
|
|
38864
|
-
},
|
|
38865
|
-
async getPolicyBundle(etag) {
|
|
38866
|
-
const response = await send({
|
|
38867
|
-
...common,
|
|
38868
|
-
method: "GET",
|
|
38869
|
-
url: url2(ROUTES.policyBundle),
|
|
38870
|
-
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
38871
|
-
});
|
|
38872
|
-
if (response.status === 304) {
|
|
38873
|
-
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
38874
|
-
}
|
|
38875
|
-
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
38876
|
-
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
38877
|
-
},
|
|
38878
|
-
async whoami() {
|
|
38879
|
-
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
38880
|
-
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
38881
|
-
},
|
|
38882
|
-
async recordProjectEgress(request) {
|
|
38883
|
-
const validated = EgressIngestRequest.safeParse(request);
|
|
38884
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
38885
|
-
const response = await send({
|
|
38886
|
-
...common,
|
|
38887
|
-
method: "POST",
|
|
38888
|
-
url: url2(ROUTES.shares),
|
|
38889
|
-
body: JSON.stringify(validated.data)
|
|
38890
|
-
});
|
|
38891
|
-
okBody(response);
|
|
38892
|
-
}
|
|
38893
|
-
};
|
|
38894
|
-
}
|
|
39673
|
+
// ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
39674
|
+
var CORRELATION_ID = EventMetadata.shape.correlationId;
|
|
39675
|
+
var TRACE_ID = EventMetadata.shape.traceId;
|
|
39676
|
+
var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
|
|
38895
39677
|
|
|
38896
39678
|
// ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
|
|
38897
39679
|
import { spawn } from "child_process";
|
|
@@ -38899,7 +39681,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
38899
39681
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
38900
39682
|
|
|
38901
39683
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
38902
|
-
import { readFileSync as
|
|
39684
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
38903
39685
|
function createPluginBlock(build, policyStore) {
|
|
38904
39686
|
return async () => {
|
|
38905
39687
|
const cached2 = await policyStore.read();
|
|
@@ -38918,7 +39700,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
38918
39700
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
38919
39701
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
38920
39702
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
38921
|
-
import { join as
|
|
39703
|
+
import { join as join26 } from "path";
|
|
38922
39704
|
|
|
38923
39705
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
38924
39706
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -38942,7 +39724,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
38942
39724
|
|
|
38943
39725
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
38944
39726
|
function createPolicyStore(dir = dataDir()) {
|
|
38945
|
-
const file2 =
|
|
39727
|
+
const file2 = join26(dir, "policy-cache.json");
|
|
38946
39728
|
async function read() {
|
|
38947
39729
|
try {
|
|
38948
39730
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -38951,22 +39733,32 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
38951
39733
|
const record2 = parsed2;
|
|
38952
39734
|
const bundle = PolicyBundle.parse(record2.bundle);
|
|
38953
39735
|
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
38954
|
-
const
|
|
39736
|
+
const stored = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
39737
|
+
const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
|
|
39738
|
+
const etag = replayable ? stored : void 0;
|
|
38955
39739
|
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
38956
39740
|
} catch {
|
|
38957
39741
|
return null;
|
|
38958
39742
|
}
|
|
38959
39743
|
}
|
|
38960
|
-
|
|
38961
|
-
|
|
38962
|
-
const
|
|
38963
|
-
|
|
38964
|
-
|
|
38965
|
-
|
|
38966
|
-
|
|
39744
|
+
function knowsMoreThanThisBuild(shapeId) {
|
|
39745
|
+
if (typeof shapeId !== "string" || shapeId === "") return false;
|
|
39746
|
+
const theirs = new Set(shapeId.split(","));
|
|
39747
|
+
const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
|
|
39748
|
+
return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
|
|
39749
|
+
}
|
|
39750
|
+
async function priorRecord() {
|
|
39751
|
+
try {
|
|
39752
|
+
const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
|
|
39753
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
39754
|
+
} catch {
|
|
39755
|
+
return null;
|
|
39756
|
+
}
|
|
39757
|
+
}
|
|
39758
|
+
async function publishRecord(record2) {
|
|
38967
39759
|
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
38968
39760
|
try {
|
|
38969
|
-
await writeFile2(tmp, JSON.stringify(
|
|
39761
|
+
await writeFile2(tmp, JSON.stringify(record2), {
|
|
38970
39762
|
encoding: "utf8",
|
|
38971
39763
|
mode: DATA_FILE_MODE,
|
|
38972
39764
|
flag: "wx"
|
|
@@ -38977,6 +39769,27 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
38977
39769
|
throw err;
|
|
38978
39770
|
}
|
|
38979
39771
|
}
|
|
39772
|
+
async function write(bundle, etag) {
|
|
39773
|
+
await ensureDataDir(dir);
|
|
39774
|
+
const prior = await priorRecord();
|
|
39775
|
+
const priorVersion = prior?.bundle?.version;
|
|
39776
|
+
if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
|
|
39777
|
+
await publishRecord({
|
|
39778
|
+
...prior,
|
|
39779
|
+
fetchedAtMs: Date.now()
|
|
39780
|
+
});
|
|
39781
|
+
return;
|
|
39782
|
+
}
|
|
39783
|
+
await publishRecord({
|
|
39784
|
+
bundle,
|
|
39785
|
+
fetchedAtMs: Date.now(),
|
|
39786
|
+
// Stamped on EVERY write, the 304 arm's included: that arm hands back the
|
|
39787
|
+
// bundle it already holds, and the point of the stamp is to describe the
|
|
39788
|
+
// build that last narrowed those bytes, which is this one.
|
|
39789
|
+
shapeId: POLICY_BUNDLE_SHAPE_ID,
|
|
39790
|
+
...etag === void 0 ? {} : { etag }
|
|
39791
|
+
});
|
|
39792
|
+
}
|
|
38980
39793
|
return { read, write, file: file2 };
|
|
38981
39794
|
}
|
|
38982
39795
|
|
|
@@ -39142,11 +39955,11 @@ function readStorePosture(dbPath2) {
|
|
|
39142
39955
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
39143
39956
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
39144
39957
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
39145
|
-
import { join as
|
|
39958
|
+
import { join as join27 } from "path";
|
|
39146
39959
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
39147
39960
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
39148
|
-
const file2 =
|
|
39149
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
39961
|
+
const file2 = join27(dir, "posture-state.json");
|
|
39962
|
+
const legacyFile = legacyDir === void 0 ? null : join27(legacyDir, "posture-state.json");
|
|
39150
39963
|
async function persist(state) {
|
|
39151
39964
|
await ensureDataDir(dir);
|
|
39152
39965
|
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
@@ -39214,8 +40027,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
39214
40027
|
}
|
|
39215
40028
|
|
|
39216
40029
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
39217
|
-
import { readFileSync as
|
|
39218
|
-
import { join as
|
|
40030
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
40031
|
+
import { join as join28 } from "path";
|
|
39219
40032
|
|
|
39220
40033
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
39221
40034
|
var REFUSAL_LINES = {
|
|
@@ -39533,9 +40346,21 @@ var StandaloneDataGateway = class {
|
|
|
39533
40346
|
// for the whole of it, so a member that threw would make that answer a lie
|
|
39534
40347
|
// the moment a composite delegated to it. A store-level no-op is the honest
|
|
39535
40348
|
// shape — a standalone machine has nothing delivered to record.
|
|
40349
|
+
markCaptureOwed(event) {
|
|
40350
|
+
this.db.markCaptureOwed(event);
|
|
40351
|
+
}
|
|
39536
40352
|
markCaptureDelivered(event, atMs) {
|
|
39537
40353
|
this.db.markCaptureDelivered(event, atMs);
|
|
39538
40354
|
}
|
|
40355
|
+
// Implemented, not stubbed, for the same reason its sibling above is: the
|
|
40356
|
+
// attached gateway is a DECORATOR over an instance of this class
|
|
40357
|
+
// (`attached/factory.ts` builds one and passes it as `deps.local`), so every
|
|
40358
|
+
// stamp the live forward makes lands here with a non-empty array. This is the
|
|
40359
|
+
// production write path for that feature, not a shape-satisfying no-op — a
|
|
40360
|
+
// machine that is merely standalone simply never calls it.
|
|
40361
|
+
markAuditEventsDelivered(events, atMs) {
|
|
40362
|
+
this.db.markAuditEventsDelivered(events, atMs);
|
|
40363
|
+
}
|
|
39539
40364
|
staleBinaryNotice(currentVersion) {
|
|
39540
40365
|
try {
|
|
39541
40366
|
const newest = this.db.installedPacks.newestRecordedBinary();
|
|
@@ -39674,17 +40499,26 @@ import { randomUUID as randomUUID19 } from "crypto";
|
|
|
39674
40499
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
39675
40500
|
|
|
39676
40501
|
// src/history/transcripts.ts
|
|
39677
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
40502
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
|
|
39678
40503
|
import { homedir as homedir3 } from "os";
|
|
39679
|
-
import { join as
|
|
40504
|
+
import { join as join29 } from "path";
|
|
39680
40505
|
function transcriptsDir(home) {
|
|
39681
|
-
return
|
|
40506
|
+
return join29(home ?? homedir3(), ".claude", "projects");
|
|
39682
40507
|
}
|
|
39683
40508
|
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
39684
40509
|
|
|
39685
40510
|
// src/remediation/redact.ts
|
|
39686
|
-
import {
|
|
39687
|
-
|
|
40511
|
+
import {
|
|
40512
|
+
lstatSync as lstatSync4,
|
|
40513
|
+
readdirSync as readdirSync7,
|
|
40514
|
+
readFileSync as readFileSync20,
|
|
40515
|
+
realpathSync as realpathSync4,
|
|
40516
|
+
renameSync as renameSync5,
|
|
40517
|
+
rmSync as rmSync8,
|
|
40518
|
+
statSync as statSync10,
|
|
40519
|
+
writeFileSync as writeFileSync10
|
|
40520
|
+
} from "fs";
|
|
40521
|
+
import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute2, join as join30, relative, resolve as resolve2 } from "path";
|
|
39688
40522
|
var REDACTED_PLACEHOLDER = "[REDACTED:SECRET]";
|
|
39689
40523
|
var REPLACE_PATTERN_SEQUENCE = /\$[$&`'<0-9]/;
|
|
39690
40524
|
function replacementFor(rawValue, replacements) {
|
|
@@ -39715,6 +40549,41 @@ function resolveRedactableArtifact(filePath, scope) {
|
|
|
39715
40549
|
if (realTarget === null) return null;
|
|
39716
40550
|
return scope.artifactRoots.some((root) => isWithinRoot(realTarget, root)) ? realTarget : null;
|
|
39717
40551
|
}
|
|
40552
|
+
var TMP_SUFFIX = ".aka-redact.tmp";
|
|
40553
|
+
function tempPathFor(realPath) {
|
|
40554
|
+
return `${realPath}.${String(process.pid)}${TMP_SUFFIX}`;
|
|
40555
|
+
}
|
|
40556
|
+
function isProcessAlive(pid) {
|
|
40557
|
+
try {
|
|
40558
|
+
process.kill(pid, 0);
|
|
40559
|
+
return true;
|
|
40560
|
+
} catch (error61) {
|
|
40561
|
+
return error61.code !== "ESRCH";
|
|
40562
|
+
}
|
|
40563
|
+
}
|
|
40564
|
+
function sweepStrandedTemps(realPath) {
|
|
40565
|
+
const dir = dirname7(realPath);
|
|
40566
|
+
const prefix = `${basename7(realPath)}.`;
|
|
40567
|
+
let names;
|
|
40568
|
+
try {
|
|
40569
|
+
names = readdirSync7(dir);
|
|
40570
|
+
} catch {
|
|
40571
|
+
return;
|
|
40572
|
+
}
|
|
40573
|
+
for (const name of names) {
|
|
40574
|
+
if (!name.startsWith(prefix) || !name.endsWith(TMP_SUFFIX)) continue;
|
|
40575
|
+
const pidPart = name.slice(prefix.length, name.length - TMP_SUFFIX.length);
|
|
40576
|
+
if (!/^\d+$/.test(pidPart)) continue;
|
|
40577
|
+
const pid = Number(pidPart);
|
|
40578
|
+
if (pid !== process.pid && isProcessAlive(pid)) continue;
|
|
40579
|
+
try {
|
|
40580
|
+
const stranded = join30(dir, name);
|
|
40581
|
+
if (!lstatSync4(stranded).isFile()) continue;
|
|
40582
|
+
rmSync8(stranded, { force: true });
|
|
40583
|
+
} catch {
|
|
40584
|
+
}
|
|
40585
|
+
}
|
|
40586
|
+
}
|
|
39718
40587
|
function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), replacements) {
|
|
39719
40588
|
const byFile = /* @__PURE__ */ new Map();
|
|
39720
40589
|
for (const target of targets) {
|
|
@@ -39730,11 +40599,14 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
|
|
|
39730
40599
|
const struck = [];
|
|
39731
40600
|
for (const [filePath, fileTargets] of byFile) {
|
|
39732
40601
|
let content;
|
|
40602
|
+
let mode;
|
|
39733
40603
|
try {
|
|
39734
|
-
|
|
40604
|
+
mode = statSync10(filePath).mode & 511;
|
|
40605
|
+
content = readFileSync20(filePath, "utf8");
|
|
39735
40606
|
} catch {
|
|
39736
40607
|
continue;
|
|
39737
40608
|
}
|
|
40609
|
+
sweepStrandedTemps(filePath);
|
|
39738
40610
|
const struckHere = [];
|
|
39739
40611
|
let pointeredHere = 0;
|
|
39740
40612
|
const applied = /* @__PURE__ */ new Map();
|
|
@@ -39759,9 +40631,9 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
|
|
|
39759
40631
|
if (replacement !== REDACTED_PLACEHOLDER) pointeredHere += 1;
|
|
39760
40632
|
}
|
|
39761
40633
|
if (struckHere.length === 0) continue;
|
|
39762
|
-
const tmpPath =
|
|
40634
|
+
const tmpPath = tempPathFor(filePath);
|
|
39763
40635
|
try {
|
|
39764
|
-
writeFileSync10(tmpPath, content);
|
|
40636
|
+
writeFileSync10(tmpPath, content, { mode, flag: "wx" });
|
|
39765
40637
|
renameSync5(tmpPath, filePath);
|
|
39766
40638
|
} catch {
|
|
39767
40639
|
try {
|
|
@@ -39817,7 +40689,8 @@ async function buildPointerReplacements(recovered, base) {
|
|
|
39817
40689
|
const replacement = await glue.tokenizeValue(rawValue, {
|
|
39818
40690
|
ruleId: entry.ruleId,
|
|
39819
40691
|
category: entry.category,
|
|
39820
|
-
maskedMatch: maskMatch(rawValue)
|
|
40692
|
+
maskedMatch: maskMatch(rawValue),
|
|
40693
|
+
userAuthorized: true
|
|
39821
40694
|
});
|
|
39822
40695
|
if (replacement.startsWith("[[aka:") && replacement !== rawValue) {
|
|
39823
40696
|
replacements.set(rawValue, replacement);
|
|
@@ -39854,7 +40727,7 @@ async function redactSurfacedSecrets(findings, overrides = {}) {
|
|
|
39854
40727
|
for (const [filePath, fileFindings] of byFile) {
|
|
39855
40728
|
let content;
|
|
39856
40729
|
try {
|
|
39857
|
-
content =
|
|
40730
|
+
content = readFileSync21(filePath, "utf8");
|
|
39858
40731
|
} catch {
|
|
39859
40732
|
unrecovered.push(...fileFindings);
|
|
39860
40733
|
continue;
|
|
@@ -40019,7 +40892,7 @@ if (process.argv[1] && fileURLToPath5(import.meta.url) === process.argv[1]) {
|
|
|
40019
40892
|
try {
|
|
40020
40893
|
const argv = process.argv.slice(2);
|
|
40021
40894
|
const optionIndex = argv.indexOf("--option");
|
|
40022
|
-
const frameText =
|
|
40895
|
+
const frameText = readFileSync22(0, "utf8");
|
|
40023
40896
|
if (optionIndex === -1) {
|
|
40024
40897
|
present(frameText);
|
|
40025
40898
|
} else {
|